#![allow(
    clippy::all,
    clippy::pedantic,
    clippy::nursery,
    clippy::cargo,
    clippy::restriction
)]

use rottlib::arena::Arena;
use rottlib::diagnostics::Diagnostic;
use rottlib::lexer::TokenizedFile;
use rottlib::parser::Parser;

/// Lexer-focused fixtures.
///
/// Keep these small: the goal is to inspect lexer diagnostics and delimiter
/// recovery behavior, not full parser behavior.
const TEST_CASES: &[(&str, &str)] = &[
    // P0034 - SwitchMissingBody
    ("files/P0034_01.uc", "switch(A) local\n"),
    ("files/P0034_02.uc", "switch\n(A)\nvar"),
    ("files/P0034_03.uc", "switch(\n    A\n)\n"),
    ("files/P0034_04.uc", "switch\n(\n    A\n)\n"),
    ("files/P0034_05.uc", "switch(A)\ncase 1:\n"),

    // P0035 - SwitchTopLevelItemNotCase
    ("files/P0035_01.uc", "switch(A) {\n    Log(\"bad\");\n}\n"),
    (
        "files/P0035_02.uc",
        "switch\n(A)\n{\n    Log(\"bad\");\n  Log(\"worse\");\n  case 1:\n}\n",
    ),
    ("files/P0035_03.uc", "switch(A) {\n    123;\n    default:\n}\n"),
    (
        "files/P0035_04.uc",
        "switch\n(\n    A\n)\n{\n    if (A) {}\n    case 1:\n}\n",
    ),
    (
        "files/P0035_05.uc",
        "switch(A) {\n    {\n        Log(\"nested\");\n    }\n    case 1:\n}\n",
    ),

    // P0036 - SwitchCaseMissingColon
    (
        "files/P0036_01.uc",
        "switch(A) {\n    case 1\n    case 2:\n}\n",
    ),
    (
        "files/P0036_02.uc",
        "switch\n(A)\n{\n    case\n        1\n    default:\n}\n",
    ),
    (
        "files/P0036_03.uc",
        "switch(A) {\n    case (A)\n    case B:\n}\n",
    ),
    (
        "files/P0036_04.uc",
        "switch\n(\n    A\n)\n{\n    case\n        A + B\n    default\n        :\n}\n",
    ),
    (
        "files/P0036_05.uc",
        "switch(A) {\n    case Foo.Bar(Baz)\n    case Other:\n}\n",
    ),

    // P0037 - SwitchDefaultMissingColon
    (
        "files/P0037_01.uc",
        "switch(A) {\n    default\n    if (A) {}\n}\n",
    ),
    (
        "files/P0037_02.uc",
        "switch\n(A)\n{\n    default\n    while (A) {}\n}\n",
    ),
    (
        "files/P0037_03.uc",
        "switch(A) {\n    default\n    for (;;) {}\n}\n",
    ),
    (
        "files/P0037_04.uc",
        "switch\n(\n    A\n)\n{\n    default\n    switch(B) {\n        case 1:\n    }\n}\n",
    ),
    (
        "files/P0037_05.uc",
        "switch(A) {\n    default\n    case 1:\n}\n",
    ),

    // P0038 - SwitchDuplicateDefault
    (
        "files/P0038_01.uc",
        "switch(A) {\n    default:\n    default:\n}\n",
    ),
    (
        "files/P0038_02.uc",
        "switch\n(A)\n{\n    default\n        :\n    default\n        :\n}\n",
    ),
    (
        "files/P0038_03.uc",
        "switch(A) {\n    default:\n    default:\n    default:\n}\n",
    ),
    (
        "files/P0038_04.uc",
        "switch\n(\n    A\n)\n{\n    default:\n        Log(\"first\");\n    default:\n        Log(\"second\");\n}\n",
    ),

    // P0039 - SwitchCasesAfterDefault
    (
        "files/P0039_01.uc",
        "switch(A) {\n    default:\n    case 1:\n}\n",
    ),
    (
        "files/P0039_02.uc",
        "switch\n(A)\n{\n    default\n        :\n    case\n        1\n        :\n}\n",
    ),
    (
        "files/P0039_03.uc",
        "switch(A) {\n    default:\n    case 1:\n    case 2:\n}\n",
    ),
    (
        "files/P0039_04.uc",
        "switch\n(\n    A\n)\n{\n    default:\n    case 1:\n        Log(\"one\");\n    case 2:\n        Log(\"two\");\n}\n",
    ),
    (
        "files/P0039_05.uc",
        "switch(A) {\n    default:\n        Log(\"done\");\n    case 1:\n    case 2:\n        Log(\"stacked\");\n}\n",
    ),

    // P0040 - SwitchMissingClosingBrace
    ("files/P0040_01.uc", "switch(A) {\n"),
    ("files/P0040_02.uc", "switch(A) {\n    case 1:\n"),
    ("files/P0040_03.uc", "switch\n(A)\n{\n    default:\n"),
    (
        "files/P0040_04.uc",
        "switch\n(\n    A\n)\n{\n    case 1:\n    case 2:\n",
    ),
    (
        "files/P0040_05.uc",
        "switch(A) {\n    case 1:\n        Log(\"body\");\n",
    ),

    // P0041 - SwitchCaseMissingExpression
    ("files/P0041_01.uc", "switch(A) {\n    case:\n}\n"),
    ("files/P0041_02.uc", "switch\n(A)\n{\n    case\n        :\n}\n"),
    ("files/P0041_03.uc", "switch(A) {\n    case:\n    default:\n}\n"),
    (
        "files/P0041_04.uc",
        "switch\n(\n    A\n)\n{\n    case\n        :\n    case 1:\n}\n",
    ),
    (
        "files/P0041_05.uc",
        "switch(A) {\n    case\n        :\n    default:\n}\n",
    ),

    // P0042 - SwitchCaseExpressionInvalidStart
    ("files/P0042_01.uc", "switch(A) {\n    case *:\n}\n"),
    (
        "files/P0042_02.uc",
        "switch\n(A)\n{\n    case\n        =\n        :\n}\n",
    ),
    ("files/P0042_03.uc", "switch(A) {\n    case &&:\n    default:\n}\n"),
    (
        "files/P0042_04.uc",
        "switch\n(\n    A\n)\n{\n    case\n        .\n        :\n    case 1:\n}\n",
    ),
    (
        "files/P0042_05.uc",
        "switch(A) {\n    case ]:\n}\n",
    ),

    // Mixed / intentional cascades
    (
        "files/P0042_cascade_01.uc",
        "switch(A) {\n    case *\n    case 1:\n}\n",
    ),
    (
        "files/P0042_cascade_02.uc",
        "switch\n(\n    A\n)\n{\n    case\n        =\n    default:\n}\n",
    ),
];

