use core::fmt;
use std::path::Path;
use std::sync::LazyLock;

use ecow::{EcoString, eco_format};
use regex::RegexSet;
use strum::{EnumIter, IntoEnumIterator};
use typst::foundations::{CastInfo, Regex};
use typst::layout::Ratio;
use typst::syntax::FileId;
use typst::{
    foundations::{AutoValue, Content, Func, NoneValue, ParamInfo, Type, Value},
    layout::Length,
};
use typst_shim::syntax::RootedPathExt;

use crate::syntax::Decl;
use crate::ty::*;

/// A kind of path recognized by the analyzer.
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, EnumIter)]
pub enum PathKind {
    /// A source path: `import "foo.typ"`.
    Source {
        /// Whether to allow package imports.
        allow_package: bool,
    },
    /// A WASM path: `plugin("foo.wasm")`.
    Wasm,
    /// A CSV path: `csv("foo.csv")`.
    Csv,
    /// An image path: `image("foo.png")`.
    Image,
    /// A JSON path: `json("foo.json")`.
    Json,
    /// A YAML path: `yaml("foo.yml")`.
    Yaml,
    /// A XML path: `xml("foo.xml")`.
    Xml,
    /// A TOML path: `toml("foo.toml")`.
    Toml,
    /// A CSL path: `bibliography(csl: "foo.csl")`.
    Csl,
    /// A bibliography path: `bibliography("foo.bib")`.
    Bibliography,
    /// A raw theme path: `raw(theme: "foo.tmTheme")`.
    RawTheme,
    /// A raw syntaxes path: `raw(syntaxes: "foo.tmLanguage")`.
    RawSyntax,
    /// All of the above kinds.
    Special,
    /// Merely known as a path.
    None,
}

impl PathKind {
    /// Matches the extension of the path by kind.
    pub fn ext_matcher(&self) -> &'static RegexSet {
        type RegSet = LazyLock<RegexSet>;

        fn make_regex(patterns: &[&str]) -> RegexSet {
            let patterns = patterns.iter().map(|pattern| format!("(?i)^{pattern}$"));
            RegexSet::new(patterns).unwrap()
        }

        static SOURCE_REGSET: RegSet = RegSet::new(|| make_regex(&["typ", "typc"]));
        static WASM_REGSET: RegSet = RegSet::new(|| make_regex(&["wasm"]));
        static IMAGE_REGSET: RegSet = RegSet::new(|| {
            make_regex(&[
                "ico", "bmp", "png", "webp", "jpg", "jpeg", "jfif", "tiff", "gif", "svg", "svgz",
                "pdf",
            ])
        });
        static JSON_REGSET: RegSet = RegSet::new(|| make_regex(&["json", "jsonc", "json5"]));
        static YAML_REGSET: RegSet = RegSet::new(|| make_regex(&["yaml", "yml"]));
        static XML_REGSET: RegSet = RegSet::new(|| make_regex(&["xml"]));
        static TOML_REGSET: RegSet = RegSet::new(|| make_regex(&["toml"]));
        static CSV_REGSET: RegSet = RegSet::new(|| make_regex(&["csv"]));
        static BIB_REGSET: RegSet = RegSet::new(|| make_regex(&["yaml", "yml", "bib"]));
        static CSL_REGSET: RegSet = RegSet::new(|| make_regex(&["csl"]));
        static RAW_THEME_REGSET: RegSet = RegSet::new(|| make_regex(&["tmTheme", "xml"]));
        static RAW_SYNTAX_REGSET: RegSet =
            RegSet::new(|| make_regex(&["tmLanguage", "sublime-syntax"]));

        static ALL_REGSET: RegSet = RegSet::new(|| RegexSet::new([r".*"]).unwrap());
        static ALL_SPECIAL_REGSET: RegSet = RegSet::new(|| {
            RegexSet::new({
                let patterns = SOURCE_REGSET.patterns();
                let patterns = patterns.iter().chain(WASM_REGSET.patterns());
                let patterns = patterns.chain(IMAGE_REGSET.patterns());
                let patterns = patterns.chain(JSON_REGSET.patterns());
                let patterns = patterns.chain(YAML_REGSET.patterns());
                let patterns = patterns.chain(XML_REGSET.patterns());
                let patterns = patterns.chain(TOML_REGSET.patterns());
                let patterns = patterns.chain(CSV_REGSET.patterns());
                let patterns = patterns.chain(BIB_REGSET.patterns());
                let patterns = patterns.chain(CSL_REGSET.patterns());
                let patterns = patterns.chain(RAW_THEME_REGSET.patterns());
                patterns.chain(RAW_SYNTAX_REGSET.patterns())
            })
            .unwrap()
        });

