//! Submodule with parsing related errors.

use std::collections::{HashMap, HashSet};

use crate::{lexer::TokenPosition, lexer::TokenSpan};

/// Internal parse error kinds.
///
/// Used by the parser as a compact signal for later construction of user-facing
/// diagnostics.
///
/// Naming convention:
/// - Prefix identifies the syntactic construct
///     (`Expression`, `For`, `Switch`, etc.).
/// - Suffix describes the exact problem (`MissingClosingParenthesis`,
///     `UnexpectedToken`, `MultipleDefaults`, etc.).
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ParseErrorKind {
    /// P0001
    ParenthesizedExpressionInvalidStart,
    /// P0002
    ExpressionExpected,
    /// P0003
    ParenthesizedExpressionMissingClosingParenthesis,
    /// P0004
    ClassTypeMissingTypeArgument,
    /// P0005
    ClassTypeExpectedQualifiedTypeName,
    /// P0006
    ClassTypeInvalidStart,
    /// P0007
    ClassTypeMissingClosingAngleBracket,
    /// P0008
    NewMissingClassSpecifier,
    /// P0009
    NewTooManyArguments,
    /// P0010
    NewMissingClosingParenthesis,
    /// P0011
    NewArgumentMissingComma,
    /// P0012
    ConditionExpected,
    /// P0013
    ControlFlowBodyExpected,
    /// P0014
    DoMissingUntil,
    /// P0015
    ForEachIteratorExpressionExpected,
    /// P0016
    ForLoopHeaderInitializerInvalidStart,
    /// P0017
    ForLoopHeaderMissingSemicolonAfterInitializer,
    /// P0018
    ForLoopHeaderConditionInvalidStart,
    /// P0019
    ForLoopHeaderMissingSemicolonAfterCondition,
    /// P0020
    ForLoopHeaderStepInvalidStart,
    /// P0021
    ForLoopHeaderMissingClosingParenthesis,
    /// P0022
    ReturnValueInvalidStart,
    /// P0023
    BreakValueInvalidStart,
    /// P0024
    GotoMissingLabel,
    /// P0025
    BlockMissingSemicolonAfterExpression,
    /// P0026
    BlockMissingClosingBrace,
    /// P0027
    BlockExpectedItem,
    /// P0028
    MemberAccessMissingMemberName,
    /// P0029
    IndexMissingExpression,
    /// P0030
    IndexMissingClosingBracket,
    /// P0031
    FunctionCallArgumentMissingComma,
    /// P0032
    FunctionCallMissingClosingParenthesis,
    /// P0033
    FunctionCallUnexpectedTokenInArgumentList,
    /// P0034
    SwitchMissingBody,
    /// P0035
    SwitchTopLevelItemNotCase,
    /// P0036
    SwitchCaseMissingColon,
    /// P0037
    SwitchDefaultMissingColon,
    /// P0038
    SwitchDuplicateDefault,
    /// P0039
    SwitchCasesAfterDefault,
    /// P0040
    SwitchMissingClosingBrace,
    /// P0041
    SwitchCaseMissingExpression,
    /// P0042
    SwitchCaseExpressionInvalidStart,
    /// P0043
    InvalidNumericLiteral,
    /// P0044
    EnumMissingName,
    /// P0045
    EnumMissingOpeningBrace,
    /// P0046
    EnumMissingClosingBrace,
    /// P0047
    EnumExpectedCommaOrClosingBraceAfterVariant,
    /// P0048
    EnumVariantInvalidStart,
    /// P0049
    EnumExpectedVariant,
    /// P0050
    LocalVariableDeclarationMissingSemicolon,
    /// P0051
    VariableDeclaratorListEmpty,
    /// P0052
    VariableDeclaratorExpected,
    /// P0053
    VariableDeclaratorListMissingSeparator,
    /// P0054
    VariableDeclaratorArraySizeMissingClosingBracket,
    /// P0055
    VariableDeclaratorArraySizeExpected,
    /// P0056
    VariableDeclaratorInitializerExpected,
    /// P0057
    VarEditorSpecifierExpected,
    /// P0058
    VarEditorSpecifierListMissingSeparator,
    /// P0059
    VarEditorSpecifierListExpectedCommaOrClosingParenthesis,
    /// P0060
    VarEditorSpecifierListMissingClosingParenthesis,
    /// P0061
    TypeSpecifierExpected,
    /// P0062
    NamedTypeInvalidName,
    /// P0063
    ArrayTypeMissingOpeningAngleBracket,
    /// P0064
    ArrayTypeMissingClosingAngleBracket,
    /// P0065
    ArrayTypeMissingElementType,
    // ================== Old errors to be thrown away! ==================
    /// Found an unexpected token while parsing an expression.
    ExpressionUnexpectedToken,
    DeclExpectedCommaAfterVariableDeclarator,
    TypeSpecArrayMissingInnerType,

    TypeSpecClassMissingInnerType,
    TypeSpecClassMissingClosingAngle,
    BlockMissingSemicolonAfterStatement,
    /// Unexpected end of input while parsing.
    UnexpectedEndOfFile,
    /// A bare expression appeared in a `switch` arm but was not the final arm.
    ///
    /// Such an expression must be terminated with `;` or be the final arm.
    SwitchBareExpressionBeforeNextArm,
    /// A `local` declaration is missing its first identifier.
    ///
    /// At least one variable name must follow the type.
    LocalMissingIdentifier,
    /// A `local` declaration was followed by a token that cannot serve
    /// as a type name.
    LocalInvalidTypeName,
    /// Invalid variable name identifier in `local` variable definition.
    LocalBadVariableIdentifier,
    /// An initializer appears in a `local` variable declaration.
    LocalInitializerNotAllowed,
    /// A non-`local` variable declaration is missing its first identifier.
    ///
    /// At least one variable name must follow the type.
    DeclMissingIdentifier,
    /// Found an unexpected token while parsing a declaration literal.
    ///
    /// Expected one of: integer, float, string, `true`, `false`, `none`
    /// or an identifier.
    DeclarationLiteralUnexpectedToken,
    /// A class name was expected, but the current token is not an identifier.
    ///
    /// Emitted when parsing `class Foo` and the token after `class` is not an
    /// identifier (so its string value cannot be extracted).
    ClassNameNotIdentifier,
    /// A parent class name after `extends` was expected, but the token is not
    /// an identifier.
    ///
    /// Emitted when parsing `class Foo extends Bar` and the token after
    /// `extends` is not an identifier.
    ClassParentNameNotIdentifier,
    /// A class declaration was not terminated with `;`.
    ///
    /// Emitted when the parser reaches the end of a class definition but
    /// does not encounter the required semicolon.
    ClassMissingSemicolon,
    /// An identifier was expected inside optional parentheses, but the token
    /// is not an identifier.
    ///
    /// Emitted by helpers that parse either `(<Ident>)` or bare `<Ident>`.
    ParenthesisedIdentifierNameNotIdentifier,
    /// A `(` was seen before an identifier, but the matching `)` was not found.
    ///
    /// Emitted when parsing a parenthesised identifier like `(Foo)`.
    ParenthesisedIdentifierMissingClosingParenthesis,
    /// `HideCategories` is missing the opening `(` before the category list.
    ///
    /// Expected syntax: `HideCategories(CategoryA, CategoryB, ...)`.
    HideCategoriesMissingOpeningParenthesis,
    /// `HideCategories` is missing the closing `)` after the category list.
    HideCategoriesMissingClosingParenthesis,
    /// `HideCategories` is missing the opening `(` before the category list.
    ///
    /// Expected syntax: `HideCategories(CategoryA, CategoryB, ...)`.
    ShowCategoriesMissingOpeningParenthesis,
    /// `HideCategories` is missing the closing `)` after the category list.
    ShowCategoriesMissingClosingParenthesis,
    /// `Within` must be followed by a class or package name identifier.
    ///
    /// Example: `Within(MyOuterClass)`.
    WithinNameNotIdentifier,
    /// `operator` modifier is missing the opening `(` before
    /// the precedence rank.
    ///
    /// Expected syntax: `operator(<integer>)`.
    OperatorMissingOpeningParenthesis,
    /// `operator(<...>)` must contain an integer literal precedence rank.
    ///
    /// Emitted when the token inside parentheses is not an integer literal.
    OperatorPrecedenceNotIntegerLiteral,
    /// `operator(<integer>` is missing the closing `)`.
    OperatorMissingClosingParenthesis,
    ParamInvalidTypeName,
    ParamMissingIdentifier,
    FunctionReturnTypeNotTypeName,
    FunctionNameNotIdentifier,
    FunctionParamsMissingOpeningParenthesis,
    FunctionParamsMissingClosingParenthesis,
    ClassUnexpectedItem,
    EnumBadVariant,
    StructFieldMissingName,
    StructFieldMissingSemicolon,
    StructMissingRightBrace,
    // Named enum/struct typedefs
    EnumMissingKeyword, // class member: expected `enum`
    EnumEmptyVariants,
    EnumNoSeparatorBetweenVariants,
    EnumMissingLBrace,
    StructMissingKeyword,      // class member: expected `struct`
    StructExpectedNameOrBrace, // after `struct`, expected identifier
    StructExpectedExtendsOrBrace,
    StructMissingLeftBrace,
    StructExpectedBaseName,
    StructBodyUnexpectedItem,
    CppDirectiveMissingCppBlock,

    // var(...) field decls
    VarMissingKeyword,                 // class member: expected `var`
    VarSpecsMissingOpeningParenthesis, // after `var`, expected '('
    VarSpecNotIdentifier,              // inside var(...), expected identifier
    VarSpecsMissingClosingParenthesis, // var(...) missing ')'

    // Generic decl end
    DeclMissingSemicolon, // class-level declaration missing `;`
    // --- Replication ---
    ReplicationMissingReliability,
    ReplicationIfMissingOpeningParenthesis,
    ReplicationIfMissingClosingParenthesis,
    ReplicationMemberNotIdentifier,
    ReplicationMemberMissingClosingParenthesis,
    ReplicationRuleMissingSemicolon,
    ReplicationMissingKeyword,
    ReplicationMissingLBrace,
    ReplicationMissingRBrace,

    // --- DefaultProperties ---
    DefaultPropPathExpectedIdentifier,
    DefaultPropIndexNotIntegerLiteral,
    DefaultPropIndexMissingClosingParenthesis,
    DefaultPropAssignMissingEq,
    DefaultPropsMissingKeyword,
    DefaultPropsMissingLBrace,
    DefaultPropsMissingRBrace,

    // --- Begin/End Object headers ---
    ObjectBeginMissingKeyword,
    ObjectMissingKeyword,
    ObjectHeaderKeyNotIdentifier,
    ObjectHeaderMissingEq,

    // --- State / ignores ---
    IgnoresItemNotIdentifier,
    IgnoresMissingSemicolon,
    StateMissingKeyword,
    StateNameNotIdentifier,
    StateParentNameNotIdentifier,
    StateMissingLBrace,
    StateMissingRBrace,

    ClassMissingKeyword,
    TypeMissingLT,
    TypeMissingGT,
    StateParensMissingRParen,
    BadTypeInClassTypeDeclaration,
    IdentifierExpected,
    VariableDeclaratorBadIdentifier,

    // --- Generic list diagnostics (comma-separated, closed by `)`) ---
    /// Saw `)` immediately after `(`, or closed the list without any items.
    /// Use when a construct requires at least one item: e.g. `HideCategories(...)`.
    ListEmpty,

    /// Parser was positioned where an item was required but found neither an
    /// item nor a terminator. Typical triggers:
    /// - Leading comma: `(, Foo)`
    /// - Double comma: `(Foo,, Bar)`
    /// - Garbage in place of an item: `(@@, Foo)`
    ///
    /// Recovery: skip to next comma or `)`.
    ListMissingIdentifierBeforeSeparator,

    /// Parser was positioned where an item was required but found neither an
    /// item nor a terminator. Typical triggers:
    /// - Leading comma: `(, Foo)`
    /// - Double comma: `(Foo,, Bar)`
    /// - Garbage in place of an item: `(@@, Foo)`
    ///
    /// Recovery: skip to next comma or `)`.
    ListInvalidIdentifier,

    /// Two items without a comma (or some token after an item where a comma
    /// was required). Typical triggers:
    /// - Adjacent identifiers: `(Foo Bar)`
    /// - Token after an item where only `,` or `)` are valid.
    ///
    /// Recovery: behave as if a comma were present; continue with the next item.
    ListMissingSeparator,

    /// Comma directly before `)`: `(Foo, )`.
    /// Treat as a soft error or warning, depending on your policy.
    ListTrailingSeparator,
    FunctionArgumentMissingComma,
    // Expression was required, but none started
    MissingExpression,
    CallableExpectedHeader,
    CallableExpectedKind,
    CallableOperatorInvalidPrecedence,
    CallableMissingBodyOrSemicolon,
    CallableNameNotIdentifier,
    CallablePrefixOperatorInvalidSymbol,
    CallableInfixOperatorInvalidSymbol,
    CallablePostfixOperatorInvalidSymbol,
    CallableParamsMissingOpeningParenthesis,
    CallableParamsMissingClosingParenthesis,
    NativeModifierIdNotIntegerLiteral,
}

/// Enumerates all specific kinds of parsing errors that the parser can emit.
#[derive(Clone, Debug, PartialEq, Eq)]
#[must_use]
pub struct ParseError {
    /// The specific kind of parse error that occurred.
    pub kind: ParseErrorKind,
    pub anchor: TokenPosition,
    /// Where the user should look first.
    pub blame_span: TokenSpan,
    /// The source span in which the error was detected.
    pub covered_span: TokenSpan,
    pub related_spans: HashMap<String, TokenSpan>,
    pub flags: HashSet<String>,
}

pub type ParseResult<'src, 'arena, T> = Result<T, ParseError>;

impl crate::parser::Parser<'_, '_> {
    pub(crate) fn make_error_at_last_consumed(&self, error_kind: ParseErrorKind) -> ParseError {
        self.make_error_at(error_kind, self.last_consumed_position_or_start())
    }

    pub(crate) fn make_error_at(
        &self,
        error_kind: ParseErrorKind,
        position: TokenPosition,
    ) -> ParseError {
        ParseError {
            kind: error_kind,
            anchor: position,
            blame_span: TokenSpan::new(position),
            covered_span: TokenSpan::new(position),
            related_spans: HashMap::new(),
            flags: HashSet::new(),
        }
    }
}
