use std::fmt;

use crate::source::SourceInfo;
use crate::{SourceId, scope::ScopeId};
use itertools::Itertools;
use smol_str::SmolStr;

slotmap::new_key_type! { pub struct SymbolId; }

/// Represents metadata about a function parameter
#[derive(Debug, Clone, PartialEq)]
pub struct ParamInfo {
    pub name: SmolStr,
    pub has_default: bool,
    pub is_variadic: bool,
}

impl From<&str> for ParamInfo {
    fn from(name: &str) -> Self {
        ParamInfo {
            name: SmolStr::from(name),
            has_default: false,
            is_variadic: false,
        }
    }
}

impl std::fmt::Display for ParamInfo {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.is_variadic {
            write!(f, "*{}", self.name)
        } else {
            write!(f, "{}", self.name)
        }
    }
}

type Params = Vec<ParamInfo>;
pub type Doc = (mq_lang::Range, String);

#[derive(Debug, Clone, PartialEq)]
pub struct Symbol {
    pub doc: Vec<Doc>,
    pub kind: SymbolKind,
    pub value: Option<SmolStr>,
    pub scope: ScopeId,
    pub source: SourceInfo,
    pub parent: Option<SymbolId>,
    /// Monotonically increasing counter set at insertion time.
    /// Parent symbols always receive a lower number than their children because
    /// `add_expr` inserts parents before recursing into child nodes.
    pub(crate) insertion_order: u32,
}

#[derive(Debug, Clone, PartialEq)]
pub enum SymbolKind {
    Argument,
    Array,
    Assign,
    BinaryOp,
    Block,
    Boolean,
    Bytes,
    Call,
    CallDynamic,
    /// Represents a destructuring binding (`let [a, b] = expr`).
    /// Owns PatternVariable children and the initializer expression,
    /// mirroring the structure of `Variable` for simple let bindings.
    DestructuringBinding,
    Dict,
    Elif,
    Else,
    Foreach,
    Function(Params),
    Ident,
    If,
    Include(SourceId),
    /// A template string literal (`s"...${expr}..."`).
    InterpolatedString,
    Keyword,
    Loop,
    Macro(Params),
    Match,
    MatchArm {
        has_guard: bool,
    },
    None,
    Number,
    Parameter,
    /// `is_dict` is `true` for dict patterns (`{a, b}` or `{x: y}`),
    /// `false` for array patterns (`[a, b]`) or match arm patterns.
    Pattern {
        is_dict: bool,
    },
    /// A variable introduced by a pattern binding.
    ///
    /// `is_rest` is `true` for the rest element in an array pattern (`..rest`),
    /// which has type `Array(elem_ty)` rather than `elem_ty`.
    PatternVariable {
        is_rest: bool,
    },
    Ref,
    Selector(mq_lang::Selector),
    String,
    Symbol,
    UnaryOp,
    Variable,
    While,
    Try,
    Catch,
    Import(SourceId),
    Module(SourceId),
    QualifiedAccess,
}

impl Symbol {
    #[inline(always)]
    pub fn is_function(&self) -> bool {
        matches!(self.kind, SymbolKind::Function(_))
    }

    #[inline(always)]
    pub fn is_macro(&self) -> bool {
        matches!(self.kind, SymbolKind::Macro(_))
    }

    #[inline(always)]
    pub fn is_variable(&self) -> bool {
        matches!(self.kind, SymbolKind::Variable | SymbolKind::DestructuringBinding)
    }

    #[inline(always)]
    pub fn is_module(&self) -> bool {
        matches!(self.kind, SymbolKind::Module(_))
    }

    #[inline(always)]
    pub fn is_import(&self) -> bool {
        matches!(self.kind, SymbolKind::Import(_))
    }

    #[inline(always)]
    pub fn is_parameter(&self) -> bool {
        matches!(self.kind, SymbolKind::Parameter)
    }

    #[inline(always)]
    pub fn is_argument(&self) -> bool {
        matches!(self.kind, SymbolKind::Argument)
    }

    #[inline(always)]
    pub fn is_pattern_variable(&self) -> bool {
        matches!(self.kind, SymbolKind::PatternVariable { .. })
    }

    #[inline(always)]
    pub fn is_ident(&self) -> bool {
        matches!(self.kind, SymbolKind::Ident)
    }

    #[inline(always)]
    pub fn is_internal_function(&self) -> bool {
        if matches!(self.kind, SymbolKind::Function(_)) {
            self.value.as_ref().is_some_and(|value| value.as_str().starts_with("_"))
        } else {
            false
        }
    }

    /// Check if documentation contains deprecated marker
    #[inline(always)]
    pub fn is_deprecated(&self) -> bool {
        self.doc
            .iter()
            .any(|(_, text)| text.contains("deprecated") || text.contains("Deprecated") || text.contains("DEPRECATED"))
    }
}

impl fmt::Display for Symbol {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let s = match &self.kind {
            SymbolKind::Function(args) => &format!("function({})", args.iter().map(|p| p.to_string()).join(", ")),
            _ => self.value.as_ref().map_or("", |value| value.as_str()),
        };
        write!(f, "{}", s)
    }
}
#[cfg(test)]
mod tests {
    use rstest::rstest;

    use super::*;

    fn create_test_symbol(kind: SymbolKind, value: Option<&str>) -> Symbol {
        Symbol {
            doc: Vec::new(),
            kind,
            value: value.map(SmolStr::from),
            scope: ScopeId::default(),
            source: SourceInfo::new(None, None),
            parent: None,
            insertion_order: 0,
        }
    }

    #[rstest]
    #[case(SymbolKind::Function(Vec::new()), true)]
    #[case(SymbolKind::Function(vec![ParamInfo { name: SmolStr::from("param"), has_default: false, is_variadic: false }]), true)]
    #[case(SymbolKind::Variable, false)]
    #[case(SymbolKind::Call, false)]
    fn test_is_function(#[case] kind: SymbolKind, #[case] expected: bool) {
        let symbol = create_test_symbol(kind, Some("test"));
        assert_eq!(symbol.is_function(), expected);
    }

    #[rstest]
    #[case(SymbolKind::Variable, true)]
    #[case(SymbolKind::Function(Vec::new()), false)]
    #[case(SymbolKind::Parameter, false)]
    fn test_is_variable(#[case] kind: SymbolKind, #[case] expected: bool) {
        let symbol = create_test_symbol(kind, Some("test"));
        assert_eq!(symbol.is_variable(), expected);
    }

    #[rstest]
    #[case(SymbolKind::Parameter, true)]
    #[case(SymbolKind::Variable, false)]
    #[case(SymbolKind::Function(Vec::new()), false)]
    fn test_is_parameter(#[case] kind: SymbolKind, #[case] expected: bool) {
        let symbol = create_test_symbol(kind, Some("test"));
        assert_eq!(symbol.is_parameter(), expected);
    }

    #[test]
    fn test_display_with_name() {
        let symbol = create_test_symbol(SymbolKind::Variable, Some("test_var"));
        assert_eq!(format!("{}", symbol), "test_var");
    }

    #[test]
    fn test_display_without_name() {
        let symbol = create_test_symbol(SymbolKind::Variable, None);
        assert_eq!(format!("{}", symbol), "");
    }
}