        match self {
            PathKind::Source { .. } => &SOURCE_REGSET,
            PathKind::Wasm => &WASM_REGSET,
            PathKind::Csv => &CSV_REGSET,
            PathKind::Image => &IMAGE_REGSET,
            PathKind::Json => &JSON_REGSET,
            PathKind::Yaml => &YAML_REGSET,
            PathKind::Xml => &XML_REGSET,
            PathKind::Toml => &TOML_REGSET,
            PathKind::Csl => &CSL_REGSET,
            PathKind::Bibliography => &BIB_REGSET,
            PathKind::RawTheme => &RAW_THEME_REGSET,
            PathKind::RawSyntax => &RAW_SYNTAX_REGSET,
            PathKind::Special => &ALL_SPECIAL_REGSET,
            PathKind::None => &ALL_REGSET,
        }
    }

    /// Checks if the path matches the kind.
    pub fn is_match(&self, path: &Path) -> bool {
        let ext = path.extension().and_then(|ext| ext.to_str());
        ext.is_some_and(|ext| self.ext_matcher().is_match(ext))
    }

    /// Gets the kind of the path by extension.
    pub fn from_ext(path: &str) -> Option<Self> {
        PathKind::iter().find(|preference| preference.is_match(std::path::Path::new(path)))
    }
}

impl Ty {
    /// Converts a cast info to a type.
    pub fn from_cast_info(ty: &CastInfo) -> Ty {
        match &ty {
            CastInfo::Any => Ty::Any,
            CastInfo::Value(val, doc) => Ty::Value(InsTy::new_doc(val.clone(), *doc)),
            CastInfo::Type(ty) => Ty::Builtin(BuiltinTy::Type(*ty)),
            CastInfo::Union(types) => {
                Ty::iter_union(UnionIter(vec![types.as_slice().iter()]).map(Self::from_cast_info))
            }
        }
    }

    /// Converts a parameter site to a type.
    pub fn from_param_site(func: &Func, param: &ParamInfo) -> Ty {
        use typst::foundations::FuncInner;
        match func.inner() {
            FuncInner::Element(..) | FuncInner::Native(..) | FuncInner::Plugin(..) => {
                if let Some(ty) = param_mapping(func, param) {
                    return ty;
                }
            }
            FuncInner::Closure(_) => {}
            FuncInner::With(w) => return Ty::from_param_site(&w.0, param),
        };

        param
            .to_native()
            .map(|native| Self::from_cast_info(&native.input))
            .unwrap_or(Ty::Any)
    }

    /// Converts a return site to a type.
    pub(crate) fn from_return_site(func: &Func, ty: &'_ CastInfo) -> Self {
        use typst::foundations::FuncInner;
        match func.inner() {
            FuncInner::Element(elem) => return Ty::Builtin(BuiltinTy::Content(Some(*elem))),
            FuncInner::Closure(_) | FuncInner::Plugin(_) => {}
            FuncInner::With(w) => return Ty::from_return_site(&w.0, ty),
            FuncInner::Native(_) => {}
        };

        Self::from_cast_info(ty)
    }
}

/// An iterator over a union of cast infos.
struct UnionIter<'a>(Vec<std::slice::Iter<'a, CastInfo>>);

impl<'a> Iterator for UnionIter<'a> {
    type Item = &'a CastInfo;

    fn next(&mut self) -> Option<Self::Item> {
        loop {
            let iter = self.0.last_mut()?;
            if let Some(ty) = iter.next() {
                match ty {
                    CastInfo::Union(types) => {
                        self.0.push(types.as_slice().iter());
                    }
                    _ => return Some(ty),
                }
            } else {
                self.0.pop();
            }
        }
    }
}

// todo: we can write some proto files for builtin sigs
/// A builtin signature.
#[derive(Debug, Clone, Copy)]
pub enum BuiltinSig<'a> {
    /// Maps a function over a tuple: `(a, b, c).map`
    TupleMap(&'a Ty),
    /// Gets element of a tuple: `(a, b, c).at`
    TupleAt(&'a Ty),
}

/// A package identifier.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct PackageId {
    /// The namespace of the package.
    pub namespace: StrRef,
    /// The name of the package.
    pub name: StrRef,
}

