use ratatui::{
    style::{Color, Modifier, Style},
    text::{Line, Span},
};

#[cfg(test)]
pub struct AnsiDocument {
    lines: Vec<AnsiLine>,
}

#[cfg(test)]
impl AnsiDocument {
    pub fn parse(text: &str) -> Self {
        Self::from_buffer(TerminalBuffer::from_text(text))
    }

    pub fn from_buffer(buffer: TerminalBuffer) -> Self {
        Self {
            lines: buffer
                .text_lines()
                .iter()
                .map(|line| AnsiLine::parse(line.as_str()))
                .collect(),
        }
    }

    pub fn lines(&self) -> Vec<Line<'static>> {
        self.lines.iter().map(AnsiLine::line).collect()
    }
}

#[derive(Clone)]
pub struct TerminalBuffer {
    lines: Vec<String>,
    widths: Vec<usize>,
    row: usize,
    col: usize,
}

impl TerminalBuffer {
    pub fn new() -> Self {
        Self {
            lines: vec![String::new()],
            widths: vec![0],
            row: 0,
            col: 0,
        }
    }

    #[cfg(test)]
    pub fn from_text(text: &str) -> Self {
        let mut buffer = Self::new();
        buffer.push_text(text);
        buffer
    }

    pub fn push_text(&mut self, text: &str) -> usize {
        let mut chars = text.chars();
        let mut dirty = self.row.min(self.lines.len().saturating_sub(1));

        while let Some(ch) = chars.next() {
            self.consume_char(ch, &mut chars, &mut dirty);
        }

        dirty
    }

