//! Conversion from parser errors to user-facing diagnostics.
//!
//! This module maps [`ParseError`] values produced by the parser to structured
//! [`Diagnostic`] values suitable for rendering.
//!
//! It owns the top-level dispatch by [`crate::parser::ParseErrorKind`] and
//! keeps small shared utilities used by parse-error diagnostic constructors.
//!
//! Concrete diagnostic constructors are grouped into submodules that mirror
//! parser areas or grammar families.

use super::{Diagnostic, DiagnosticBuilder};
use crate::diagnostics::parse_error_diagnostics::block_items::{
    diagnostic_block_expected_item, diagnostic_block_missing_closing_brace,
    diagnostic_block_missing_semicolon_after_expression,
};
use crate::lexer::{TokenPosition, TokenSpan, TokenizedFile};
use crate::parser::{ParseError, ParseErrorKind};

mod block_items;
mod control_flow_expressions;
mod declarations;
mod primary_expressions;
mod selector_expressions;
mod switch_expressions;

#[derive(Clone, Copy)]
enum FoundAt<'src> {
    Token(&'src str),
    EndOfFile,
    Unknown,
}

fn found_at<'src>(file: &TokenizedFile<'src>, position: TokenPosition) -> FoundAt<'src> {
    if let Some(token_text) = file.token_text(position) {
        FoundAt::Token(token_text)
    } else if file.is_eof(&position) {
        FoundAt::EndOfFile
    } else {
        FoundAt::Unknown
    }
}

fn collapse_span_to_end_on_same_line(file: &TokenizedFile<'_>, mut span: TokenSpan) -> TokenSpan {
    if file.same_line(span.start, span.end) {
        span.start = span.end;
    }
    span
}

fn should_show_context_label<'src>(
    file: &TokenizedFile<'src>,
    context_span: TokenSpan,
    blame_span: TokenSpan,
) -> bool {
    !file.same_line(context_span.start, blame_span.end)
}

fn primary_span_with_optional_multiline_context<'src>(
    file: &TokenizedFile<'src>,
    context_span: Option<TokenSpan>,
    blame_span: TokenSpan,
) -> TokenSpan {
    if let Some(context_span) = context_span
        && should_show_context_label(file, context_span, blame_span)
    {
        TokenSpan {
            start: context_span.start,
            end: blame_span.end,
        }
    } else {
        collapse_span_to_end_on_same_line(file, blame_span)
    }
}