impl fmt::Debug for PackageId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "@{}/{}", self.namespace, self.name)
    }
}

impl TryFrom<FileId> for PackageId {
    type Error = ();

    fn try_from(value: FileId) -> Result<Self, Self::Error> {
        let spec = value.package_compat().ok_or(())?;
        Ok(PackageId {
            namespace: spec.namespace.as_str().into(),
            name: spec.name.as_str().into(),
        })
    }
}

/// A builtin type.
#[derive(Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum BuiltinTy {
    /// A clause type.
    Clause,
    /// An undefined type.
    Undef,
    /// A space type: `[ ]`
    Space,
    /// A none type: `none`
    None,
    /// A break type: `break`
    Break,
    /// A continue type: `continue`
    Continue,
    /// An infer type: `any`
    Infer,
    /// A flow none type: `none`
    FlowNone,
    /// An auto type: `auto`
    Auto,

    /// Arguments: `arguments(a, b: c, ..d)`
    Args,
    /// A color type: `rgb(r, g, b)`
    Color,
    /// A text size type: `text.size`
    TextSize,
    /// A text font type: `text.font`
    TextFont,
    /// A text feature type: `text.feature`
    TextFeature,
    /// A text language type: `text.lang`
    TextLang,
    /// A text region type: `text.region`
    TextRegion,
    /// A dir type: `left`
    Dir,
    /// A label type: `<label>`
    Label,
    /// A cite label type: `#cite(<label>)`
    CiteLabel,
    /// A ref label type: `@label`
    RefLabel,
    /// A length type: `10pt`
    Length,
    /// A float type: `1.0`
    Float,
    /// A stroke type: `stroke(paint: red)`
    Stroke,
    /// A margin type: `page(margin: 10pt)`
    Margin,
    /// An inset type: `box(inset: 10pt)`
    Inset,
    /// An outset type: `box(outset: 10pt)`
    Outset,
    /// A radius type: `box(radius: 10pt)`
    Radius,

    /// A tag type: `tag`
    Tag(Box<(StrRef, Option<Interned<PackageId>>)>),

    /// The type of a value: `int` of `10`
    Type(typst::foundations::Type),
    /// The type of a type: `type(int)`
    TypeType(typst::foundations::Type),
    /// The element type of a content value. For example, `#[text]` has
    /// element type `text`.
    ///
    /// If the element is not specified, the element type is `content`.
    Content(Option<typst::foundations::Element>),
    /// The type of an element: `text`
    Element(typst::foundations::Element),

    /// A module type: `module(foo)`
    Module(Interned<Decl>),
    /// A path type: `import "foo.typ"`
    Path(PathKind),
}

impl fmt::Debug for BuiltinTy {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BuiltinTy::Clause => f.write_str("Clause"),
            BuiltinTy::Undef => f.write_str("Undef"),
            BuiltinTy::Content(ty) => {
                if let Some(ty) = ty {
                    write!(f, "Content({})", ty.name())
                } else {
                    f.write_str("Content")
                }
            }
            BuiltinTy::Space => f.write_str("Space"),
            BuiltinTy::None => f.write_str("None"),
            BuiltinTy::Break => f.write_str("Break"),
            BuiltinTy::Continue => f.write_str("Continue"),
            BuiltinTy::Infer => f.write_str("Infer"),
            BuiltinTy::FlowNone => f.write_str("FlowNone"),
            BuiltinTy::Auto => f.write_str("Auto"),

