#[cfg(feature = "source_browser")]
pub mod browser;
pub mod local;
pub mod remote;

use crate::prelude::*;

use iref::InvalidIri;
use taganak_core::{
    default_graph_view,
    graphs::{either_graph::{EitherGraph3, EitherGraph4, EitherGraph5}, GraphView},
    terms::Iri,
};

use futures::Stream;

use taganak_orm::GraphORM;

pub trait GraphConfig: Clone {
    type Graph: ConfigurableGraph<Config = Self>;

    fn into_graph(self) -> Result<Self::Graph, SourceError>
    where
        Self: Sized,
    {
        Self::Graph::from_config(self)
    }
}

pub trait ConfigurableGraph: Graph {
    type Config: GraphConfig<Graph = Self>;

    fn from_config(config: Self::Config) -> Result<Self, SourceError>
    where
        Self: Sized;
}

#[derive(Clone, Debug, PartialEq, Eq, GraphORM, Hash)]
#[rdf(
    prefix(ns = "sdk", iri = "http://sdk.taganak.net/vocab/taganak#"),
    rdf_type = "sdk:GraphConfigs"
)]
pub enum GraphConfigs {
    #[cfg(feature = "source_local_file")]
    #[rdf(proxy(rdf_type = "sdk:LocalFileSourceGraphConfig"))]
    LocalFile(self::local::persistent::local_file::LocalFileSourceGraphConfig),
    #[cfg(feature = "source_graph_store_http")]
    #[rdf(proxy(rdf_type = "sdk:GraphStoreHttpSourceGraphConfig"))]
    GraphStoreHttp(self::remote::graph_store_http::GraphStoreHttpSourceGraphConfig),
    #[cfg(feature = "source_indexeddb")]
    #[rdf(proxy(rdf_type = "sdk:IndexedDbSourceGraphConfig"))]
    IndexedDb(self::browser::indexeddb::IndexedDbSourceGraphConfig),
    #[rdf(proxy(rdf_type = "sdk:DummyGraphConfig"))]
    Dummy,
    #[cfg(feature = "source_fidibats")]
    #[rdf(proxy(rdf_type = "sdk:FidibatsSourceGraphConfig"))]
    Fidibats(self::local::persistent::fidibats::FidibatsSourceGraphConfig)
}

impl GraphConfig for GraphConfigs {
    type Graph = ConfigurableGraphs;
}

#[cfg(feature = "source_local_file")]
type LocalFileSourceGraph = self::local::persistent::local_file::LocalFileSourceGraph;
#[cfg(not(feature = "source_local_file"))]
type LocalFileSourceGraph = ();

#[cfg(feature = "source_graph_store_http")]
type GraphStoreHttpSourceGraph = self::remote::graph_store_http::GraphStoreHttpSourceGraph;
#[cfg(not(feature = "source_graph_store_http"))]
type GraphStoreHttpSourceGraph = ();

#[cfg(feature = "source_indexeddb")]
type IndexedDbSourceGraph = self::browser::indexeddb::IndexedDbSourceGraph;
#[cfg(not(feature = "source_indexeddb"))]
type IndexedDbSourceGraph = ();

#[cfg(feature = "source_fidibats")]
type FidibatsSourceGrah = self::local::persistent::fidibats::FidibatsSourceGraph;
#[cfg(not(feature = "source_fidibats"))]
type FidibatsSourceGrah = ();

type ConfigEither =
    EitherGraph5<LocalFileSourceGraph, GraphStoreHttpSourceGraph, IndexedDbSourceGraph, FidibatsSourceGrah, ()>;
#[derive(Debug)]
pub struct ConfigurableGraphs(ConfigEither);

