use crate::{
    check, diag::Diagnostic, formatter, lexer, llvm, lower, sexpr, test_runner, types::Type,
};

pub fn compile_to_llvm(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let checked = checked_program(file, source)?;
    llvm::emit(file, &checked)
}

pub fn check_source(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let checked = checked_program(file, source)?;
    llvm::emit(file, &checked)?;
    Ok(String::new())
}

pub fn print_parse_tree(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let tokens = lexer::lex(file, source)?;
    let forms = sexpr::parse(file, &tokens)?;
    Ok(sexpr::print_tree(&forms))
}

pub fn format_source(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let tokens = lexer::lex(file, source)?;
    let forms = sexpr::parse(file, &tokens)?;
    formatter::format(file, source, &forms)
}

pub fn inspect_lowering_surface(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let tokens = lexer::lex(file, source)?;
    let forms = sexpr::parse(file, &tokens)?;
    let program = lower::lower_program(file, &forms)?;
    Ok(lower::print_program(&program))
}

pub fn inspect_lowering_checked(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let checked = checked_program(file, source)?;
    Ok(check::print_checked_program(&checked))
}

pub fn check_tests(file: &str, source: &str) -> Result<String, Vec<Diagnostic>> {
    let checked = checked_program(file, source)?;
    Ok(test_runner::check_output(&checked))
}

pub fn run_tests(
    file: &str,
    source: &str,
    filter: Option<&str>,
) -> Result<test_runner::TestRunSuccess, test_runner::TestRunFailure> {
    let checked =
        checked_program(file, source).map_err(test_runner::TestRunFailure::before_execution)?;
    test_runner::run(file, &checked, filter)
}

pub fn list_tests(
    file: &str,
    source: &str,
    filter: Option<&str>,
) -> Result<test_runner::TestRunSuccess, test_runner::TestRunFailure> {
    let checked =
        checked_program(file, source).map_err(test_runner::TestRunFailure::before_execution)?;
    Ok(test_runner::list(&checked, filter))
}

fn checked_program(file: &str, source: &str) -> Result<check::CheckedProgram, Vec<Diagnostic>> {
    let tokens = lexer::lex(file, source)?;
    let forms = sexpr::parse(file, &tokens)?;
    let program = lower::lower_program(file, &forms)?;
    let checked = check::check_program(file, program)?;
    validate_single_file_main_signature(file, &checked)?;
    Ok(checked)
}

fn validate_single_file_main_signature(
    file: &str,
    program: &check::CheckedProgram,
) -> Result<(), Vec<Diagnostic>> {
    let Some(main) = program
        .functions
        .iter()
        .find(|function| function.name == "main")
    else {
        return Ok(());
    };

    if main.params.is_empty() && main.return_type == Type::I32 {
        return Ok(());
    }

    let found = format!(
        "fn main ({}) -> {}",
        main.params
            .iter()
            .map(|(_, ty)| ty.to_string())
            .collect::<Vec<_>>()
            .join(" "),
        main.return_type
    );

    Err(vec![Diagnostic::new(
        file,
        "SingleFileMainSignature",
        "single-file `main` must have no parameters and return `i32`",
    )
    .with_span(main.span)
    .expected("fn main () -> i32")
    .found(found)
    .hint(
        "return an i32 exit status from main; use helper functions for wider values",
    )])
}
