use crate::stdlib::{
    cmp::Ordering,
    collections::BTreeMap,
    string::{String, ToString},
    vec,
    vec::Vec,
};
use crate::{Engine, AST};
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum FnType {
    Script,
    Native,
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum FnNamespace {
    Global,
    Internal,
}

impl From<crate::FnNamespace> for FnNamespace {
    fn from(value: crate::FnNamespace) -> Self {
        match value {
            crate::FnNamespace::Global => Self::Global,
            crate::FnNamespace::Internal => Self::Internal,
        }
    }
}

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
enum FnAccess {
    Public,
    Private,
}

impl From<crate::FnAccess> for FnAccess {
    fn from(value: crate::FnAccess) -> Self {
        match value {
            crate::FnAccess::Public => Self::Public,
            crate::FnAccess::Private => Self::Private,
        }
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FnParam {
    pub name: String,
    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
    pub typ: Option<String>,
}

impl PartialOrd for FnParam {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(match self.name.partial_cmp(&other.name).unwrap() {
            Ordering::Less => Ordering::Less,
            Ordering::Greater => Ordering::Greater,
            Ordering::Equal => match (self.typ.is_none(), other.typ.is_none()) {
                (true, true) => Ordering::Equal,
                (true, false) => Ordering::Greater,
                (false, true) => Ordering::Less,
                (false, false) => self
                    .typ
                    .as_ref()
                    .unwrap()
                    .partial_cmp(other.typ.as_ref().unwrap())
                    .unwrap(),
            },
        })
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct FnMetadata {
    pub namespace: FnNamespace,
    pub access: FnAccess,
    pub name: String,
    #[serde(rename = "type")]
    pub typ: FnType,
    pub num_params: usize,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub params: Vec<FnParam>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub return_type: Option<String>,
    pub signature: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub doc_comments: Vec<String>,
}

impl PartialOrd for FnMetadata {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(match self.name.partial_cmp(&other.name).unwrap() {
            Ordering::Less => Ordering::Less,
            Ordering::Greater => Ordering::Greater,
            Ordering::Equal => match self.num_params.partial_cmp(&other.num_params).unwrap() {
                Ordering::Less => Ordering::Less,
                Ordering::Greater => Ordering::Greater,
                Ordering::Equal => self.params.partial_cmp(&other.params).unwrap(),
            },
        })
    }
}

impl Ord for FnMetadata {
    fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }
}

impl From<&crate::module::FuncInfo> for FnMetadata {
    fn from(info: &crate::module::FuncInfo) -> Self {
        Self {
            namespace: info.namespace.into(),
            access: info.access.into(),
            name: info.name.to_string(),
            typ: if info.func.is_script() {
                FnType::Script
            } else {
                FnType::Native
            },
            num_params: info.params,
            params: if let Some(ref names) = info.param_names {
                names
                    .iter()
                    .take(info.params)
                    .map(|s| {
                        let mut seg = s.splitn(2, ':');
                        let name = seg
                            .next()
                            .map(|s| s.trim().to_string())
                            .unwrap_or("_".to_string());
                        let typ = seg.next().map(|s| s.trim().to_string());
                        FnParam { name, typ }
                    })
                    .collect()
            } else {
                vec![]
            },
            return_type: if let Some(ref names) = info.param_names {
                names
                    .last()
                    .map(|s| s.to_string())
                    .or_else(|| Some("()".to_string()))
            } else {
                None
            },
            signature: info.gen_signature(),
            doc_comments: if info.func.is_script() {
                info.func.get_fn_def().comments.clone()
            } else {
                Default::default()
            },
        }
    }
}

impl From<crate::ScriptFnMetadata<'_>> for FnMetadata {
    fn from(info: crate::ScriptFnMetadata) -> Self {
        Self {
            namespace: FnNamespace::Global,
            access: info.access.into(),
            name: info.name.to_string(),
            typ: FnType::Script,
            num_params: info.params.len(),
            params: info
                .params
                .iter()
                .map(|s| FnParam {
                    name: s.to_string(),
                    typ: Some("Dynamic".to_string()),
                })
                .collect(),
            return_type: Some("Dynamic".to_string()),
            signature: info.to_string(),
            doc_comments: if info.comments.is_empty() {
                None
            } else {
                Some(info.comments.iter().map(|s| s.to_string()).collect())
            },
        }
    }
}

#[derive(Debug, Clone, Default, Serialize)]
#[serde(rename_all = "camelCase")]
struct ModuleMetadata {
    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
    pub modules: BTreeMap<String, Self>,
    #[serde(skip_serializing_if = "Vec::is_empty")]
    pub functions: Vec<FnMetadata>,
}

impl From<&crate::Module> for ModuleMetadata {
    fn from(module: &crate::Module) -> Self {
        let mut functions: Vec<_> = module.iter_fn().map(|f| f.into()).collect();
        functions.sort();

        Self {
            modules: module
                .iter_sub_modules()
                .map(|(name, m)| (name.to_string(), m.as_ref().into()))
                .collect(),
            functions,
        }
    }
}

#[cfg(feature = "serde")]
impl Engine {
    /// Generate a list of all functions (including those defined in an [`AST`][crate::AST])
    /// in JSON format.  Available only under the `metadata` feature.
    ///
    /// Functions from the following sources are included:
    /// 1) Functions defined in an [`AST`][crate::AST]
    /// 2) Functions registered into the global namespace
    /// 3) Functions in static modules
    /// 4) Functions in global modules (optional)
    pub fn gen_fn_metadata_with_ast_to_json(
        &self,
        ast: &AST,
        include_global: bool,
    ) -> serde_json::Result<String> {
        let mut global: ModuleMetadata = Default::default();

        if include_global {
            self.global_modules
                .iter()
                .flat_map(|m| m.iter_fn().map(|f| f.into()))
                .for_each(|info| global.functions.push(info));
        }

        self.global_sub_modules.iter().for_each(|(name, m)| {
            global.modules.insert(name.to_string(), m.as_ref().into());
        });

        self.global_namespace
            .iter_fn()
            .map(|f| f.into())
            .for_each(|info| global.functions.push(info));

        if let Some(ast) = ast {
            ast.iter_functions()
                .map(|f| f.into())
                .for_each(|info| global.functions.push(info));
        }

        global.functions.sort();

        serde_json::to_string_pretty(&global)
    }

    /// Generate a list of all functions in JSON format.
    /// Available only under the `metadata` feature.
    ///
    /// Functions from the following sources are included:
    /// 1) Functions registered into the global namespace
    /// 2) Functions in static modules
    /// 3) Functions in global modules (optional)
    pub fn gen_fn_metadata_to_json(&self, include_global: bool) -> serde_json::Result<String> {
        self.gen_fn_metadata_with_ast_to_json(&Default::default(), include_global)
    }
}