            BuiltinTy::Args => write!(f, "Args"),
            BuiltinTy::Color => write!(f, "Color"),
            BuiltinTy::TextSize => write!(f, "TextSize"),
            BuiltinTy::TextFont => write!(f, "TextFont"),
            BuiltinTy::TextFeature => write!(f, "TextFeature"),
            BuiltinTy::TextLang => write!(f, "TextLang"),
            BuiltinTy::TextRegion => write!(f, "TextRegion"),
            BuiltinTy::Dir => write!(f, "Dir"),
            BuiltinTy::Length => write!(f, "Length"),
            BuiltinTy::Label => write!(f, "Label"),
            BuiltinTy::CiteLabel => write!(f, "CiteLabel"),
            BuiltinTy::RefLabel => write!(f, "RefLabel"),
            BuiltinTy::Float => write!(f, "Float"),
            BuiltinTy::Stroke => write!(f, "Stroke"),
            BuiltinTy::Margin => write!(f, "Margin"),
            BuiltinTy::Inset => write!(f, "Inset"),
            BuiltinTy::Outset => write!(f, "Outset"),
            BuiltinTy::Radius => write!(f, "Radius"),
            BuiltinTy::TypeType(ty) => write!(f, "TypeType({})", ty.short_name()),
            BuiltinTy::Type(ty) => write!(f, "Type({})", ty.short_name()),
            BuiltinTy::Element(elem) => elem.fmt(f),
            BuiltinTy::Tag(tag) => {
                let (name, id) = tag.as_ref();
                if let Some(id) = id {
                    write!(f, "Tag({name:?}) of {id:?}")
                } else {
                    write!(f, "Tag({name:?})")
                }
            }
            BuiltinTy::Module(decl) => write!(f, "{decl:?}"),
            BuiltinTy::Path(preference) => write!(f, "Path({preference:?})"),
        }
    }
}

impl BuiltinTy {
    /// Converts a value to a type.
    pub fn from_value(builtin: &Value) -> Ty {
        if let Value::Bool(v) = builtin {
            return Ty::Boolean(Some(*v));
        }

        Self::from_builtin(builtin.ty())
    }

    /// Converts a builtin type to a type.
    pub fn from_builtin(builtin: Type) -> Ty {
        if builtin == Type::of::<AutoValue>() {
            return Ty::Builtin(BuiltinTy::Auto);
        }
        if builtin == Type::of::<NoneValue>() {
            return Ty::Builtin(BuiltinTy::None);
        }
        if builtin == Type::of::<typst::visualize::Color>() {
            return Color.literally();
        }
        if builtin == Type::of::<bool>() {
            return Ty::Builtin(BuiltinTy::None);
        }
        if builtin == Type::of::<f64>() {
            return Float.literally();
        }
        if builtin == Type::of::<Length>() {
            return Length.literally();
        }
        if builtin == Type::of::<Content>() {
            return Ty::Builtin(BuiltinTy::Content(Option::None));
        }

        BuiltinTy::Type(builtin).literally()
    }

    /// Describes the builtin type.
    pub(crate) fn describe(&self) -> EcoString {
        let res = match self {
            BuiltinTy::Clause => "any",
            BuiltinTy::Undef => "any",
            BuiltinTy::Content(ty) => {
                return if let Some(ty) = ty {
                    eco_format!("content({})", ty.name())
                } else {
                    "content".into()
                };
            }
            BuiltinTy::Space => "content",
            BuiltinTy::None => "none",
            BuiltinTy::Break => "break",
            BuiltinTy::Continue => "continue",
            BuiltinTy::Infer => "any",
            BuiltinTy::FlowNone => "none",
            BuiltinTy::Auto => "auto",

            BuiltinTy::Args => "arguments",
            BuiltinTy::Color => "color",
            BuiltinTy::TextSize => "text.size",
            BuiltinTy::TextFont => "text.font",
            BuiltinTy::TextFeature => "text.feature",
            BuiltinTy::TextLang => "text.lang",
            BuiltinTy::TextRegion => "text.region",
            BuiltinTy::Dir => "dir",
            BuiltinTy::Length => "length",
            BuiltinTy::Float => "float",
            BuiltinTy::Label => "label",
            BuiltinTy::CiteLabel => "cite-label",
            BuiltinTy::RefLabel => "ref-label",
            BuiltinTy::Stroke => "stroke",
            BuiltinTy::Margin => "margin",
            BuiltinTy::Inset => "inset",
            BuiltinTy::Outset => "outset",
            BuiltinTy::Radius => "radius",
            BuiltinTy::TypeType(..) => "type",
            BuiltinTy::Type(ty) => ty.short_name(),
            BuiltinTy::Element(ty) => ty.name(),
            BuiltinTy::Tag(tag) => {
                let (name, id) = tag.as_ref();
                return if let Some(id) = id {
                    eco_format!("tag {name} of {id:?}")
                } else {
                    eco_format!("tag {name}")
                };
            }
            BuiltinTy::Module(m) => return eco_format!("module({})", m.name()),
            BuiltinTy::Path(s) => match s {
                PathKind::None => "[any]",
                PathKind::Special => "[any]",
                PathKind::Source { .. } => "[source]",
                PathKind::Wasm => "[wasm]",
                PathKind::Csv => "[csv]",
                PathKind::Image => "[image]",
                PathKind::Json => "[json]",
                PathKind::Yaml => "[yaml]",
                PathKind::Xml => "[xml]",
                PathKind::Toml => "[toml]",
                PathKind::Csl => "[csl]",
                PathKind::Bibliography => "[bib]",
                PathKind::RawTheme => "[theme]",
                PathKind::RawSyntax => "[syntax]",
            },
        };

        res.into()
    }
}

