use crate::{Context, Data, Function, Identifier, Model, Treatment};
use itertools::Itertools;
use melodium_common::descriptor::{
    Collection as CommonCollection, Entry, Identified, Identifier as CommonIdentifier,
};
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;

#[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 Element {
    Context(Context),
    Data(Data),
    Function(Function),
    Model(Model),
    Treatment(Treatment),
}

impl Element {
    pub fn identifier(&self) -> &Identifier {
        match self {
            Element::Context(c) => &c.identifier,
            Element::Data(d) => &d.identifier,
            Element::Function(f) => &f.identifier,
            Element::Model(m) => &m.identifier,
            Element::Treatment(t) => &t.identifier,
        }
    }

    pub fn is_compiled(&self) -> bool {
        match self {
            Element::Context(_) => true,
            Element::Data(_) => true,
            Element::Function(_) => true,
            Element::Model(m) => m.implementation_kind.is_compiled(),
            Element::Treatment(t) => t.implementation_kind.is_compiled(),
        }
    }
}

impl From<&Entry> for Element {
    fn from(value: &Entry) -> Self {
        match value {
            Entry::Context(c) => Element::Context(c.as_ref().into()),
            Entry::Data(d) => Element::Data(d.as_ref().into()),
            Entry::Function(f) => Element::Function(f.as_ref().into()),
            Entry::Model(m) => Element::Model(m.into()),
            Entry::Treatment(t) => Element::Treatment(t.into()),
        }
    }
}

#[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 Collection {
    elements: Vec<Element>,
}

impl Collection {
    pub fn elements(&self) -> &Vec<Element> {
        &self.elements
    }

    pub fn to_elements(self) -> Vec<Element> {
        self.elements
    }

    pub fn from_entrypoint(collection: &CommonCollection, entrypoint: &CommonIdentifier) -> Self {
        let mut identifiers: VecDeque<CommonIdentifier> = VecDeque::new();

        if let Some(element) = collection.get(&entrypoint.into()) {
            let mut prepending_identifiers = element.uses();
            while !prepending_identifiers.is_empty() {
                prepending_identifiers
                    .iter()
                    .rev()
                    .for_each(|id| identifiers.push_front(id.clone()));

                let mut new_identifiers = Vec::new();
                for id in &prepending_identifiers {
                    if let Some(element) = collection.get(&id.into()) {
                        new_identifiers.extend(element.uses());
                    }
                }

                prepending_identifiers = new_identifiers;
            }
        }

        identifiers.push_back(entrypoint.clone());

        let elements = identifiers
            .iter()
            .unique()
            .filter_map(|identifier| collection.get(&identifier.into()).map(|entry| entry.into()))
            .collect();

        Self { elements }
    }
}

impl From<&CommonCollection> for Collection {
    fn from(collection: &CommonCollection) -> Self {
        Self {
            elements: collection
                .identifiers()
                .iter()
                .filter_map(|identifier| {
                    collection
                        .get(&(identifier.into()))
                        .map(|entry| entry.into())
                })
                .collect(),
        }
    }
}
