use chumsky::{input::Stream, prelude::*};
use imbl::HashMap;
use wasm_bindgen::prelude::*;
use std::rc::Rc;
use std::cell::RefCell;

const DEBUG_SCRIPT_COMPILE: bool = false;
const DEBUG_SCRIPT_RUN: bool = false;
const DEBUG_PRELUDE_COMPILE: bool = false;
const DEBUG_PRELUDE_RUN: bool = false;

mod io;
use io::send_err_to_ludus_console;

mod ast;
use crate::ast::Ast;

mod base;

mod world;
use crate::world::{World, Zoo};

mod spans;
use crate::spans::Spanned;

mod lexer;
use crate::lexer::lexer;

mod parser;
use crate::parser::parser;

mod validator;
use crate::validator::Validator;

mod errors;
use crate::errors::{lexing, parsing, validation};

mod panic;

mod js;
use crate::js::*;

mod chunk;
mod op;

mod compiler;
use crate::compiler::Compiler;

pub mod value;
use value::{Value, Key};

mod vm;
use vm::Creature;

const PRELUDE: &str = include_str!("../assets/prelude.ld");

fn prelude() -> HashMap<Key, Value> {
    let tokens = lexer().parse(PRELUDE).into_output_errors().0.unwrap();
    let (parsed, parse_errors) = parser()
        .parse(Stream::from_iter(tokens).map((0..PRELUDE.len()).into(), |(t, s)| (t, s)))
        .into_output_errors();

    if !parse_errors.is_empty() {
        console_log!("ERROR PARSING PRELUDE:");
        console_log!("{:?}", parse_errors);
        panic!("parsing errors in prelude");
    }

    let parsed = parsed.unwrap();
    let (ast, span) = &parsed;

    let base = base::make_base();
    let mut base_env = imbl::HashMap::new();
    base_env.insert(Key::Keyword("base"), base.clone());

    let mut validator = Validator::new(ast, span, "prelude", PRELUDE, base_env);

    validator.validate();

    if !validator.errors.is_empty() {
        console_log!("VALIDATION ERRORS IN PRLUDE:");
        // report_invalidation(validator.errors);
        console_log!("{:?}", validator.errors);
        panic!("validator errors in prelude");
    }

    let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parsed));
    let mut compiler = Compiler::new(
        parsed,
        "prelude",
        PRELUDE,
        0,
        HashMap::new(),
        DEBUG_PRELUDE_COMPILE,
    );
    compiler.emit_constant(base);
    compiler.bind("base");
    compiler.compile();

    let chunk = compiler.chunk;
    let stub_zoo = Rc::new(RefCell::new(Zoo::new()));
    let mut prld_sync = Creature::new(chunk, stub_zoo, DEBUG_PRELUDE_RUN);
    prld_sync.interpret();
    let prelude = prld_sync.result.unwrap().unwrap();
    match prelude {
        Value::Dict(hashmap) => *hashmap,
        _ => unreachable!(),
    }
}

#[wasm_bindgen]
pub async fn ludus(src: String) {
    // instrument wasm to report rust panics
    console_error_panic_hook::set_once();
    // leak the source so it lives FOREVER
    let src = src.to_string().leak();

    // lex the source
    let (tokens, lex_errs) = lexer().parse(src).into_output_errors();
    if !lex_errs.is_empty() {
        send_err_to_ludus_console(lexing(lex_errs, src, "user script")).await;
        return;
    }

    let tokens = tokens.unwrap();

    let (parse_result, parse_errors) = parser()
        .parse(Stream::from_iter(tokens).map((0..src.len()).into(), |(t, s)| (t, s)))
        .into_output_errors();
    if !parse_errors.is_empty() {
        send_err_to_ludus_console(parsing(parse_errors, src, "user script")).await;
        return;
    }

    let parsed: &'static Spanned<Ast> = Box::leak(Box::new(parse_result.unwrap()));

    let prelude = prelude();
    // let prelude = imbl::HashMap::new();

    let mut validator = Validator::new(&parsed.0, &parsed.1, "user input", src, prelude.clone());
    validator.validate();

    // TODO: validator should generate a string, not print to the console
    if !validator.errors.is_empty() {
        send_err_to_ludus_console(validation(validator.errors)).await;
        return;
    }

    let mut compiler = Compiler::new(
        parsed,
        "user script",
        src,
        0,
        prelude.clone(),
        DEBUG_SCRIPT_COMPILE,
    );

    compiler.compile();

    if DEBUG_SCRIPT_COMPILE {
        console_log!("=== source code ===");
        console_log!("{src}");
        compiler.disassemble();
        console_log!("\n\n")
    }

    if DEBUG_SCRIPT_RUN {
        console_log!("=== vm run ===");
    }

    let vm_chunk = compiler.chunk;

    let mut world = World::new(vm_chunk, prelude.clone(), DEBUG_SCRIPT_RUN);
    world.run().await;

    // TODO: actually do something useful on a panic
    // match result {
    //     Some(Ok(val)) => val.show(),
    //     Some(Err(panic)) => format!("Ludus panicked! {panic}"),
    //     None => "Ludus run terminated by user".to_string()
    // };
    // if DEBUG_SCRIPT_RUN {
    //     // vm.print_stack();
    // }

}