#[derive(Debug, thiserror::Error)]
pub enum FromGraphError {
    #[error("the requested subject is lacking any type")]
    NoType,
    #[error("the requested subject is not a valid GraphConfigs type {0:?}")]
    NotGraph(Arc<Term>),
    #[error(
        "the requested subject has no <http://sdk.taganak.net/vocab/taganak#SourceIri> predicate"
    )]
    NoSourceIri,
    #[error("the source iri is invalid: `{_0}`")]
    InvalidSourceIri(
        #[source]
        #[from]
        InvalidIri<String>,
    ),
    #[error(transparent)]
    InvalidGrahIri(#[from] FromIriError),
    #[error("the source iri of the provided subject is no literal")]
    SourceIriNotLiteral,
    #[cfg(feature = "source_graph_store_http")]
    #[error(transparent)]
    InvalidHttpSourceGraphConfig(#[from] remote::graph_store_http::FromGraphError),
    #[cfg(feature = "source_local_file")]
    #[error(transparent)]
    InvalidLocalFileConfig(#[from] local::persistent::local_file::FromGraphError),
    #[error(transparent)]
    GraphErr(#[from] GraphError),
}

#[derive(Debug, thiserror::Error)]
pub enum FromIriError {
    #[error("Invalid scheme {_0:?}")]
    InvalidScheme(iref::iri::SchemeBuf),
}

impl GraphConfigs {
    pub fn from_iri(iri: Iri) -> Result<Self, FromIriError> {
        let scheme = iri.scheme();
        Ok(match scheme.as_str() {
            #[cfg(feature = "source_local_file")]
            "file" => Self::LocalFile(
                self::local::persistent::local_file::LocalFileSourceGraphConfig::new(
                    iri,
                    None,
                    None,
                    false,
                ),
            ),
            #[cfg(feature = "source_graph_store_http")]
            "http" | "https" => Self::GraphStoreHttp(
                self::remote::graph_store_http::GraphStoreHttpSourceGraphConfig::new(iri),
            ),
            #[cfg(feature = "source_indexeddb")]
            "indexeddb" => Self::IndexedDb(
                self::browser::indexeddb::IndexedDbSourceGraphConfig::new(iri),
            ),

            #[cfg(feature = "source_fidibats")]
            "file+fidibats" => Self::Fidibats(self::local::persistent::fidibats::FidibatsSourceGraphConfig::from_iri(iri).unwrap()),
            _ => return Err(FromIriError::InvalidScheme(scheme.to_owned())),
        })
    }
}

impl ConfigurableGraph for ConfigurableGraphs {
    type Config = GraphConfigs;

    fn from_config(config: Self::Config) -> Result<Self, SourceError> {
        match config {
            #[cfg(feature = "source_local_file")]
            GraphConfigs::LocalFile(c) => Ok(Self(EitherGraph5::N1(
                self::local::persistent::local_file::LocalFileSourceGraph::from_config(c)?,
            ))),
            #[cfg(feature = "source_graph_store_http")]
            GraphConfigs::GraphStoreHttp(c) => Ok(Self(EitherGraph5::N2(
                self::remote::graph_store_http::GraphStoreHttpSourceGraph::from_config(c)?,
            ))),
            #[cfg(feature = "source_indexeddb")]
            GraphConfigs::IndexedDb(c) => Ok(Self(EitherGraph5::N3(
                self::browser::indexeddb::IndexedDbSourceGraph::from_config(c)?,
            ))),
            #[cfg(feature = "source_fidibats")]
            GraphConfigs::Fidibats(c) => Ok(Self(EitherGraph5::N4(self::local::persistent::fidibats::FidibatsSourceGraph::from_config(c)?))),
            _ => unreachable!(),
        }
    }
}

impl Graph for ConfigurableGraphs {
    type View<'g> = <ConfigEither as Graph>::View<'g>
    where
        Self: 'g;

    fn view(&self) -> impl core::future::Future<Output = Self::View<'_>> + Send {
        self.0.view()
    }

    fn add(
        &mut self,
        triple: Arc<Triple>,
    ) -> impl core::future::Future<Output = Result<(), GraphError>> + Send {
        self.0.add(triple)
    }

    fn remove(
        &mut self,
        triple: TripleRef<'_>,
    ) -> impl core::future::Future<Output = Result<(), GraphError>> + Send {
        self.0.remove(triple)
    }

    fn merge<G>(
        &mut self,
        source_graph: G,
    ) -> impl core::future::Future<Output = Result<(), GraphError>> + Send
    where
        G: GraphView,
    {
        self.0.merge(source_graph)
    }

    fn merge_stream<S, E>(
        &mut self,
        stream: S,
    ) -> impl core::future::Future<Output = Result<(), GraphError>> + Send
    where
        S: Stream<Item = Result<Arc<Triple>, E>> + Unpin + Send,
        GraphError: From<E>,
        E: Send,
    {
        self.0.merge_stream(stream)
    }

    
}