pub(crate) fn diagnostic_from_parse_error<'src>(
    error: ParseError,
    file: &TokenizedFile<'src>,
) -> Diagnostic {
    use control_flow_expressions::*;
    use declarations::*;
    use primary_expressions::*;
    use selector_expressions::*;
    use switch_expressions::*;
    match error.kind {
        // primary_expressions.rs
        ParseErrorKind::ParenthesizedExpressionInvalidStart => {
            diagnostic_parenthesized_expression_invalid_start(error, file)
        }
        ParseErrorKind::ExpressionExpected => diagnostic_expression_expected(error, file),
        ParseErrorKind::ParenthesizedExpressionMissingClosingParenthesis => {
            diagnostic_parenthesized_expression_missing_closing_parenthesis(error, file)
        }
        ParseErrorKind::ClassTypeMissingTypeArgument => {
            diagnostic_class_type_missing_type_argument(error, file)
        }
        ParseErrorKind::ClassTypeExpectedQualifiedTypeName => {
            diagnostic_class_type_expected_qualified_type_name(error, file)
        }
        ParseErrorKind::ClassTypeInvalidStart => diagnostic_class_type_invalid_start(error, file),
        ParseErrorKind::ClassTypeMissingClosingAngleBracket => {
            diagnostic_class_type_missing_closing_angle_bracket(error, file)
        }
        ParseErrorKind::NewMissingClassSpecifier => {
            diagnostic_new_missing_class_specifier(error, file)
        }
        ParseErrorKind::NewTooManyArguments => diagnostic_new_too_many_arguments(error, file),

        ParseErrorKind::NewMissingClosingParenthesis => {
            diagnostic_new_missing_closing_parenthesis(error, file)
        }
        ParseErrorKind::NewArgumentMissingComma => {
            diagnostic_new_argument_missing_comma(error, file)
        }
        // control_flow_expressions.rs
        ParseErrorKind::ConditionExpected => diagnostic_condition_expected(error, file),
        ParseErrorKind::ControlFlowBodyExpected => {
            diagnostic_control_flow_body_expected(error, file)
        }
        ParseErrorKind::DoMissingUntil => diagnostic_do_missing_until(error, file),
        ParseErrorKind::ForEachIteratorExpressionExpected => {
            diagnostic_for_each_iterator_expression_expected(error, file)
        }
        ParseErrorKind::ForLoopHeaderInitializerInvalidStart => {
            diagnostic_for_loop_header_initializer_invalid_start(error, file)
        }
        ParseErrorKind::ForLoopHeaderMissingSemicolonAfterInitializer => {
            diagnostic_for_loop_header_missing_semicolon_after_initializer(error, file)
        }
        ParseErrorKind::ForLoopHeaderConditionInvalidStart => {
            diagnostic_for_loop_header_condition_invalid_start(error, file)
        }
        ParseErrorKind::ForLoopHeaderMissingSemicolonAfterCondition => {
            diagnostic_for_loop_header_missing_semicolon_after_condition(error, file)
        }
        ParseErrorKind::ForLoopHeaderStepInvalidStart => {
            diagnostic_for_loop_header_step_invalid_start(error, file)
        }
        ParseErrorKind::ForLoopHeaderMissingClosingParenthesis => {
            diagnostic_for_loop_header_missing_closing_parenthesis(error, file)
        }
        ParseErrorKind::ReturnValueInvalidStart => {
            diagnostic_return_value_invalid_start(error, file)
        }
        ParseErrorKind::BreakValueInvalidStart => diagnostic_break_value_invalid_start(error, file),
        ParseErrorKind::GotoMissingLabel => diagnostic_goto_missing_label(error, file),
        // block_items.rs
        ParseErrorKind::BlockMissingSemicolonAfterExpression => {
            diagnostic_block_missing_semicolon_after_expression(error, file)
        }
        ParseErrorKind::BlockMissingClosingBrace => {
            diagnostic_block_missing_closing_brace(error, file)
        }
        ParseErrorKind::BlockExpectedItem => diagnostic_block_expected_item(error, file),
        // selector_expression.rs
        ParseErrorKind::MemberAccessMissingMemberName => {
            diagnostic_member_access_missing_member_name(error, file)
        }
        ParseErrorKind::IndexMissingExpression => diagnostic_index_missing_expression(error, file),
        ParseErrorKind::IndexMissingClosingBracket => {
            diagnostic_index_missing_closing_bracket(error, file)
        }
        ParseErrorKind::FunctionCallArgumentMissingComma => {
            diagnostic_function_call_argument_missing_comma(error, file)
        }
        ParseErrorKind::FunctionCallMissingClosingParenthesis => {
            diagnostic_function_call_missing_closing_parenthesis(error, file)
        }
        ParseErrorKind::FunctionCallUnexpectedTokenInArgumentList => {
            diagnostic_function_call_unexpected_token_in_argument_list(error, file)
        }
        // switch_expressions.rs
        ParseErrorKind::SwitchMissingBody => diagnostic_switch_missing_body(error, file),
        ParseErrorKind::SwitchTopLevelItemNotCase => {
            diagnostic_switch_top_level_item_not_case(error, file)
        }
        ParseErrorKind::SwitchCaseMissingColon => diagnostic_switch_case_missing_colon(error, file),
        ParseErrorKind::SwitchDefaultMissingColon => {
            diagnostic_switch_default_missing_colon(error, file)
        }
        ParseErrorKind::SwitchDuplicateDefault => diagnostic_switch_duplicate_default(error, file),
        ParseErrorKind::SwitchCasesAfterDefault => {
            diagnostic_switch_cases_after_default(error, file)
        }
        ParseErrorKind::SwitchMissingClosingBrace => {
            diagnostic_switch_missing_closing_brace(error, file)
        }
        ParseErrorKind::SwitchCaseMissingExpression => {
            diagnostic_switch_case_missing_expression(error, file)
        }
        ParseErrorKind::SwitchCaseExpressionInvalidStart => {
            diagnostic_switch_case_expression_invalid_start(error, file)
        }
        // declarations.rs
        ParseErrorKind::LocalVariableDeclarationMissingSemicolon => {
            diagnostic_local_variable_declaration_missing_semicolon(error, file)
        }
        ParseErrorKind::VariableDeclaratorListEmpty => {
            diagnostic_variable_declarator_list_empty(error, file)
        }
        ParseErrorKind::VariableDeclaratorExpected => {
            diagnostic_variable_declarator_expected(error, file)
        }
        ParseErrorKind::VariableDeclaratorListMissingSeparator => {
            diagnostic_variable_declarator_list_missing_separator(error, file)
        }
        ParseErrorKind::VariableDeclaratorArraySizeMissingClosingBracket => {
            diagnostic_variable_declarator_array_size_missing_closing_bracket(error, file)
        }
        ParseErrorKind::VariableDeclaratorArraySizeExpected => {
            diagnostic_variable_declarator_array_size_expected(error, file)
        }
        ParseErrorKind::VariableDeclaratorInitializerExpected => {
            diagnostic_variable_declarator_initializer_expected(error, file)
        }

        _ => DiagnosticBuilder::error(format!("error {:?} while parsing", error.kind))
            .primary_label(error.covered_span, "happened here")
            .build(),
    }
}
