//
// Copyright 2025 Formata, Inc. All rights reserved.
//
// Licensed 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 std::ops::Deref;
use arcstr::{literal, ArcStr};
use imbl::Vector;
use serde::{Deserialize, Serialize};
use crate::{model::{Graph, NodeRef, SId, PROTOTYPE_TYPE_ATTR, SELF_STR_KEYWORD, SUPER_STR_KEYWORD}, parser::types::parse_type_complete, runtime::{Units, Val}};


// Literal string types.
const NULL: ArcStr = literal!("null");
const VOID: ArcStr = literal!("void");
const UNKNOWN: ArcStr = literal!("unknown");
const MAP: ArcStr = literal!("map");
const SET: ArcStr = literal!("set");
const LIST: ArcStr = literal!("list");
const BOOL: ArcStr = literal!("bool");
const BLOB: ArcStr = literal!("blob");
const FUNC: ArcStr = literal!("fn");
pub(super) const DATA: ArcStr = literal!("data");
pub(super) const OBJ: ArcStr = literal!("obj");
const VER: ArcStr = literal!("ver");
const STR: ArcStr = literal!("str");
const PROMPT: ArcStr = literal!("prompt");
const INT: ArcStr = literal!("int");
const FLOAT: ArcStr = literal!("float");


#[derive(Debug, Clone, Deserialize, Serialize, Default, Hash)]
/// Type.
pub enum Type {
    #[default]
    Void,
    Null,

    // Type that does not match null (used with func args that have a !)
    NotNull(Box<Self>),

    Promise(Box<Self>),

    Bool,
    Num(NumT),
    Str,
    Ver,
    Prompt,

    Obj(SId), // Prototypes
    Fn,
    Data(ArcStr), // typetag lib linking, etc.

    Blob,

    List,
    Tup(Vector<Self>),
    Map,
    Set,