/// If true, also run the parser after tokenization.
///
/// For lexer-focused fixtures this is usually noisy, so keep it off unless you
/// want to inspect how parser recovery behaves after lexer diagnostics.
const RUN_PARSER: bool = true;

/// If true, print the parsed expression using Debug formatting.
const PRINT_PARSED_EXPR: bool = false;

const PRINT_LEXER_DIAGNOSTICS: bool = false;
const ALWAYS_PRINT_DIAGNOSTICS: bool = true;

fn main() {
    let arena = Arena::new();

    let mut had_any_problem = false;

    for (idx, (label, source)) in TEST_CASES.iter().enumerate() {
        println!("============================================================");
        println!("Case {}: {}", idx + 1, label);
        println!("------------------------------------------------------------");

        let tf = TokenizedFile::tokenize(source);

        let lexer_diagnostics = tf.diagnostics();

        if lexer_diagnostics.is_empty() {
            println!("Lexer diagnostics: none");
        } else {
            had_any_problem = true;

            if PRINT_LEXER_DIAGNOSTICS {
                println!("Lexer diagnostics:");
                for diag in lexer_diagnostics {
                    render_diagnostic(diag, &tf, Some(label), false);
                }
            }
        }

        if RUN_PARSER {
            let mut parser = Parser::new(&tf, &arena);
            let expr = parser.parse_expression();

            if PRINT_PARSED_EXPR {
                println!("Parsed expression:");
                println!("{expr:#?}");
            }

            if parser.diagnostics.is_empty() {
                println!("Parser diagnostics: none");
            } else {
                had_any_problem = true;

                if ALWAYS_PRINT_DIAGNOSTICS {
                    println!("Parser diagnostics:");
                    for diag in &parser.diagnostics {
                        render_diagnostic(diag, &tf, Some(label), false);
                    }
                }
            }
        }

        println!();
    }

    println!("============================================================");

    if had_any_problem {
        println!("Done. At least one case had lexer or parser diagnostics.");
        std::process::exit(1);
    } else {
        println!("Done. All cases completed without diagnostics.");
    }
}

fn render_diagnostic(
    diag: &Diagnostic,
    file: &TokenizedFile<'_>,
    file_name: Option<&str>,
    _colors: bool,
) {
    diag.render(file, file_name.unwrap_or("<default>"));
}
