// rasm-core/src/error.rs
//
//! Вывод ошибок RASM.
use crate::{
    analysis::{
        ast_analysis::err::{ASTAnalyseErrorKind, ASTAnalyseWarningKind},
        err::{AnalyseError, AnalyseErrorKind},
    },
    codegen::err::CodegenError,
    isa::err::ISAError,
    isa::{GPRs, Instruction, MnemonicKind, Operand, Registers, SegmentRegisters, XMMRs},
    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),
    Analyse(AnalyseError),
}

// ========== Форматирование инструкций для красивого вывода диагностик ==========
//
// У `Instruction`, `Operand` и `Registers` нет своего `Display`, поэтому для
// вывода человекочитаемого варианта инструкции в варнингах (например,
// `mov rax, 5`) собираем строку вручную.

fn fmt_gpr(reg: GPRs) -> &'static str {
    match reg {
        GPRs::Rax => "rax",
        GPRs::Rcx => "rcx",
        GPRs::Rdx => "rdx",
        GPRs::Rbx => "rbx",
        GPRs::Rsp => "rsp",
        GPRs::Rbp => "rbp",
        GPRs::Rsi => "rsi",
        GPRs::Rdi => "rdi",
        GPRs::R8 => "r8",
        GPRs::R9 => "r9",
        GPRs::R10 => "r10",
        GPRs::R11 => "r11",
        GPRs::R12 => "r12",
        GPRs::R13 => "r13",
        GPRs::R14 => "r14",
        GPRs::R15 => "r15",
    }
}

fn fmt_xmm(reg: XMMRs) -> &'static str {
    match reg {
        XMMRs::Xmm0 => "xmm0",
        XMMRs::Xmm1 => "xmm1",
        XMMRs::Xmm2 => "xmm2",
        XMMRs::Xmm3 => "xmm3",
        XMMRs::Xmm4 => "xmm4",
        XMMRs::Xmm5 => "xmm5",
        XMMRs::Xmm6 => "xmm6",
        XMMRs::Xmm7 => "xmm7",
        XMMRs::Xmm8 => "xmm8",
        XMMRs::Xmm9 => "xmm9",
        XMMRs::Xmm10 => "xmm10",
        XMMRs::Xmm11 => "xmm11",
        XMMRs::Xmm12 => "xmm12",
        XMMRs::Xmm13 => "xmm13",
        XMMRs::Xmm14 => "xmm14",
        XMMRs::Xmm15 => "xmm15",
    }
}

fn fmt_segment(reg: SegmentRegisters) -> &'static str {
    match reg {
        SegmentRegisters::Fs => "fs",
        SegmentRegisters::Gs => "gs",
        SegmentRegisters::Cs => "cs",
        SegmentRegisters::Ss => "ss",
        SegmentRegisters::Ds => "ds",
        SegmentRegisters::Es => "es",
    }
}

fn fmt_register(reg: Registers) -> String {
    match reg {
        Registers::GPR(gpr) => fmt_gpr(gpr).to_string(),
        Registers::XMM(xmm) => fmt_xmm(xmm).to_string(),
        Registers::SR(sr) => fmt_segment(sr).to_string(),
        Registers::Rflags => "rflags".to_string(),
        Registers::Rip => "rip".to_string(),
    }
}

fn fmt_operand(operand: &Operand) -> String {
    match operand {
        Operand::Register(reg) => fmt_register(*reg),
        Operand::Immediate(imm) => imm.to_string(),
        // Полная адресация (база + индекс*scale + displacement) здесь не нужна
        // для краткого превью инструкции в варнинге — достаточно пометить как память.
        Operand::Memory(_) => "[...]".to_string(),
        Operand::Label(_) => "<label>".to_string(),
    }
}

fn fmt_mnemonic(mnemonic: MnemonicKind) -> &'static str {
    match mnemonic {
        MnemonicKind::Mov => "mov",
        MnemonicKind::Push => "push",
        MnemonicKind::Pop => "pop",
        MnemonicKind::Add => "add",
        MnemonicKind::Sub => "sub",
        MnemonicKind::Mul => "mul",
        MnemonicKind::Imul => "imul",
        MnemonicKind::Div => "div",
        MnemonicKind::Idiv => "idiv",
        MnemonicKind::Xor => "xor",
        MnemonicKind::And => "and",
        MnemonicKind::Or => "or",
        MnemonicKind::Test => "test",
        MnemonicKind::Lea => "lea",
        MnemonicKind::Inc => "inc",
        MnemonicKind::Dec => "dec",
        MnemonicKind::Neg => "neg",
        MnemonicKind::Not => "not",
        MnemonicKind::Shl => "shl",
        MnemonicKind::Shr => "shr",
        MnemonicKind::Sar => "sar",
        MnemonicKind::Cqo => "cqo",
        MnemonicKind::Cmp => "cmp",
        MnemonicKind::Jmp => "jmp",
        MnemonicKind::Je => "je",
        MnemonicKind::Jne => "jne",
        MnemonicKind::Jg => "jg",
        MnemonicKind::Jge => "jge",
        MnemonicKind::Jl => "jl",
        MnemonicKind::Jle => "jle",
        MnemonicKind::Ja => "ja",
        MnemonicKind::Jae => "jae",
        MnemonicKind::Jb => "jb",
        MnemonicKind::Jbe => "jbe",
        MnemonicKind::Jo => "jo",
        MnemonicKind::Jno => "jno",
        MnemonicKind::Js => "js",
        MnemonicKind::Jns => "jns",
        MnemonicKind::Jp => "jp",
        MnemonicKind::Jnp => "jnp",
        MnemonicKind::Call => "call",
        MnemonicKind::Ret => "ret",
        MnemonicKind::Syscall => "syscall",
        MnemonicKind::Nop => "nop",
    }
}