    Unknown,
    Union(Vector<Self>),
}
impl PartialEq for Type {
    fn eq(&self, other: &Self) -> bool {
        match other {
            Self::Null => return !self.exp_not_null(),
            Self::Unknown => return true,
            Self::Union(types) => {
                match self {
                    Self::Union(otypes) => {
                        for ty in types {
                            for oty in otypes {
                                if ty.eq(oty) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    },
                    sf => {
                        for ty in types {
                            if ty.eq(sf) {
                                return true;
                            }
                        }
                        return false;
                    }
                }
            },
            Self::Promise(ty) => {
                match self {
                    Self::Promise(oty) => return ty == oty,
                    _ => return **ty == *self,
                }
            },
            _ => {}
        }
        match self {
            Self::Union(types) => {
                match other {
                    Self::Union(otypes) => {
                        for ty in types {
                            for oty in otypes {
                                if ty.eq(oty) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    },
                    other => {
                        for ty in types {
                            if ty.eq(other) {
                                return true;
                            }
                        }
                        return false;
                    }
                }
            },
            Self::Void => {
                match other {
                    Self::Void => true,
                    _ => false,
                }
            },
            Self::Bool => {
                match other {
                    Self::Bool => true,
                    _ => false,
                }
            },
            Self::Num(t) => {
                match other {
                    Self::Num(ot) => t.eq(ot),
                    _ => false,
                }
            },
            Self::Prompt => {
                match other {
                    Self::Prompt => true,
                    _ =>  false,
                }
            },
            Self::Str => {
                match other {
                    Self::Str => true,
                    _ => false,
                }
            },
            Self::Ver => {
                match other {
                    Self::Ver => true,
                    _ => false,
                }
            },
            Self::Obj(t) => {
                match other {
                    Self::Obj(ot) => t.eq(ot),
                    _ => false,
                }
            },
            Self::Fn => {
                match other {
                    Self::Fn => true,
                    _ => false,
                }
            },
            Self::Data(t) => {
                match other {
                    Self::Data(ot) => t.eq(ot),
                    _ => false, // gen type handled in cast
                }
            },
            Self::List => {
                match other {
                    Self::List => true,
                    _ => false,
                }
            },
            Self::Map => {
                match other {
                    Self::Map => true,
                    _ => false,
                }
            },
            Self::Set => {
                match other {
                    Self::Set => true,
                    _ => false,
                }
            },
            Self::Tup(types) => {
                match other {
                    Self::Tup(otypes) => types.eq(otypes),
                    _ => false,
                }
            },
            Self::Blob => {
                match other {
                    Self::Blob => true,
                    _ => false,
                }
            },
            Self::Promise(ty) => {
                match other {
                    Self::Promise(oty) => ty == oty,
                    _ => **ty == *other,
                }
            },
            Self::Null => !other.exp_not_null(),
            Self::NotNull(t) => t.deref().eq(other),
            Self::Unknown => true,
        }
    }
}
impl Eq for Type {}
impl Type {
    #[inline]
    pub fn empty(&self) -> bool {
        match self {
            Self::Null |
            Self::Void => true,
            _ => false,
        }
    }

    #[inline]
    pub fn null(&self) -> bool {
        match self {
            Self::Null => true,
            _ => false,
        }
    }

    #[inline]
    pub fn exp_not_null(&self) -> bool {
        match self {
            Self::NotNull(_) => true,
            _ => false,
        }
    }

    /// If this is an object type as a name, find the prototype and swap it.
    /// Or if a union, make sure all objects in the union are NodeRefs.
    pub fn obj_to_proto(&mut self, graph: &Graph, mut context: Option<NodeRef>) {
        match &mut *self {
            Self::Union(types) => {
                for ty in types { ty.obj_to_proto(graph, context.clone()); }
            },
            Self::Promise(ctype) => {
                ctype.obj_to_proto(graph, context);
            },
            Self::NotNull(ctype) => {
                ctype.obj_to_proto(graph, context);
            },
            Self::Tup(types) => {
                for ty in types { ty.obj_to_proto(graph, context.clone()); }
            },
            Self::Obj(name_or_id) => {
                if !name_or_id.node_exists(graph) { // not a node ref, so must be a name
                    let mut path = name_or_id.as_ref().to_string();
                    if path.contains('.') {
                        // This is a pathname (not just a typename), so if it doesn't start with self or super, remove the current context
                        if !path.starts_with(SELF_STR_KEYWORD.as_str()) && !path.starts_with(SUPER_STR_KEYWORD.as_str()) {
                            context = None;
                        }

                        let mut split = path.split('.').collect::<Vec<_>>();
                        let typename = split.pop().unwrap();
                        if let Some(node) = graph.find_node_named(&split.join("."), context.clone()) {
                            context = Some(node);
                            path = typename.to_string();
                        }
                    }

                    if context.is_none() { context = graph.main_root(); }
                    if let Some(proto_id) = graph.find_type(&path, context) {
                        *name_or_id = proto_id;
                    }
                }
            },
            _ => {}
        }
    }

    pub fn type_of(&self) -> ArcStr {
        match self {
            Self::Union(types) => {
                let mut geo = String::default();
                for ty in types {
                    if geo.len() < 1 {
                        geo.push_str(&ty.type_of());
                    } else {
                        geo.push_str(&format!(" | {}", ty.type_of()));
                    }
                }
                geo.into()
            },
            Self::Unknown => UNKNOWN,
            Self::Map => MAP,
            Self::Set => SET,
            Self::List => LIST,
            Self::Bool => BOOL,
            Self::Blob => BLOB,
            Self::Fn => FUNC,
            Self::Data(tname) => {
                let dta = DATA;
                if tname == &dta {
                    return dta;
                }
                format!("Data<{}>", tname).into()
            },
            Self::Null => NULL,
            Self::NotNull(t) => t.type_of(),
            Self::Num(num) => num.type_of(),
            Self::Ver => VER,
            Self::Str => STR,
            Self::Prompt => PROMPT,
            Self::Tup(vals) => {
                let mut res = "(".to_string();
                for i in 0..vals.len() {
                    let v = &vals[i];
                    let type_of = v.type_of();
                    if i < vals.len() - 1 {
                        res.push_str(&format!("{}, ", type_of));
                    } else {
                        res.push_str(&type_of);
                    }
                }
                res.push_str(")");
                res.into()
            },
            Self::Void => VOID,
            Self::Obj(ctype) => ctype.as_ref().into(),
            Self::Promise(ty) => format!("Promise<{}>", ty.type_of()).into(),
        }
    }

    pub fn rt_type_of(&self, graph: &Graph) -> ArcStr {
        match self {
            Self::Union(types) => {
                let mut geo = String::default();
                for ty in types {
                    if geo.len() < 1 {
                        geo.push_str(&ty.rt_type_of(graph));
                    } else {
                        geo.push_str(&format!(" | {}", ty.rt_type_of(graph)));
                    }
                }
                geo.into()
            },
            Self::Unknown => UNKNOWN,
            Self::Map => MAP,
            Self::Set => SET,
            Self::List => LIST,
            Self::Bool => BOOL,
            Self::Blob => BLOB,
            Self::Fn => FUNC,
            Self::Data(tname) => {
                let dta = DATA;
                if tname == &dta {
                    return dta;
                }
                format!("Data<{}>", tname).into()
            },
            Self::Null => NULL,
            Self::NotNull(t) => t.rt_type_of(graph),
            Self::Num(num) => num.type_of(),
            Self::Ver => VER,
            Self::Str => STR,
            Self::Prompt => PROMPT,
            Self::Tup(vals) => {
                let mut res = "(".to_string();
                for i in 0..vals.len() {
                    let v = &vals[i];
                    let type_of = v.rt_type_of(graph);
                    if i < vals.len() - 1 {
                        res.push_str(&format!("{}, ", type_of));
                    } else {
                        res.push_str(&type_of);
                    }
                }
                res.push_str(")");
                res.into()
            },
            Self::Void => VOID,
            Self::Obj(ctype) => {
                if let Some(node) = ctype.node(graph) {
                    if let Some(type_attr) = node.attributes.get(PROTOTYPE_TYPE_ATTR.as_str()) {
                        match type_attr {
                            Val::Str(name) => {
                                return name.clone();
                            },
                            _ => {
                                return node.name.as_ref().into();
                            }
                        }
                    }
                }
                ctype.as_ref().into()
            },
            Self::Promise(ty) => format!("Promise<{}>", ty.rt_type_of(graph)).into(),
        }
    }

    pub fn md_type_of(&self, graph: &Graph) -> String {
        self.rt_type_of(graph).replace("<", "\\<")
    }

    /// Generic libname.
    pub fn gen_lib_name(&self) -> ArcStr {
        match self {
            Self::Unknown |
            Self::Null |
            Self::Union(_) |
            Self::Void => literal!("Empty"),
            Self::List => literal!("List"),
            Self::Map => literal!("Map"),
            Self::Set => literal!("Set"),
            Self::Blob => literal!("Blob"),
            Self::Bool => literal!("Bool"),
            Self::Fn => literal!("Fn"),
            Self::Num(_) => literal!("Num"),
            Self::Data(_) => literal!("Data"),
            Self::Str => literal!("Str"),
            Self::Prompt => literal!("Prompt"),
            Self::Obj(_) => literal!("Obj"),
            Self::Promise(_) => literal!("Promise"),
            Self::Ver => literal!("Ver"),
            Self::Tup(_) => literal!("Tup"),
            Self::NotNull(t) => t.gen_lib_name(),
        }
    }
}
impl<T: AsRef<str>> From<T> for Type {
    fn from(value: T) -> Self {
        parse_type_complete(value.as_ref()).expect(&format!("failed to parse stof type string '{}' into a valid Type", value.as_ref()))
    }
}
impl ToString for Type {
    fn to_string(&self) -> String {
        self.type_of().to_string()
    }
}


#[derive(Debug, Clone, Copy, Deserialize, Serialize, Hash)]
/// Number Type.
pub enum NumT {
    Int,
    Float,
    Units(Units),
}
impl PartialEq for NumT {
    fn eq(&self, other: &Self) -> bool {
        match self {
            Self::Int => {
                match other {
                    Self::Int => true,
                    _ => false,
                }
            },
            Self::Float => {
                match other {
                    Self::Float => true,
                    _ => false,
                }
            },
            Self::Units(units) => {
                match other {
                    Self::Float => true,
                    Self::Units(ounits) => {
                        units == ounits
                    },
                    _ => false,
                }
            },
        }
    }
}
impl Eq for NumT {}
impl NumT {
    pub fn type_of(&self) -> ArcStr {
        match self {
            Self::Float => FLOAT,
            Self::Int => INT,
            Self::Units(units) => units.to_string(),
        }
    }
}
