//! Callable-declaration AST nodes.
//!
//! This module defines function-like declarations together with their
//! parameter lists and callable modifiers.
//!
//! The language groups several callable forms under a largely shared header
//! structure, including ordinary functions, events, delegates, and operator
//! declarations. This module preserves those forms as AST nodes together with
//! source-relevant modifier and parameter information.

use super::{BlockBody, ExpressionRef, IdentifierToken, InfixOperatorName, PostfixOperatorName,
    PrefixOperatorName, TypeSpecifierRef,
};
use crate::arena::ArenaVec;
use crate::lexer::{TokenSpan, Keyword, TokenPosition};

use crate::arena::ArenaNode;

use core::convert::TryFrom;

/// Parameter modifier kind.
///
/// These modifiers apply to a single callable parameter and are preserved in
/// source order on the parameter node.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ParameterModifierKind {
    Optional,
    Out,
    Skip,
    Coerce,
}

/// Parameter modifier together with the source position of its token.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ParameterModifier {
    pub kind: ParameterModifierKind,
    pub position: TokenPosition,
}

/// One callable parameter declaration.
#[derive(Debug, PartialEq)]
pub struct Parameter<'src, 'arena> {
    /// Parameter modifiers in source order.
    pub modifiers: ArenaVec<'arena, ParameterModifier>,
    /// Declared parameter type.
    pub type_specifier: TypeSpecifierRef<'src, 'arena>,
    /// Declared parameter name.
    pub name: IdentifierToken,
    /// Optional array-size expression from `[expr]`.
    pub array_size: Option<ExpressionRef<'src, 'arena>>,
    /// Optional default-value expression after `=`.
    pub default_value: Option<ExpressionRef<'src, 'arena>>,
}

/// Stable arena reference to a parameter node.
pub type ParameterRef<'src, 'arena> = ArenaNode<'arena, Parameter<'src, 'arena>>;

/// Syntactic callable declaration kind.
///
/// This enum distinguishes ordinary callable declarations from operator
/// declarations and preserves operator fixity / precedence where applicable.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum CallableKind {
    /// Ordinary function declaration: `function`.
    Function,
    /// Event declaration: `event`.
    Event,
    /// Delegate declaration: `delegate`.
    Delegate,
    /// Prefix operator declaration: `preoperator`.
    PrefixOperator,
    /// Infix operator declaration: `operator(<precedence>)`.
    ///
    /// Precedence can be skipped as all supported operators already have
    /// built-in precedence value that can't actually be changed in
    /// `UnrealScript`. So omitting precedence when redefining operators is
    /// a better approach.
    InfixOperator(Option<u128>),
    /// Postfix operator declaration: `postoperator`.
    PostfixOperator,
}

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

    /// Converts a keyword into a [`CallableKind`] when the callable form
    /// is fully determined by the keyword alone.
    ///
    /// Returns `Err(())` for keywords that either do not represent callable
    /// declarations or require additional syntax to determine the final kind
    /// (for example `operator(<precedence>)`).
    fn try_from(keyword: Keyword) -> Result<Self, Self::Error> {
        let kind = match keyword {
            Keyword::Function => Self::Function,
            Keyword::Event => Self::Event,
            Keyword::Delegate => Self::Delegate,
            Keyword::PreOperator => Self::PrefixOperator,
            Keyword::PostOperator => Self::PostfixOperator,
            _ => return Err(()),
        };
        Ok(kind)
    }
}

#[derive(Debug, PartialEq, Eq, Hash)]
pub enum CallableName {
    Identifier(IdentifierToken),
    PrefixOperator(PrefixOperatorName),
    InfixOperator(InfixOperatorName),
    PostfixOperator(PostfixOperatorName),
}

/// Callable definition.
///
/// This node represents the common syntactic shape shared by function-like
/// declarations, including ordinary functions, events, delegates, and
/// operator forms.
#[derive(Debug, PartialEq)]
pub struct CallableDefinition<'src, 'arena> {
    /// Declared callable name.
    pub name: CallableName,
    /// Callable declaration form.
    pub kind: CallableKind,
    /// Optional return type.
    ///
    /// Some callable forms may omit a return type entirely.
    pub return_type_specifier: Option<TypeSpecifierRef<'src, 'arena>>,
    /// Declaration modifiers attached to the callable header.
    pub modifiers: ArenaVec<'arena, CallableModifier>,
    /// Formal parameters in source order.
    pub parameters: ArenaVec<'arena, ParameterRef<'src, 'arena>>,
    /// Optional callable body.
    ///
    /// `None` represents a header-only declaration terminated by `;`.
    /// `Some(...)` stores the parsed block statements belonging to the body.
    pub body: Option<BlockBody<'src, 'arena>>,
}

/// Stable arena reference to a callable definition node.
pub type CallableDefinitionRef<'src, 'arena> = ArenaNode<'arena, CallableDefinition<'src, 'arena>>;

/// Callable declaration modifier kind.
///
/// These modifiers apply to the callable declaration itself rather than to an
/// individual parameter.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CallableModifierKind {
    Final,
    /// `native` or `native(<index>)`
    Native(Option<u128>),
    Abstract,
    Transient,
    Public,
    Protected,
    Private,
    Static,
    /// `config(<name>)`
    Config(IdentifierToken),
    Const,
    Deprecated,
    NoExport,
    Export,
    Simulated,
    Latent,
    Iterator,
    Singular,
    Exec,
    Reliable,
    Unreliable,
    NativeReplication,
}

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

    /// Converts a keyword into a [`CallableModifierKind`] when the modifier
    /// is fully determined by the keyword alone.
    ///
    /// Returns `Err(())` for keywords that either do not represent callable
    /// modifiers or require additional syntax
    /// (e.g. `native(...)`, `config(...)`).
    #[allow(clippy::enum_glob_use)]
    fn try_from(keyword: Keyword) -> Result<Self, Self::Error> {
        use CallableModifierKind::*;

        let kind = match keyword {
            Keyword::Final => Final,
            Keyword::Abstract => Abstract,
            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::Simulated => Simulated,
            Keyword::Latent => Latent,
            Keyword::Iterator => Iterator,
            Keyword::Singular => Singular,
            Keyword::Exec => Exec,
            Keyword::Reliable => Reliable,
            Keyword::Unreliable => Unreliable,
            Keyword::NativeReplication => NativeReplication,
            _ => return Err(()),
        };
        Ok(kind)
    }
}

/// Callable modifier together with its full source span.
///
/// A modifier may occupy more than one token in source, for example when it
/// carries an argument like `native(12)` or `config(System)`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CallableModifier {
    /// Modifier kind.
    pub kind: CallableModifierKind,
    /// Span covering the full modifier syntax.
    pub span: TokenSpan,
}

impl Keyword {
    #[must_use]
    pub fn is_callable_modifier(self) -> bool {
        matches!(self, Self::Native | Self::Config) || CallableModifierKind::try_from(self).is_ok()
    }

    #[must_use]
    pub fn is_callable_kind_keyword(self) -> bool {
        matches!(self, Self::Operator) || CallableKind::try_from(self).is_ok()
    }
}
