use super::{Attributes, Identifier, ModelDesign, Parameter, SharingError, SharingResult};
use melodium_common::descriptor::{
    Collection, Entry as CommonEntry, Identifier as CommonIdentifier, Model as CommonModel,
};
use melodium_engine::{descriptor::Model as DesignedModel, LogicError};
use serde::{Deserialize, Serialize};
use std::{
    collections::{BTreeMap, HashMap},
    sync::Arc,
};

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "webassembly", derive(tsify::Tsify))]
#[cfg_attr(feature = "webassembly", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Model {
    pub identifier: Identifier,
    pub documentation: String,
    pub parameters: BTreeMap<String, Parameter>,
    pub implementation_kind: ModelImplementationKind,
    pub hierarchy: Vec<Identifier>,
    pub sources: BTreeMap<String, Vec<Identifier>>,
    pub attributes: Attributes,
}

impl Model {
    pub fn make_descriptor(&self, collection: &Collection) -> SharingResult<Arc<DesignedModel>> {
        let identifier = if let Ok(identifier) = (&self.identifier).try_into() {
            identifier
        } else {
            return SharingResult::new_failure(SharingError::invalid_identifier(
                1,
                self.identifier.clone(),
            ));
        };

        if self.implementation_kind.is_compiled() {
            return SharingResult::new_failure(SharingError::compiled_model(2, identifier));
        }

        let base_identifier: CommonIdentifier = if let Some(base_identifier) = self.hierarchy.last()
        {
            if let Ok(base_identifier) = base_identifier.try_into() {
                base_identifier
            } else {
                return SharingResult::new_failure(SharingError::invalid_identifier(
                    4,
                    self.identifier.clone(),
                ));
            }
        } else {
            return SharingResult::new_failure(SharingError::missing_base_identifier(
                3, identifier,
            ));
        };

        let base_model = if let Some(melodium_common::descriptor::Entry::Model(base_model)) =
            collection.get(&(&base_identifier).into())
        {
            base_model
        } else {
            return SharingResult::new_failure(
                LogicError::unexisting_model(230, identifier.clone(), base_identifier.into(), None)
                    .into(),
            );
        };

        let mut result = SharingResult::new_success(());
        let mut descriptor = DesignedModel::new(identifier.clone(), base_model);

        descriptor.set_documentation(&self.documentation);

        for (name, attribute) in &self.attributes.0 {
            descriptor.add_attribute(name.clone(), attribute.clone());
        }

        for (_, param) in &self.parameters {
            if let Some(parameter) =
                result.merge_degrade_failure(param.to_parameter(collection, &identifier))
            {
                descriptor.add_parameter(parameter);
            }
        }

        result.and(SharingResult::new_success(descriptor.commit()))
    }

    pub fn make_design(&self, collection: &Arc<Collection>) -> SharingResult<()> {
        let identifier = if let Ok(identifier) = (&self.identifier).try_into() {
            identifier
        } else {
            return SharingResult::new_failure(SharingError::invalid_identifier(
                6,
                self.identifier.clone(),
            ));
        };

        let design =
            if let ModelImplementationKind::Designed(Some(design)) = &self.implementation_kind {
                design
            } else {
                return SharingResult::new_failure(SharingError::no_model_design_available(
                    7, identifier,
                ));
            };

        let descriptor = if let Some(CommonEntry::Model(model)) =
            collection.get(&(&identifier).into())
        {
            if let Ok(model) = model.clone().downcast_arc::<DesignedModel>() {
                model
            } else {
                return SharingResult::new_failure(
                    LogicError::unexisting_model(235, identifier.clone(), identifier.into(), None)
                        .into(),
                );
            }
        } else {
            return SharingResult::new_failure(
                LogicError::unexisting_model(234, identifier.clone(), identifier.into(), None)
                    .into(),
            );
        };

        let designer = match descriptor.designer(Arc::clone(collection), None) {
            melodium_common::descriptor::Status::Success { success, errors: _ } => success,
            melodium_common::descriptor::Status::Failure { failure, errors: _ } => {
                return SharingResult::new_failure(failure.into())
            }
        };

        design
            .make_design(collection, &descriptor)
            .and_then(|design| {
                SharingResult::from(designer.write().unwrap().import_design(
                    &design,
                    &HashMap::new(),
                    None,
                ))
            })
            .and_then(|()| SharingResult::from(descriptor.commit_design()))
    }
}

impl From<&Arc<dyn CommonModel>> for Model {
    fn from(value: &Arc<dyn CommonModel>) -> Self {
        let mut hierarchy = Vec::new();
        let mut base = value.base_model();
        while let Some(parent) = base {
            hierarchy.push(parent.identifier().into());
            base = parent.base_model();
        }
        Self {
            identifier: Identifier::from(value.identifier()),
            documentation: value.documentation().to_string(),
            parameters: value
                .parameters()
                .iter()
                .map(|(name, param)| (name.clone(), Parameter::from(param)))
                .collect(),
            implementation_kind: match value.build_mode() {
                melodium_common::descriptor::ModelBuildMode::Compiled(_) => {
                    ModelImplementationKind::Compiled
                }
                melodium_common::descriptor::ModelBuildMode::Designed() => {
                    ModelImplementationKind::Designed(
                        value
                            .clone()
                            .downcast_arc::<DesignedModel>()
                            .unwrap()
                            .design()
                            .success()
                            .map(|design| design.as_ref().into()),
                    )
                }
            },
            hierarchy,
            sources: value
                .sources()
                .iter()
                .map(|(name, contexts)| {
                    (
                        name.clone(),
                        contexts
                            .iter()
                            .map(|context| Identifier::from(context.identifier()))
                            .collect(),
                    )
                })
                .collect(),
            attributes: value.attributes().into(),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[cfg_attr(feature = "webassembly", derive(tsify::Tsify))]
#[cfg_attr(feature = "webassembly", tsify(into_wasm_abi, from_wasm_abi))]
pub enum ModelImplementationKind {
    Compiled,
    Designed(Option<ModelDesign>),
}

impl ModelImplementationKind {
    pub fn is_compiled(&self) -> bool {
        match self {
            ModelImplementationKind::Compiled => true,
            _ => false,
        }
    }
}
