//! Type-specifier and declaration AST nodes.
//!
//! This module defines syntactic forms used to represent type names, inline
//! type declarations, variable declarators, and declaration modifiers.
use super::{TokenSpan, ExpressionRef, IdentifierToken, QualifiedIdentifierRef};

use crate::arena::{ArenaNode, ArenaString, ArenaVec};
use crate::lexer::{Keyword, Token, TokenPosition};

use core::convert::TryFrom;

/// Type syntax used in declarations, fields, and other type-annotated grammar
/// positions.
///
/// This enum covers both named types and inline type-definition forms supported
/// by the language.
#[derive(Debug, PartialEq)]
pub enum TypeSpecifier<'src, 'arena> {
    /// Named type reference such as `EDrawType` or `Pkg.Group.Type`.
    Named(QualifiedIdentifierRef<'arena>),
    /// Inline enum definition used directly in type position.
    ///
    /// Example:
    /// `enum EMyKind { A, B, C }`
    InlineEnum(EnumDefRef<'src, 'arena>),
    /// Inline struct definition used directly in type position.
    ///
    /// Example:
    /// `struct SMyData { var int X; }`
    InlineStruct(StructDefRef<'src, 'arena>),
    /// Generic array type: `array<...>`.
    ///
    /// The parser currently allows a sequence of variable-style modifiers to
    /// appear before the inner type and preserves them here.
    Array {
        /// Modifiers parsed before the inner type inside `array<...>`.
        element_modifiers: ArenaVec<'arena, VarModifier>,
        /// Element / inner type.
        element_type: TypeSpecifierRef<'src, 'arena>,
    },
    /// `class` or `class<SomeType>`.
    ///
    /// `None` represents a bare `class` with no type argument.
    Class(Option<QualifiedIdentifierRef<'arena>>),
}

/// Stable arena reference to a type-specifier node.
pub type TypeSpecifierRef<'src, 'arena> = ArenaNode<'arena, TypeSpecifier<'src, 'arena>>;

/// Enum definition used either inline in a type position or elsewhere in the
/// declaration grammar.
#[derive(Debug, PartialEq, Eq)]
pub struct EnumDefinition<'arena> {
    /// Declared enum name.
    pub name: IdentifierToken,
    /// Enum variants in source order.
    pub variants: ArenaVec<'arena, IdentifierToken>,
}

/// Stable arena reference to an enum definition.
pub type EnumDefRef<'src, 'arena> = ArenaNode<'arena, EnumDefinition<'arena>>;

/// Struct-level modifier kind.
///
/// These are modifiers that apply to the struct declaration itself rather than
/// to an individual field.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StructModifierKind {
    Native,
    Export,
    NoExport,
    Transient,
    Deprecated,
    Init,
    Long,
}

/// Struct declaration modifier together with its source token position.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct StructModifier {
    /// Modifier kind.
    pub kind: StructModifierKind,
    /// Position of the modifier token in the source stream.
    pub position: TokenPosition,
}

impl StructModifier {
    /// Span covering just this modifier token.
    #[must_use]
    pub const fn span(self) -> TokenSpan {
        TokenSpan::new(self.position)
    }
    /// Construct a struct modifier from kind and token position.
    #[must_use]
    pub const fn new(kind: StructModifierKind, token: TokenPosition) -> Self {
        Self {
            kind,
            position: token,
        }
    }
}

/// Struct field declaration.
///
/// A field stores the declared type together with one or more declarators
/// sharing that type, plus optional `var(...)` editor specifiers and ordinary
/// declaration modifiers.
#[derive(Debug, PartialEq)]
pub struct StructField<'src, 'arena> {
    /// Field type.
    pub type_specifier: TypeSpecifierRef<'src, 'arena>,
    /// One or more declarators declared with the same type.
    ///
    /// Examples:
    /// - `var int A;`
    /// - `var int A, B[4], C = 10;`
    pub declarators: DeclaratorsList<'src, 'arena>,
    /// Optional `var(...)` editor specifiers attached to the field declaration.
    ///
    /// Example:
    /// `var(Display, "Advanced/Hidden")`
    pub editor_specifiers: Option<ArenaVec<'arena, VarEditorSpecifierRef<'src, 'arena>>>,
    /// Declaration modifiers attached to the field.
    ///
    /// These are preserved in source order.
    pub declaration_modifiers: ArenaVec<'arena, VarModifier>,
}

/// Stable arena reference to a struct field declaration.
pub type StructFieldRef<'src, 'arena> = ArenaNode<'arena, StructField<'src, 'arena>>;

/// Struct definition used either inline in a type position or elsewhere in the
/// declaration grammar.
#[derive(Debug, PartialEq)]
pub struct StructDefinition<'src, 'arena> {
    /// Struct name, if present.
    ///
    /// Anonymous inline structs use `None`.
    pub name: Option<IdentifierToken>,
    /// Optional base struct after `extends`.
    pub base_type_name: Option<QualifiedIdentifierRef<'arena>>,
    /// Modifiers attached to the struct declaration itself.
    pub modifiers: ArenaVec<'arena, StructModifier>,
    /// Struct fields in source order.
    pub fields: ArenaVec<'arena, StructFieldRef<'src, 'arena>>,
}

