use crate::js::*;
use crate::lexer::Token;
use crate::panic::{Panic, PanicMsg};
use crate::validator::VErr;
use crate::vm::CallFrame;
use chumsky::error::RichPattern;
use chumsky::prelude::*;

const SEPARATOR: &str = "\n\n";

fn line_number(src: &'static str, span: SimpleSpan) -> usize {
    src.chars().take(span.start).filter(|c| *c == '\n').count()
}

fn get_line(src: &'static str, line: usize) -> String {
    src.split("\n").nth(line).unwrap().to_string()
}

pub fn lexing(errs: Vec<Rich<'static, char>>, src: &'static str, input: &'static str) -> String {
    let mut msgs = vec!["Ludus found some errors.".to_string()];
    for err in errs {
        let mut msg = vec![];
        let line_number = line_number(src, *err.span());
        let line = get_line(src, line_number);
        let char = src.chars().nth(err.span().start).unwrap();
        msg.push(format!("Syntax error: unexpected {char}"));
        msg.push(format!("  on line {} in {}", line_number + 1, input));
        msg.push(format!("  >>> {line}"));
        msgs.push(msg.join("\n"));
    }
    msgs.join(SEPARATOR)
}

pub fn validation(errs: Vec<VErr>) -> String {
    let mut msgs = vec!["Ludus found some errors.".to_string()];
    for err in errs {
        let mut msg = vec![];
        let line_number = line_number(err.src, *err.span);
        let line = get_line(err.src, line_number);
        msg.push(format!("Validation error: {}", err.msg));
        msg.push(format!("  on line {} in {}", line_number + 1, err.input));
        msg.push(format!("  >>> {line}"));
        msgs.push(msg.join("\n"));
    }
    msgs.join(SEPARATOR)
}

pub fn parsing(errs: Vec<Rich<'static, Token>>, src: &'static str, input: &'static str) -> String {
    let mut msgs = vec!["Ludus found some errors.".to_string()];
    for err in errs {
        let mut msg = vec![];
        let line_number = line_number(src, *err.span());
        let line = get_line(src, line_number);
        let details = parsing_message(err);
        msg.push(format!("Syntax error: {}", details));
        msg.push(format!("  on line {} in {}", line_number + 1, input));
        msg.push(format!("  >>> {line}"));
        msgs.push(msg.join("\n"))
    }
    msgs.join(SEPARATOR)
}

fn parsing_message(err: Rich<'static, Token>) -> String {
    let found = match err.found() {
        Some(token) => token.show(),
        None => "end of input".to_string(),
    };
    let expected = err.expected();
    let mut expecteds = vec![];
    for pattern in expected {
        let shown = match pattern {
            RichPattern::Token(t) => t.show(),
            RichPattern::Label(s) => s.to_string(),
            RichPattern::Identifier(s) => s.clone(),
            RichPattern::Any => "any".to_string(),
            RichPattern::SomethingElse => "something else".to_string(),
            RichPattern::EndOfInput => "eof".to_string(),
        };
        expecteds.push(shown);
    }
    let expecteds = if expecteds.iter().any(|e| e == &"else".to_string()) {
        vec!["else".to_string()]
    } else {
        expecteds
    };
    let expecteds = if expecteds.iter().any(|e| e == &"then".to_string()) {
        vec!["then".to_string()]
    } else {
        expecteds
    };

    let expecteds = expecteds.join(" | ");
    format!("Ludus did not expect to see: {found}\n    expected: {expecteds}")
}

pub fn panic(panic: Panic) -> String {
    // console_log!("Ludus panicked!: {panic}");
    // panic.call_stack.last().unwrap().chunk().dissasemble();
    // console_log!("{:?}", panic.call_stack.last().unwrap().chunk().spans);
    let mut msgs = vec!["Ludus panicked!".to_string()];
    let msg = match panic.msg {
        PanicMsg::Generic(ref s) => s,
        _ => &"no match".to_string(),
    };
    msgs.push(msg.clone());
    msgs.push(traceback(&panic));

    msgs.join("\n")
}

fn traceback(panic: &Panic) -> String {
    let mut traceback = vec![];
    for frame in panic.call_stack.iter().rev() {
        traceback.push(frame_info(frame));
    }
    traceback.join("\n")
}

fn frame_info(frame: &CallFrame) -> String {
    let span = frame.chunk().spans[if frame.ip == 0 {
        frame.ip
    } else {
        frame.ip - 1
    }];
    let line_number = line_number(frame.chunk().src, span);
    let line = get_line(frame.chunk().src, line_number);
    let line = line.trim_start();
    let name = frame.function.as_fn().name();
    let input = frame.chunk().input;
    format!(
        "  in {name} on line {} in {input}\n    >>> {line}",
        line_number + 1
    )
}

/////// Some thoughts
// We're putting the information we need on the function and the chunk.
// In the compiler, on functions, build up a vec of strings that are the patterns the function can match against
// The pattern asts have a `show` method.
// And with the additional members on Chunk, we should have everything we need for a pretty fn no match message
// Let no match is no problem, either. We should have no concerns pulling the line with the span start and string
// We don't need to reproduce the pattern, since it will be right there in the code
// As for match forms, we'll just use "no match" and print the value