use BuiltinTy::*;

/// Converts a flow builtin to a type.
fn literally(s: impl FlowBuiltinLiterally) -> Ty {
    s.literally()
}

/// A trait for converting a flow builtin to a type.
trait FlowBuiltinLiterally {
    fn literally(self) -> Ty;
}

impl FlowBuiltinLiterally for &str {
    fn literally(self) -> Ty {
        Ty::Value(InsTy::new(Value::Str(self.into())))
    }
}

impl FlowBuiltinLiterally for BuiltinTy {
    fn literally(self) -> Ty {
        Ty::Builtin(self.clone())
    }
}

impl FlowBuiltinLiterally for Ty {
    fn literally(self) -> Ty {
        self
    }
}

/// A macro for converting a flow builtin to a type.
macro_rules! flow_builtin_union_inner {
    ($literal_kind:expr) => {
        literally($literal_kind)
    };
    ($($x:expr),+ $(,)?) => {
        Vec::from_iter([
            $(flow_builtin_union_inner!($x)),*
        ])
    };
}

/// A macro for converting a flow builtin to a type.
macro_rules! flow_union {
    // the first one is string
    ($($b:tt)*) => {
        Ty::iter_union(flow_builtin_union_inner!( $($b)* ).into_iter())
    };

}

/// A macro for converting a flow builtin to a type.
macro_rules! flow_record {
    ($($name:expr => $ty:expr),* $(,)?) => {
        RecordTy::new(vec![
            $(
                (
                    $name.into(),
                    $ty,
                ),
            )*
        ])
    };
}

