// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

use crate::types::builder::{Builder, Configurator};
use crate::types::{IntoOperatorUri, OperatorUri};
use crate::{Error, ErrorKind, Operator, Result};
use std::collections::HashMap;
use std::sync::{LazyLock, Mutex};

/// Factory signature used to construct [`Operator`] from a URI and extra options.
pub type OperatorFactory = fn(&OperatorUri) -> Result<Operator>;

/// Global registry that maps schemes to [`OperatorFactory`] functions.
#[derive(Debug)]
pub struct OperatorRegistry {
    factories: Mutex<HashMap<String, OperatorFactory>>,
}

impl OperatorRegistry {
    /// Get the global registry.
    pub fn get() -> &'static Self {
        /// The global registry used by [`Operator::from_uri`].
        ///
        /// `memory` is always registered here since it's used pervasively in unit tests
        /// and as a zero-dependency backend.
        ///
        /// Other optional service registrations are handled by the facade crate `opendal`.
        static OPERATOR_REGISTRY: LazyLock<OperatorRegistry> = LazyLock::new(|| {
            let factories = Mutex::new(HashMap::default());
            let registry = OperatorRegistry { factories };
            crate::services::register_memory_service(&registry);
            registry
        });

        &OPERATOR_REGISTRY
    }

    /// Register a builder for the given scheme.
    pub fn register<B: Builder>(&self, scheme: &str) {
        let key = scheme.to_ascii_lowercase();
        let mut guard = self
            .factories
            .lock()
            .expect("operator registry mutex poisoned");
        guard.insert(key, factory::<B::Config>);
    }

    /// Load an [`Operator`] via the factory registered for the URI's scheme.
    pub fn load(&self, uri: impl IntoOperatorUri) -> Result<Operator> {
        let parsed = uri.into_operator_uri()?;
        let scheme = parsed.scheme();

        let factory = self
            .factories
            .lock()
            .expect("operator registry mutex poisoned")
            .get(scheme)
            .copied()
            .ok_or_else(|| {
                Error::new(ErrorKind::Unsupported, "scheme is not registered")
                    .with_context("scheme", scheme.to_string())
            })?;

        factory(&parsed)
    }
}

/// Factory adapter that builds an operator from a configurator type.
fn factory<C: Configurator>(uri: &OperatorUri) -> Result<Operator> {
    let cfg = C::from_uri(uri)?;
    Operator::from_config(cfg)
}