    pub fn lines_from(&self, start: usize) -> Vec<Line<'static>> {
        self.text_lines()[start..]
            .iter()
            .map(|line| AnsiLine::parse(line.as_str()).line())
            .collect()
    }

    fn text_lines(&self) -> Vec<String> {
        self.lines.clone()
    }

    fn consume_char(&mut self, ch: char, chars: &mut std::str::Chars<'_>, dirty: &mut usize) {
        match ch {
            '\r' => {
                *dirty = (*dirty).min(self.row);
                self.col = 0;
            }
            '\n' => {
                *dirty = (*dirty).min(self.row);
                self.line_feed();
            }
            '\u{1b}' => self.consume_escape(chars, dirty),
            _ if ch.is_control() => {}
            _ => self.write_char(ch, dirty),
        }
    }

    fn consume_escape(&mut self, chars: &mut std::str::Chars<'_>, dirty: &mut usize) {
        if chars.next() == Some('[') {
            self.consume_csi(chars, dirty);
        }
    }

    fn consume_csi(&mut self, chars: &mut std::str::Chars<'_>, dirty: &mut usize) {
        let mut payload = String::new();

        for ch in chars.by_ref() {
            if Self::is_csi_final(ch) {
                self.apply_csi(&payload, ch, dirty);
                return;
            }
            payload.push(ch);
        }
    }

    fn apply_csi(&mut self, payload: &str, final_char: char, dirty: &mut usize) {
        match final_char {
            'A' => self.move_up(Self::count(payload)),
            'B' => self.move_down(Self::count(payload)),
            'E' => self.next_line(Self::count(payload)),
            'F' => self.previous_line(Self::count(payload)),
            'G' => self.move_column(Self::count(payload)),
            'K' => self.clear_line_from_cursor(dirty),
            'm' => self.write_sgr(payload, dirty),
            _ => {}
        }
    }

    fn is_csi_final(ch: char) -> bool {
        ('@'..='~').contains(&ch)
    }

    fn count(payload: &str) -> usize {
        payload
            .split(';')
            .next()
            .filter(|part| !part.is_empty())
            .and_then(|part| part.parse::<usize>().ok())
            .unwrap_or(1)
    }

    fn move_up(&mut self, count: usize) {
        self.row = self.row.saturating_sub(count);
        self.ensure_row();
    }

    fn move_down(&mut self, count: usize) {
        self.row = self.row.saturating_add(count);
        self.ensure_row();
    }

    fn move_column(&mut self, column: usize) {
        self.col = column.saturating_sub(1);
    }

    fn next_line(&mut self, count: usize) {
        self.row = self.row.saturating_add(count);
        self.col = 0;
        self.ensure_row();
    }

    fn previous_line(&mut self, count: usize) {
        self.row = self.row.saturating_sub(count);
        self.col = 0;
        self.ensure_row();
    }

    fn clear_line_from_cursor(&mut self, dirty: &mut usize) {
        self.ensure_row();
        *dirty = (*dirty).min(self.row);
        if self.col == 0 {
            self.lines[self.row].clear();
            self.widths[self.row] = 0;
            return;
        }
        self.visible_byte_index(self.row, self.col)
            .into_iter()
            .for_each(|index| {
                self.lines[self.row].truncate(index);
                self.widths[self.row] = self.col;
            });
    }

    fn write_sgr(&mut self, payload: &str, dirty: &mut usize) {
        self.ensure_row();
        *dirty = (*dirty).min(self.row);
        self.lines[self.row].push('\u{1b}');
        self.lines[self.row].push('[');
        self.lines[self.row].push_str(payload);
        self.lines[self.row].push('m');
    }

    fn write_char(&mut self, ch: char, dirty: &mut usize) {
        self.ensure_row();
        *dirty = (*dirty).min(self.row);
        if self.col == self.widths[self.row] {
            self.lines[self.row].push(ch);
            self.widths[self.row] += 1;
            self.col += 1;
            return;
        }
        self.ensure_col();
        self.replace_visible_char(self.row, self.col, ch);
        self.col += 1;
    }

    fn ensure_row(&mut self) {
        while self.lines.len() <= self.row {
            self.lines.push(String::new());
            self.widths.push(0);
        }
    }

    fn ensure_col(&mut self) {
        while self.widths[self.row] <= self.col {
            self.lines[self.row].push(' ');
            self.widths[self.row] += 1;
        }
    }

    fn line_feed(&mut self) {
        self.row += 1;
        self.col = 0;
        self.ensure_row();
    }

    fn visible_len(&self, row: usize) -> usize {
        self.widths[row]
    }

    fn visible_byte_index(&self, row: usize, col: usize) -> Option<usize> {
        self.visible_ranges(row)
            .get(col)
            .map(|(start, _)| *start)
            .or_else(|| (col == self.visible_len(row)).then_some(self.lines[row].len()))
    }

    fn replace_visible_char(&mut self, row: usize, col: usize, ch: char) {
        self.visible_ranges(row)
            .get(col)
            .cloned()
            .into_iter()
            .for_each(|(start, end)| self.lines[row].replace_range(start..end, &ch.to_string()));
    }

    fn visible_ranges(&self, row: usize) -> Vec<(usize, usize)> {
        let line = &self.lines[row];
        let mut ranges = Vec::new();
        let mut chars = line.char_indices().peekable();

        while let Some((start, ch)) = chars.next() {
            if ch == '\u{1b}' {
                self.skip_csi(&mut chars);
                continue;
            }

            let end = chars.peek().map(|(index, _)| *index).unwrap_or(line.len());
            ranges.push((start, end));
        }

        ranges
    }

    fn skip_csi(&self, chars: &mut std::iter::Peekable<std::str::CharIndices<'_>>) {
        for (_, marker) in chars.by_ref() {
            if marker == '[' {
                break;
            }
        }
        for (_, marker) in chars.by_ref() {
            if Self::is_csi_final(marker) {
                break;
            }
        }
    }
}

struct AnsiLine {
    spans: Vec<AnsiSpan>,
}

impl AnsiLine {
    fn parse(text: &str) -> Self {
        Self {
            spans: AnsiParser::new(text).parse(),
        }
    }

    fn line(&self) -> Line<'static> {
        Line::from(self.spans.iter().map(AnsiSpan::span).collect::<Vec<_>>())
    }
}

struct AnsiSpan {
    text: String,
    style: Style,
}

impl AnsiSpan {
    fn new(text: String, style: Style) -> Self {
        Self { text, style }
    }

    fn span(&self) -> Span<'static> {
        Span::styled(self.text.clone(), self.style)
    }
}