/// Maps a function parameter to a type.
pub(super) fn param_mapping(func: &Func, param: &ParamInfo) -> Option<Ty> {
    // todo: remove path params which is compatible with 0.12.0
    let input_ty = || {
        param
            .to_native()
            .map(|native| Ty::from_cast_info(&native.input))
            .unwrap_or(Ty::Any)
    };

    match (func.name()?, param.name()?) {
        // todo: pdf.embed
        ("embed", "path") => Some(literally(Path(PathKind::None))),
        ("cbor", "path" | "source") => Some(literally(Path(PathKind::None))),
        ("plugin", "source") => Some(literally(Path(PathKind::Wasm))),
        ("csv", "path" | "source") => Some(literally(Path(PathKind::Csv))),
        ("image", "path" | "source") => Some(literally(Path(PathKind::Image))),
        ("read", "path" | "source") => Some(literally(Path(PathKind::None))),
        ("json", "path" | "source") => Some(literally(Path(PathKind::Json))),
        ("yaml", "path" | "source") => Some(literally(Path(PathKind::Yaml))),
        ("xml", "path" | "source") => Some(literally(Path(PathKind::Xml))),
        ("toml", "path" | "source") => Some(literally(Path(PathKind::Toml))),
        ("raw", "theme") => Some(literally(Path(PathKind::RawTheme))),
        ("raw", "syntaxes") => Some(literally(Path(PathKind::RawSyntax))),
        ("bibliography" | "cite", "style") => {
            Some(Ty::iter_union([literally(Path(PathKind::Csl)), input_ty()]))
        }
        ("cite", "key") => Some(Ty::iter_union([literally(CiteLabel)])),
        ("ref", "target") => Some(Ty::iter_union([literally(RefLabel)])),
        ("footnote", "body") => Some(Ty::iter_union([literally(RefLabel), input_ty()])),
        ("link", "dest") => {
            static LINK_DEST_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                flow_union!(
                    literally(RefLabel),
                    Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Str>())),
                    Ty::Builtin(BuiltinTy::Type(Type::of::<typst::introspection::Location>())),
                    Ty::Dict(RecordTy::new(vec![
                        ("x".into(), literally(Length)),
                        ("y".into(), literally(Length)),
                    ])),
                )
            });
            Some(LINK_DEST_TYPE.clone())
        }
        ("bibliography", "path" | "sources") => {
            static BIB_PATH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                let bib_path_ty = literally(Path(PathKind::Bibliography));
                Ty::iter_union([bib_path_ty.clone(), Ty::Array(bib_path_ty.into())])
            });
            Some(BIB_PATH_TYPE.clone())
        }
        ("text", "size") => Some(literally(TextSize)),
        ("text", "font") => {
            // todo: the dict can be completed, but we have bugs...
            static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                Ty::iter_union([literally(TextFont), Ty::Array(literally(TextFont).into())])
            });
            Some(FONT_TYPE.clone())
        }
        ("text", "feature") => {
            static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                Ty::iter_union([
                    // todo: the key can only be the text feature
                    Ty::Builtin(BuiltinTy::Type(Type::of::<foundations::Dict>())),
                    Ty::Array(literally(TextFeature).into()),
                ])
            });
            Some(FONT_TYPE.clone())
        }
        ("text", "costs") => {
            static FONT_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                Ty::Dict(flow_record!(
                    "hyphenation" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
                    "runt" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
                    "widow" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
                    "orphan" => literally(BuiltinTy::Type(Type::of::<Ratio>())),
                ))
            });
            Some(FONT_TYPE.clone())
        }
        ("text", "lang") => Some(literally(TextLang)),
        ("text", "region") => Some(literally(TextRegion)),
        ("text" | "stack", "dir") => Some(literally(Dir)),
        ("par", "first-line-indent") => {
            static FIRST_LINE_INDENT: LazyLock<Ty> = LazyLock::new(|| {
                Ty::iter_union([
                    literally(Length),
                    Ty::Dict(RecordTy::new(vec![
                        ("amount".into(), literally(Length)),
                        ("all".into(), Ty::Boolean(Option::None)),
                    ])),
                ])
            });
            Some(FIRST_LINE_INDENT.clone())
        }
        (
            // todo: polygon.regular
            "page" | "highlight" | "text" | "path" | "curve" | "rect" | "ellipse" | "circle"
            | "polygon" | "box" | "block" | "table" | "regular",
            "fill",
        ) => Some(literally(Color)),
        (
            // todo: table.cell
            "table" | "cell" | "block" | "box" | "circle" | "ellipse" | "rect" | "square",
            "inset",
        ) => Some(literally(Inset)),
        ("block" | "box" | "circle" | "ellipse" | "rect" | "square", "outset") => {
            Some(literally(Outset))
        }
        ("block" | "box" | "rect" | "square" | "highlight", "radius") => Some(literally(Radius)),
        ("grid" | "table", "columns" | "rows" | "gutter" | "column-gutter" | "row-gutter") => {
            static COLUMN_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                flow_union!(
                    Ty::Value(InsTy::new(Value::Auto)),
                    Ty::Value(InsTy::new(Value::Type(Type::of::<i64>()))),
                    literally(Length),
                    Ty::Array(literally(Length).into()),
                )
            });
            Some(COLUMN_TYPE.clone())
        }
        ("pattern" | "tiling", "size") => {
            static PATTERN_SIZE_TYPE: LazyLock<Ty> = LazyLock::new(|| {
                flow_union!(
                    Ty::Value(InsTy::new(Value::Auto)),
                    Ty::Array(Ty::Builtin(Length).into()),
                )
            });
            Some(PATTERN_SIZE_TYPE.clone())
        }
        ("stroke", "dash") => Some(FLOW_STROKE_DASH_TYPE.clone()),
        (
            //todo: table.cell, table.hline, table.vline, math.cancel, grid.cell, polygon.regular
            "cancel" | "highlight" | "overline" | "strike" | "underline" | "text" | "path"
            | "curve" | "rect" | "ellipse" | "circle" | "polygon" | "box" | "block" | "table"
            | "line" | "cell" | "hline" | "vline" | "regular",
            "stroke",
        ) => Some(Ty::Builtin(Stroke)),
        ("page", "margin") => Some(Ty::Builtin(Margin)),
        _ => Option::None,
    }
}

