// rasm-core/src/error.rs
//
//! Вывод ошибок RASM.
use crate::{
    codegen::err::CodegenError,
    isa::err::ISAError,
    lexer::{err::LexerError, token::Span},
    parser::err::ParserError,
    src::SourceCode,
};
use std::fmt::Display;

#[derive(Debug)]
pub enum ErrorKind {
    ISA(ISAError),
    Lexer(LexerError),
    Parser(ParserError),
    Codegen(CodegenError),
}

impl Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            ErrorKind::ISA(err) => write!(f, "{err}"),
            ErrorKind::Lexer(err) => write!(f, "{err}"),
            ErrorKind::Parser(err) => write!(f, "{err}"),
            ErrorKind::Codegen(err) => write!(f, "{err}"),
        }
    }
}

#[derive(Debug)]
pub struct Error<'a> {
    pub kind: ErrorKind,
    pub position: Span,
    pub src: &'a SourceCode,
}

impl<'a> Error<'a> {
    pub fn new(kind: ErrorKind, position: Span, src: &'a SourceCode) -> Self {
        Self {
            kind,
            position,
            src,
        }
    }

    /// Выводит красивое сообщение об ошибке с подсветкой синтаксиса и указателями
    pub fn report(&self, filename: &str) {
        // Получаем точные координаты начала и конца ошибки
        let (start_line, start_col) = self.src.lookup_coordinates(self.position.start);
        let (end_line, end_col) = self.src.lookup_coordinates(self.position.end);

        // 1. Первая линия: Красный заголовок "Error" и жирный текст сообщения
        println!("\x1b[1;31mError\x1b[0m: \x1b[1m{}\x1b[0m.", self.kind);

        // 2. Вторая линия: Бирюзовый указатель на файл и точный диапазон позиций
        println!("\x1b[1;36m{filename} -> {start_line}:{start_col}..{end_line}:{end_col}\x1b[0m");

        // 3. Графика в стиле rustc (извлечение и подсветка строки кода)
        let line_idx = (start_line - 1) as usize;
        if line_idx < self.src.line_starts.len() {
            // Находим границы строки в байтах
            let start_byte = self.src.line_starts[line_idx] as usize;
            let end_byte = if line_idx + 1 < self.src.line_starts.len() {
                self.src.line_starts[line_idx + 1] as usize
            } else {
                self.src.source.len()
            };

            // Вырезаем строку и очищаем её от символов переноса
            let mut line_str = &self.src.source[start_byte..end_byte];
            if line_str.ends_with('\n') {
                line_str = &line_str[..line_str.len() - 1];
            }
            if line_str.ends_with('\r') {
                line_str = &line_str[..line_str.len() - 1];
            }

            // Вычисляем ширину левого поля (боковой панели) на основе номера строки
            let line_num_str = start_line.to_string();
            let margin = " ".repeat(line_num_str.len());

            // Синие разделители '|', как в оригинальном rustc
            println!("\x1b[1;34m{margin} |\x1b[0m");
            println!("\x1b[1;34m{line_num_str} |\x1b[0m {line_str}");

            // Считаем отступ до ошибочного токена и количество стрелочек ^
            let spaces = " ".repeat((start_col - 1) as usize);
            let underline_len = if start_line == end_line {
                (end_col - start_col) as usize
            } else {
                // Если токен почему-то перенёсся на другую строку, подчеркиваем до конца текущей
                line_str.len() - (start_col - 1) as usize
            };

            // Защита: всегда рисуем хотя бы одну стрелочку
            let underline_len = std::cmp::max(1, underline_len);
            let arrows = "^".repeat(underline_len);

            // Отрисовываем стрелочки под кодом и дублируем текст ошибки в конце
            println!(
                "\x1b[1;34m{margin} |\x1b[0m {spaces}\x1b[1;31m{arrows}\x1b[0m \x1b[1;31m{}\x1b[0m",
                self.kind
            );
            println!("\x1b[1;34m{margin} |\x1b[0m");
        }
    }
}

impl<'a> Display for Error<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        // Получаем точные координаты начала и конца ошибки
        let (start_line, start_col) = self.src.lookup_coordinates(self.position.start);
        let (end_line, end_col) = self.src.lookup_coordinates(self.position.end);

        // 1. Первая линия: Красный заголовок "Error" и жирный текст сообщения
        writeln!(f, "\x1b[1;31mError\x1b[0m: \x1b[1m{}\x1b[0m.", self.kind)?;

        // 2. Вторая линия: Бирюзовый указатель на файл и точный диапазон позиций
        writeln!(
            f,
            "\x1b[1;36mno_file -> {start_line}:{start_col}..{end_line}:{end_col}\x1b[0m"
        )?;

        // 3. Графика в стиле rustc
        let line_idx = (start_line - 1) as usize;
        if line_idx < self.src.line_starts.len() {
            // Находим границы строки в байтах
            let start_byte = self.src.line_starts[line_idx] as usize;
            let end_byte = if line_idx + 1 < self.src.line_starts.len() {
                self.src.line_starts[line_idx + 1] as usize
            } else {
                self.src.source.len()
            };

            let mut line_str = &self.src.source[start_byte..end_byte];
            if line_str.ends_with('\n') {
                line_str = &line_str[..line_str.len() - 1];
            }
            if line_str.ends_with('\r') {
                line_str = &line_str[..line_str.len() - 1];
            }

            let line_num_str = start_line.to_string();
            let margin = " ".repeat(line_num_str.len());

            writeln!(f, "\x1b[1;34m{margin} |\x1b[0m")?;
            writeln!(f, "\x1b[1;34m{line_num_str} |\x1b[0m {line_str}")?;

            let spaces = " ".repeat((start_col - 1) as usize);
            let underline_len = if start_line == end_line {
                (end_col - start_col) as usize
            } else {
                line_str.len() - (start_col - 1) as usize
            };

            let underline_len = std::cmp::max(1, underline_len);
            let arrows = "^".repeat(underline_len);

            writeln!(
                f,
                "\x1b[1;34m{margin} |\x1b[0m {spaces}\x1b[1;31m{arrows}\x1b[0m \x1b[1;31m{}\x1b[0m",
                self.kind
            )?;
            write!(f, "\x1b[1;34m{margin} |\x1b[0m")?;
        }

        Ok(())
    }
}