struct AnsiParser<'a> {
    chars: std::str::Chars<'a>,
    state: AnsiState,
    spans: Vec<AnsiSpan>,
    current: String,
}

impl<'a> AnsiParser<'a> {
    fn new(text: &'a str) -> Self {
        Self {
            chars: text.chars(),
            state: AnsiState::default(),
            spans: Vec::new(),
            current: String::new(),
        }
    }

    fn parse(mut self) -> Vec<AnsiSpan> {
        while let Some(ch) = self.chars.next() {
            self.consume_char(ch);
        }
        self.flush_current();
        self.spans
    }

    fn consume_char(&mut self, ch: char) {
        if ch == '\u{1b}' {
            self.consume_escape();
            return;
        }
        if !ch.is_control() {
            self.current.push(ch);
        }
    }

    fn consume_escape(&mut self) {
        self.flush_current();
        self.chars
            .next()
            .filter(|marker| *marker == '[')
            .into_iter()
            .for_each(|_| self.consume_csi());
    }

    fn consume_csi(&mut self) {
        let mut payload = String::new();

        while let Some(ch) = self.chars.next() {
            if TerminalBuffer::is_csi_final(ch) {
                self.apply_csi(&payload, ch);
                return;
            }
            payload.push(ch);
        }
    }

    fn apply_csi(&mut self, payload: &str, final_char: char) {
        if final_char == 'm' {
            self.state = self.state.apply(
                &payload
                    .split(';')
                    .filter(|code| !code.is_empty())
                    .map(|code| code.parse::<u16>().unwrap_or(0))
                    .collect::<Vec<_>>(),
            );
        }
    }

    fn flush_current(&mut self) {
        (!self.current.is_empty())
            .then_some(AnsiSpan::new(
                std::mem::take(&mut self.current),
                self.state.style(),
            ))
            .into_iter()
            .for_each(|span| self.spans.push(span));
    }
}

#[derive(Clone, Copy, Default)]
struct AnsiState {
    fg: Option<Color>,
    bold: bool,
}

impl AnsiState {
    fn apply(&self, codes: &[u16]) -> Self {
        codes
            .iter()
            .fold(*self, |state, code| state.apply_code(*code))
    }

    fn apply_code(&self, code: u16) -> Self {
        match code {
            0 => Self::default(),
            1 => Self {
                bold: true,
                ..*self
            },
            22 => Self {
                bold: false,
                ..*self
            },
            30 => Self {
                fg: Some(Color::Black),
                ..*self
            },
            31 => Self {
                fg: Some(Color::Red),
                ..*self
            },
            32 => Self {
                fg: Some(Color::Green),
                ..*self
            },
            33 => Self {
                fg: Some(Color::Yellow),
                ..*self
            },
            34 => Self {
                fg: Some(Color::Blue),
                ..*self
            },
            35 => Self {
                fg: Some(Color::Magenta),
                ..*self
            },
            36 => Self {
                fg: Some(Color::Cyan),
                ..*self
            },
            37 => Self {
                fg: Some(Color::Gray),
                ..*self
            },
            39 => Self { fg: None, ..*self },
            90 => Self {
                fg: Some(Color::DarkGray),
                ..*self
            },
            91 => Self {
                fg: Some(Color::LightRed),
                ..*self
            },
            92 => Self {
                fg: Some(Color::LightGreen),
                ..*self
            },
            93 => Self {
                fg: Some(Color::LightYellow),
                ..*self
            },
            94 => Self {
                fg: Some(Color::LightBlue),
                ..*self
            },
            95 => Self {
                fg: Some(Color::LightMagenta),
                ..*self
            },
            96 => Self {
                fg: Some(Color::LightCyan),
                ..*self
            },
            97 => Self {
                fg: Some(Color::White),
                ..*self
            },
            _ => *self,
        }
    }

    fn style(&self) -> Style {
        self.fg
            .map(|fg| Style::default().fg(fg))
            .unwrap_or_default()
            .add_modifier(if self.bold {
                Modifier::BOLD
            } else {
                Modifier::empty()
            })
    }
}
