use super::{Diagnostic, DiagnosticBuilder, FoundAt, found_at};
use crate::lexer::{Token, TokenSpan, TokenizedFile};
use crate::parser::ParseError;

/// P0025
pub(super) fn diagnostic_block_missing_semicolon_after_expression<'src>(
    error: ParseError,
    file: &TokenizedFile<'src>,
) -> Diagnostic {
    let expression_span = error.related_spans.get("expression_span").copied();

    let primary_span = TokenSpan::new(error.blame_span.end);

    let primary_text = match found_at(file, primary_span.end) {
        FoundAt::Token(token_text) => format!("expected `;` before `{}`", token_text),
        FoundAt::EndOfFile => "expected `;` before end of file".to_string(),
        FoundAt::Unknown => "expected `;` here".to_string(),
    };

    let mut builder = DiagnosticBuilder::error("missing `;` after expression statement");

    if let Some(expression_span) = expression_span {
        if file.same_line(expression_span.start, primary_span.end) {
            builder = builder.secondary_label(expression_span, "expression statement");
        } else {
            builder = builder.secondary_label(
                TokenSpan::new(expression_span.end),
                "expression statement ends here",
            );
        }
    }

    builder
        .primary_label(primary_span, primary_text)
        .code("P0025")
        .build()
}

/// P0026
pub(super) fn diagnostic_block_missing_closing_brace<'src>(
    error: ParseError,
    file: &TokenizedFile<'src>,
) -> Diagnostic {
    let left_brace_span = error.related_spans.get("left_brace").copied();
    let unexpected_token_span = error.related_spans.get("unexpected_token").copied();

    let (mut primary_span, primary_text) =
        if let Some(unexpected_token_span) = unexpected_token_span {
            let primary_span = TokenSpan::new(unexpected_token_span.end);

            let primary_text = match found_at(file, primary_span.end) {
                FoundAt::Token(token_text) => format!("expected `}}` before `{}`", token_text),
                FoundAt::EndOfFile => "expected `}` before end of file".to_string(),
                FoundAt::Unknown => "expected `}` here".to_string(),
            };

            (primary_span, primary_text)
        } else {
            (
                TokenSpan::new(error.blame_span.end),
                "expected `}` before end of file".to_string(),
            )
        };

    let builder = DiagnosticBuilder::error("missing `}` to close block");

    if let Some(left_brace_span) = left_brace_span
        && !file.same_line(left_brace_span.start, primary_span.end)
    {
        primary_span.start = left_brace_span.start;
    }

    builder
        .primary_label(primary_span, primary_text)
        .code("P0026")
        .build()
}

/// P0027
pub(super) fn diagnostic_block_expected_item<'src>(
    error: ParseError,
    file: &TokenizedFile<'src>,
) -> Diagnostic {
    let primary_span = error.blame_span;

    let (title, primary_text) = match file.token_at(primary_span.start).map(|data| data.token) {
        Some(Token::ExecDirective) => (
            "expected statement or expression, found `#exec` directive".to_string(),
            "`#exec` directives are not allowed in a statement block".to_string(),
        ),
        _ => match found_at(file, primary_span.start) {
            FoundAt::Token(token_text) => (
                format!("expected statement or expression, found `{}`", token_text),
                format!("unexpected `{}`", token_text),
            ),
            FoundAt::EndOfFile => (
                "expected statement or expression, found end of file".to_string(),
                "reached end of file here".to_string(),
            ),
            FoundAt::Unknown => (
                "expected statement or expression".to_string(),
                "expected statement or expression here".to_string(),
            ),
        },
    };

    DiagnosticBuilder::error(title)
        .primary_label(primary_span, primary_text)
        .code("P0027")
        .build()
}