/// Собирает человекочитаемое текстовое представление инструкции, например `mov rax, 5`.
fn fmt_instruction(instruction: &Instruction) -> String {
    if instruction.operands.is_empty() {
        return fmt_mnemonic(instruction.mnemonic).to_string();
    }

    let operands: Vec<String> = instruction.operands.iter().map(fmt_operand).collect();
    format!(
        "{} {}",
        fmt_mnemonic(instruction.mnemonic),
        operands.join(", ")
    )
}

/// Возвращает текстовое название регистра-приёмника инструкции, если первый
/// операнд — регистр (для сообщения вида "assigning a value to rax").
fn destination_register_name(instruction: &Instruction) -> Option<String> {
    match instruction.operands.first() {
        Some(Operand::Register(reg)) => Some(fmt_register(*reg)),
        _ => None,
    }
}

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

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

/// Достаёт из исходного кода строку по её номеру (нумерация с 1), очищенную
/// от символов переноса строки. Возвращает `None`, если строка вне диапазона.
fn extract_line(src: &SourceCode, line_number: u32) -> Option<&str> {
    let line_idx = (line_number.checked_sub(1)?) as usize;

    if line_idx >= src.line_starts.len() {
        return None;
    }

    let start_byte = src.line_starts[line_idx] as usize;
    let end_byte = if line_idx + 1 < src.line_starts.len() {
        src.line_starts[line_idx + 1] as usize
    } else {
        src.source.len()
    };

    let mut line_str = &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];
    }

    Some(line_str)
}

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) {
        match &self.kind {
            ErrorKind::Analyse(err) => match &err.kind {
                AnalyseErrorKind::ASTAnalyse(ast_analyse_err) => match &ast_analyse_err.kind {
                    // Находки уровня ошибки (не стилистика) — выводятся как Error.
                    ASTAnalyseErrorKind::Error(_) => self.report_generic(filename, false),

                    ASTAnalyseErrorKind::Warning(ast_analyse_warning) => {
                        match ast_analyse_warning {
                            ASTAnalyseWarningKind::UnneededInstruction {
                                unneeded,
                                unneeded_pos,
                                useful,
                                useful_pos,
                            } => {
                                let headline = ast_analyse_warning.to_string();
                                let second_label = match destination_register_name(useful) {
                                    Some(register) => format!("assigning a value to {register}"),
                                    None => format!(
                                        "this instruction makes `{}` unnecessary",
                                        fmt_instruction(unneeded)
                                    ),
                                };

                                self.report_two_line_diagnostic(
                                    filename,
                                    &headline,
                                    *unneeded_pos,
                                    "unnecessary line",
                                    *useful_pos,
                                    &second_label,
                                );
                            }
                            ASTAnalyseWarningKind::DeadCodeAfterTerminator {
                                terminator_pos,
                                dead_pos,
                                ..
                            } => {
                                let headline = ast_analyse_warning.to_string();
                                self.report_two_line_diagnostic(
                                    filename,
                                    &headline,
                                    *terminator_pos,
                                    "control flow never continues past this point",
                                    *dead_pos,
                                    "unreachable",
                                );
                            }
                            // Для остальных варнингов вывод как обычно (общая графика ниже).
                            ASTAnalyseWarningKind::LabelNameIsTooLong(_)
                            | ASTAnalyseWarningKind::SelfLoop { .. }
                            | ASTAnalyseWarningKind::UnusedComparison(_)
                            | ASTAnalyseWarningKind::RedundantSelfAssign(_)
                            | ASTAnalyseWarningKind::NoOpArithmetic(_)
                            | ASTAnalyseWarningKind::UnbalancedStack { .. } => {
                                self.report_generic(filename, true);
                            }
                        }
                    }
                },
                // Находки построения AIR (граф потока управления) — переход
                // на неизвестную метку и т.п. — все на сегодня уровня ошибки.
                AnalyseErrorKind::AIRAnalyse(_) => self.report_generic(filename, false),
            },
            _ => self.report_generic(filename, false),
        }
    }

    /// Вывод диагностики, ссылающейся на две позиции в коде: первая
    /// подсвечивается жёлтым с подписью `first_label`, вторая — синим с
    /// подписью `second_label`. Используется и для "ненужной инструкции"
    /// (она + та, что её перезаписывает), и для "недостижимого кода"
    /// (терминатор + первая недостижимая инструкция после него).
    ///
    /// ## Пример вывода
    /// ```text
    /// Warning: unnecessary instruction.
    /// {filename} -> {start_line}:{start_col}..{end_line}:{end_col}
    ///  |
    /// 2 | mov rax, 1
    ///  |    ^^^^^^^^ unnecessary line
    /// 3 | mov rax, 0
    ///  |    ^^^^^^^^ assigning a value to rax
    ///  |
    /// ```
    fn report_two_line_diagnostic(
        &self,
        filename: &str,
        headline: &str,
        first_pos: Span,
        first_label: &str,
        second_pos: Span,
        second_label: &str,
    ) {
        let (first_start_line, first_start_col) = self.src.lookup_coordinates(first_pos.start);
        let (first_end_line, first_end_col) = self.src.lookup_coordinates(first_pos.end);

        let (second_start_line, second_start_col) = self.src.lookup_coordinates(second_pos.start);
        let (second_end_line, second_end_col) = self.src.lookup_coordinates(second_pos.end);

        // 1. Заголовок: жёлтый "Warning" и текст сообщения
        println!("\x1b[1;33mWarning\x1b[0m: \x1b[1m{headline}\x1b[0m.");

        println!(
            "\x1b[1;36m{filename} -> {first_start_line}:{first_start_col}..{second_end_line}:{second_end_col}\x1b[0m"
        );

        // Ширина левого поля рассчитывается по самому длинному номеру строки
        // (т.к. показываем сразу две строки кода).
        let widest_line_num = second_end_line.max(first_end_line).to_string().len();
        let margin = " ".repeat(widest_line_num);

        println!("\x1b[1;34m{margin} |\x1b[0m");

        // 2. Первая строка, подсвечена жёлтым
        if let Some(first_line_str) = extract_line(self.src, first_start_line) {
            let first_line_num_str = first_start_line.to_string();
            let pad = " ".repeat(widest_line_num.saturating_sub(first_line_num_str.len()));

            println!("\x1b[1;34m{pad}{first_line_num_str} |\x1b[0m {first_line_str}");

            let spaces = " ".repeat((first_start_col - 1) as usize);
            let underline_len = if first_start_line == first_end_line {
                first_end_col.saturating_sub(first_start_col) as usize
            } else {
                first_line_str
                    .len()
                    .saturating_sub((first_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;33m{arrows} {first_label}\x1b[0m");
        }

        // 3. Вторая строка, подсвечена синим
        if let Some(second_line_str) = extract_line(self.src, second_start_line) {
            let second_line_num_str = second_start_line.to_string();
            let pad = " ".repeat(widest_line_num.saturating_sub(second_line_num_str.len()));

            println!("\x1b[1;34m{pad}{second_line_num_str} |\x1b[0m {second_line_str}");

            let spaces = " ".repeat((second_start_col - 1) as usize);
            let underline_len = if second_start_line == second_end_line {
                second_end_col.saturating_sub(second_start_col) as usize
            } else {
                second_line_str
                    .len()
                    .saturating_sub((second_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;34m{arrows}\x1b[0m {second_label}");
        }

        println!("\x1b[1;34m{margin} |\x1b[0m");
    }

    /// Общий вывод диагностики (ошибки или варнинга без специальной графики):
    /// одна строка кода с подсветкой указанного диапазона `self.position`.
    fn report_generic(&self, filename: &str, is_warning: bool) {
        // Получаем точные координаты начала и конца ошибки
        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. Первая линия: заголовок и жирный текст сообщения
        if is_warning {
            println!("\x1b[1;33mWarning\x1b[0m: \x1b[1m{}\x1b[0m.", self.kind);
        } else {
            println!("\x1b[1;31mError\x1b[0m: \x1b[1m{}\x1b[0m.", self.kind);
        }

        println!("\x1b[1;36m{filename} -> {start_line}:{start_col}..{end_line}:{end_col}\x1b[0m");

        // 3. Графика в стиле rustc (извлечение и подсветка строки кода)
        let Some(line_str) = extract_line(self.src, start_line) else {
            return;
        };

        // Вычисляем ширину левого поля (боковой панели) на основе номера строки
        let line_num_str = start_line.to_string();
        let margin = " ".repeat(line_num_str.len());
        let highlight_color = if is_warning {
            "\x1b[1;33m"
        } else {
            "\x1b[1;31m"
        };

        // Синие разделители '|', как в оригинальном 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.saturating_sub(start_col) as usize
        } else {
            line_str.len().saturating_sub((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}{highlight_color}{arrows}\x1b[0m {highlight_color}{}\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)?;

        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(())
    }
}