/// The record component of a stroke type.
static FLOW_STROKE_DASH_TYPE: LazyLock<Ty> = LazyLock::new(|| {
    flow_union!(
        "solid",
        "dotted",
        "densely-dotted",
        "loosely-dotted",
        "dashed",
        "densely-dashed",
        "loosely-dashed",
        "dash-dotted",
        "densely-dash-dotted",
        "loosely-dash-dotted",
        Ty::Array(flow_union!("dot", literally(Float)).into()),
        Ty::Dict(flow_record!(
            "array" => Ty::Array(flow_union!("dot", literally(Float)).into()),
            "phase" => literally(Length),
        ))
    )
});

/// The record component of a stroke type.
pub static FLOW_STROKE_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
    flow_record!(
        "paint" => literally(Color),
        "thickness" => literally(Length),
        "cap" => flow_union!("butt", "round", "square"),
        "join" => flow_union!("miter", "round", "bevel"),
        "dash" => FLOW_STROKE_DASH_TYPE.clone(),
        "miter-limit" => literally(Float),
    )
});

/// The record component of a margin type.
pub static FLOW_MARGIN_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
    flow_record!(
        "top" => literally(Length),
        "right" => literally(Length),
        "bottom" => literally(Length),
        "left" => literally(Length),
        "inside" => literally(Length),
        "outside" => literally(Length),
        "x" => literally(Length),
        "y" => literally(Length),
        "rest" => literally(Length),
    )
});

/// The record component of an inset type.
pub static FLOW_INSET_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
    flow_record!(
        "top" => literally(Length),
        "right" => literally(Length),
        "bottom" => literally(Length),
        "left" => literally(Length),
        "x" => literally(Length),
        "y" => literally(Length),
        "rest" => literally(Length),
    )
});

/// The record component of an outset type.
pub static FLOW_OUTSET_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
    flow_record!(
        "top" => literally(Length),
        "right" => literally(Length),
        "bottom" => literally(Length),
        "left" => literally(Length),
        "x" => literally(Length),
        "y" => literally(Length),
        "rest" => literally(Length),
    )
});

/// The record component of a radius type.
pub static FLOW_RADIUS_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
    flow_record!(
        "top" => literally(Length),
        "right" => literally(Length),
        "bottom" => literally(Length),
        "left" => literally(Length),
        "top-left" => literally(Length),
        "top-right" => literally(Length),
        "bottom-left" => literally(Length),
        "bottom-right" => literally(Length),
        "rest" => literally(Length),
    )
});

/// The record component of a text font type.
pub static FLOW_TEXT_FONT_DICT: LazyLock<Interned<RecordTy>> = LazyLock::new(|| {
    flow_record!(
        "name" => literally(TextFont),
        "covers" => flow_union!("latin-in-cjk", BuiltinTy::Type(Type::of::<Regex>())),
    )
});

// todo bad case: array.fold
// todo bad case: datetime
// todo bad case: selector
// todo: function signatures, for example: `locate(loc => ...)`

// todo: numbering/supplement
// todo: grid/table.fill/align/stroke/inset can be a function
// todo: math.cancel.angle can be a function
// todo: math.mat.augment
// todo: csv.row-type can be an array or a dictionary
// todo: text.stylistic-set is an array of integer
// todo: raw.lang can be completed
// todo: smartquote.quotes can be an array or a dictionary
// todo: mat.augment can be a dictionary
// todo: pdf.embed mime-type can be special

// ISO 639

#[cfg(test)]
mod tests {

    use crate::syntax::Decl;

    use super::{SigTy, Ty, TypeVar};

    #[test]
    fn test_image_extension() {
        let path = "test.png";
        let preference = super::PathKind::from_ext(path).unwrap();
        assert_eq!(preference, super::PathKind::Image);
    }

    #[test]
    fn test_image_extension_uppercase() {
        let path = "TEST.PNG";
        let preference = super::PathKind::from_ext(path).unwrap();
        assert_eq!(preference, super::PathKind::Image);
    }

    // todo: map function
    // Technical Note for implementing a map function:
    // `u`, `v` is in level 2
    // instantiate a `v` as the return type of the map function.
    #[test]
    fn test_map() {
        let u = Ty::Var(TypeVar::new("u".into(), Decl::lit("u").into()));
        let v = Ty::Var(TypeVar::new("v".into(), Decl::lit("v").into()));
        let mapper_fn =
            Ty::Func(SigTy::new([u].into_iter(), None, None, None, Some(v.clone())).into());
        let map_fn =
            Ty::Func(SigTy::new([mapper_fn].into_iter(), None, None, None, Some(v)).into());
        let _ = map_fn;
        // println!("{map_fn:?}");
    }
}