/// Stable arena reference to a struct definition.
pub type StructDefRef<'src, 'arena> = ArenaNode<'arena, StructDefinition<'src, 'arena>>;

/// One declared variable name together with optional array size and initializer.
///
/// This node represents one declarator inside a declaration that may contain
/// several comma-separated declarators sharing the same type.
#[derive(Debug, PartialEq)]
pub struct VariableDeclarator<'src, 'arena> {
    /// Declared variable name.
    pub name: IdentifierToken,
    /// Optional initializer after `=`.
    pub initializer: Option<ExpressionRef<'src, 'arena>>,
    /// Optional array-size expression from `[expr]`.
    pub array_size: Option<ExpressionRef<'src, 'arena>>,
}

/// Stable arena reference to a variable declarator.
///
/// The node span is expected to cover the entire declarator, not only the
/// identifier token.
pub type VariableDeclaratorRef<'src, 'arena> = ArenaNode<'arena, VariableDeclarator<'src, 'arena>>;
/// Declarations in `var` or `local` variable list.
pub type DeclaratorsList<'src, 'arena> = ArenaVec<'arena, VariableDeclaratorRef<'src, 'arena>>;

/// One item inside `var(...)` editor specifiers.
#[derive(Debug, PartialEq, Eq)]
pub enum VarEditorSpecifier<'arena> {
    /// Identifier-like editor specifier such as `Display` or `Advanced`.
    Identifier(IdentifierToken),
    /// String editor specifier such as `"Category/Sub"`.
    String(ArenaString<'arena>),
}

/// Stable arena reference to an editor specifier.
pub type VarEditorSpecifierRef<'src, 'arena> = ArenaNode<'arena, VarEditorSpecifier<'arena>>;

/// Field / variable declaration modifier kind.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum VarModifierKind {
    Transient,
    Public,
    Protected,
    Private,
    Static,
    Const,
    Deprecated,
    NoExport,
    Export,
    Config,
    Localized,
    GlobalConfig,
    PerObjectConfig,
    Input,
    EdFindable,
    EditConst,
    EditConstArray,
    EditInline,
    EditInlineUse,
    EditInlineNew,
    EditInlineNotify,
    NotEditInlineNew,
    Automated,
    Native,
    Travel,
    Cache,
}

/// Variable-style declaration modifier together with its token position.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VarModifier {
    /// Modifier kind.
    pub kind: VarModifierKind,
    /// Position of the modifier token in the source stream.
    pub position: TokenPosition,
}

impl TryFrom<Keyword> for VarModifierKind {
    type Error = ();

    fn try_from(keyword: Keyword) -> Result<Self, Self::Error> {
        use VarModifierKind::{
            Automated, Cache, Config, Const, Deprecated, EdFindable, EditConst, EditConstArray,
            EditInline, EditInlineNew, EditInlineNotify, EditInlineUse, Export, GlobalConfig,
            Input, Localized, Native, NoExport, NotEditInlineNew, PerObjectConfig, Private,
            Protected, Public, Static, Transient, Travel,
        };

        let kind = match keyword {
            Keyword::Transient => Transient,
            Keyword::Public => Public,
            Keyword::Protected => Protected,
            Keyword::Private => Private,
            Keyword::Static => Static,
            Keyword::Const => Const,
            Keyword::Deprecated => Deprecated,
            Keyword::NoExport => NoExport,
            Keyword::Export => Export,
            Keyword::Config => Config,
            Keyword::Localized => Localized,
            Keyword::GlobalConfig => GlobalConfig,
            Keyword::PerObjectConfig => PerObjectConfig,
            Keyword::EdFindable => EdFindable,
            Keyword::EditConst => EditConst,
            Keyword::EditConstArray => EditConstArray,
            Keyword::EditInline => EditInline,
            Keyword::EditInlineUse => EditInlineUse,
            Keyword::EditInlineNew => EditInlineNew,
            Keyword::EditInlineNotify => EditInlineNotify,
            Keyword::NotEditInlineNew => NotEditInlineNew,
            Keyword::Automated => Automated,
            Keyword::Native => Native,
            Keyword::Input => Input,
            Keyword::Travel => Travel,
            Keyword::Cache => Cache,
            _ => return Err(()),
        };
        Ok(kind)
    }
}

impl TryFrom<(Token, TokenPosition)> for VarModifier {
    type Error = ();

    fn try_from((token, position): (Token, TokenPosition)) -> Result<Self, Self::Error> {
        let Token::Keyword(keyword) = token else {
            return Err(());
        };
        let kind = VarModifierKind::try_from(keyword)?;
        Ok(Self { kind, position })
    }
}
