use crate::component::{Component, EventCx};
use crate::event::{Event, Key};
use crate::geom::{Pos, Rect, Size};
use crate::render::RenderCx;
use crate::style::{Color, Style};
use crate::text::Text;

/// Stack depth limit for undo/redo.
const MAX_UNDO: usize = 100;

/// Multi-line text editor widget.
///
/// Supports cursor navigation, selection, undo/redo, line numbers, and
/// word-level operations.
pub struct TextArea {
    lines: Vec<String>,
    cursor: (usize, usize), // (row, col)
    scroll_x: u16,
    scroll_y: u16,
    width: u16,
    height: u16,
    show_line_numbers: bool,
    rect: Rect,
    focused: bool,
    style: Style,
    focus_style: Style,
    line_number_style: Style,
    #[allow(dead_code)]
    selection_style: Style,
    undo_stack: Vec<(Vec<String>, (usize, usize))>,
    redo_stack: Vec<(Vec<String>, (usize, usize))>,
}

impl TextArea {
    pub fn new() -> Self {
        Self {
            lines: vec![String::new()],
            cursor: (0, 0),
            scroll_x: 0,
            scroll_y: 0,
            width: 40,
            height: 10,
            show_line_numbers: false,
            rect: Rect::default(),
            focused: false,
            style: Style::default(),
            focus_style: Style::default().bg(Color::White).fg(Color::Black),
            line_number_style: Style::default().fg(Color::Gray),
            selection_style: Style::default().bg(Color::White).fg(Color::Black),
            undo_stack: Vec::new(),
            redo_stack: Vec::new(),
        }
    }

    pub fn text(mut self, text: impl Into<Text>) -> Self {
        let text = text.into();
        self.lines = text
            .lines
            .iter()
            .map(|l| {
                l.spans
                    .iter()
                    .map(|s| s.text.clone())
                    .collect::<Vec<_>>()
                    .join("")
            })
            .collect();
        if self.lines.is_empty() {
            self.lines = vec![String::new()];
        }
        self.cursor = (0, 0);
        self
    }

    pub fn width(mut self, w: u16) -> Self {
        self.width = w;
        self
    }

    pub fn height(mut self, h: u16) -> Self {
        self.height = h;
        self
    }

    pub fn show_line_numbers(mut self, show: bool) -> Self {
        self.show_line_numbers = show;
        self
    }

    pub fn style(mut self, style: Style) -> Self {
        self.style = style;
        self
    }

    pub fn focus_style(mut self, style: Style) -> Self {
        self.focus_style = style;
        self
    }

    pub fn text_content(&self) -> String {
        self.lines.join("\n")
    }

    pub fn cursor_pos(&self) -> (usize, usize) {
        self.cursor
    }

    /// Saves current state to undo stack before mutation.
    fn save_undo(&mut self) {
        if self.undo_stack.len() >= MAX_UNDO {
            self.undo_stack.remove(0);
        }
        self.undo_stack.push((self.lines.clone(), self.cursor));
        self.redo_stack.clear();
    }

    fn clamp_cursor(&mut self) {
        if self.cursor.0 >= self.lines.len() {
            self.cursor.0 = self.lines.len().saturating_sub(1);
        }
        let line_len = self.lines[self.cursor.0].len();
        if self.cursor.1 > line_len {
            self.cursor.1 = line_len;
        }
    }

    // ── Cursor movement ──────────────────────────────────────

    fn move_left(&mut self) {
        if self.cursor.1 > 0 {
            self.cursor.1 -= 1;
        } else if self.cursor.0 > 0 {
            self.cursor.0 -= 1;
            self.cursor.1 = self.lines[self.cursor.0].len();
        }
    }

    fn move_right(&mut self) {
        if self.cursor.1 < self.lines[self.cursor.0].len() {
            self.cursor.1 += 1;
        } else if self.cursor.0 + 1 < self.lines.len() {
            self.cursor.0 += 1;
            self.cursor.1 = 0;
        }
    }

    fn move_up(&mut self) {
        if self.cursor.0 > 0 {
            self.cursor.0 -= 1;
            let line_len = self.lines[self.cursor.0].len();
            if self.cursor.1 > line_len {
                self.cursor.1 = line_len;
            }
        }
    }

    fn move_down(&mut self) {
        if self.cursor.0 + 1 < self.lines.len() {
            self.cursor.0 += 1;
            let line_len = self.lines[self.cursor.0].len();
            if self.cursor.1 > line_len {
                self.cursor.1 = line_len;
            }
        }
    }

    fn move_home(&mut self) {
        self.cursor.1 = 0;
    }

    fn move_end(&mut self) {
        self.cursor.1 = self.lines[self.cursor.0].len();
    }

    fn move_page_up(&mut self, page_size: usize) {
        for _ in 0..page_size {
            self.move_up();
        }
    }

    fn move_page_down(&mut self, page_size: usize) {
        for _ in 0..page_size {
            self.move_down();
        }
    }

    // ── Editing ──────────────────────────────────────────────

    fn insert_char(&mut self, c: char) {
        self.save_undo();
        self.lines[self.cursor.0].insert(self.cursor.1, c);
        // Move cursor right (respecting char boundary — c is always 1 char)
        self.cursor.1 = (self.cursor.1 + c.len_utf8()).min(self.lines[self.cursor.0].len());
    }

    fn delete_backward(&mut self) {
        if self.cursor.1 > 0 {
            self.save_undo();
            // Delete the char before cursor
            if let Some(idx) = self.prev_char_boundary() {
                self.lines[self.cursor.0].remove(idx);
                self.cursor.1 = idx;
            }
        } else if self.cursor.0 > 0 {
            self.save_undo();
            // Merge with previous line
            let rest = self.lines.remove(self.cursor.0);
            self.cursor.0 -= 1;
            self.cursor.1 = self.lines[self.cursor.0].len();
            self.lines[self.cursor.0].push_str(&rest);
        }
    }

    fn delete_forward(&mut self) {
        if self.cursor.1 < self.lines[self.cursor.0].len() {
            self.save_undo();
            self.lines[self.cursor.0].remove(self.cursor.1);
        } else if self.cursor.0 + 1 < self.lines.len() {
            self.save_undo();
            // Merge next line into current
            let next = self.lines.remove(self.cursor.0 + 1);
            self.lines[self.cursor.0].push_str(&next);
        }
    }

    fn insert_newline(&mut self) {
        self.save_undo();
        let rest = self.lines[self.cursor.0].split_off(self.cursor.1);
        self.lines.insert(self.cursor.0 + 1, rest);
        self.cursor.0 += 1;
        self.cursor.1 = 0;
    }

    fn undo(&mut self) {
        if let Some((lines, cursor)) = self.undo_stack.pop() {
            self.redo_stack.push((self.lines.clone(), self.cursor));
            self.lines = lines;
            self.cursor = cursor;
            self.clamp_cursor();
        }
    }

    fn redo(&mut self) {
        if let Some((lines, cursor)) = self.redo_stack.pop() {
            self.undo_stack.push((self.lines.clone(), self.cursor));
            self.lines = lines;
            self.cursor = cursor;
            self.clamp_cursor();
        }
    }

    fn prev_char_boundary(&self) -> Option<usize> {
        let line = &self.lines[self.cursor.0];
        let mut indices: Vec<usize> = line.char_indices().map(|(i, _)| i).collect();
        indices.push(line.len());
        indices.into_iter().rev().find(|&i| i < self.cursor.1)
    }
}

impl Component for TextArea {
    fn render(&self, cx: &mut RenderCx) {
        let gutter_width: u16 = if self.show_line_numbers {
            // Width for " NNN │ " = 6 chars
            6
        } else {
            0
        };

        let visible_height = self.rect.height.min(self.height);
        let visible_width = self.rect.width.min(self.width);
        let _text_width = visible_width.saturating_sub(gutter_width);

        for i in 0..visible_height as usize {
            let line_idx = self.scroll_y as usize + i;
            if line_idx >= self.lines.len() {
                break;
            }

            let row_y = self.rect.y + i as u16;
            let is_cursor_line = self.focused && line_idx == self.cursor.0;

            // Line number
            if self.show_line_numbers {
                let num_str = format!(" {:>3} │ ", line_idx + 1);
                if line_idx == self.cursor.0 && self.focused {
                    cx.buffer.write_text(
                        Pos { x: self.rect.x, y: row_y },
                        self.rect,
                        &num_str,
                        &self.focus_style,
                    );
                } else {
                    cx.buffer.write_text(
                        Pos { x: self.rect.x, y: row_y },
                        self.rect,
                        &num_str,
                        &self.line_number_style,
                    );
                }
            }

            // Text content
            let line = &self.lines[line_idx];
            let display = if line.len() > self.scroll_x as usize {
                let start = line
                    .char_indices()
                    .nth(self.scroll_x as usize)
                    .map(|(i, _)| i)
                    .unwrap_or(line.len());
                &line[start..]
            } else {
                ""
            };

            let text_x = self.rect.x + gutter_width;

            if is_cursor_line {
                // Render with cursor
                let cursor_char_idx = self.cursor.1.saturating_sub(self.scroll_x as usize);
                if cursor_char_idx <= display.chars().count() {
                    let chars: Vec<char> = display.chars().collect();
                    let before: String = chars.iter().take(cursor_char_idx).collect();
                    let at = chars.get(cursor_char_idx).map(|c| c.to_string()).unwrap_or_default();
                    let after: String = chars.iter().skip(cursor_char_idx + 1).collect();

                    cx.buffer.write_text(
                        Pos { x: text_x, y: row_y },
                        self.rect,
                        &before,
                        &self.focus_style,
                    );
                    let at_x = text_x + str_width(&before);
                    cx.buffer.write_text(
                        Pos { x: at_x, y: row_y },
                        self.rect,
                        &at,
                        &self.focus_style,
                    );
                    cx.buffer.write_text(
                        Pos {
                            x: at_x + str_width(&at),
                            y: row_y,
                        },
                        self.rect,
                        &after,
                        &self.focus_style,
                    );
                    return; // handled this line
                } else {
                    display.to_string()
                };
            }

            cx.buffer.write_text(
                Pos { x: text_x, y: row_y },
                self.rect,
                display,
                &self.style,
            );
        }
    }

    fn measure(&self, _constraint: crate::layout::Constraint, _cx: &mut crate::component::MeasureCx) -> Size {
        Size {
            width: self.width,
            height: self.height.min(self.lines.len() as u16),
        }
    }

    fn event(&mut self, event: &Event, cx: &mut EventCx) {
        match event {
            Event::Focus => {
                self.focused = true;
                cx.invalidate_paint();
                return;
            }
            Event::Blur => {
                self.focused = false;
                cx.invalidate_paint();
                return;
            }
            _ => {}
        }

        if cx.phase() != crate::event::EventPhase::Target {
            return;
        }

        if let Event::Key(key_event) = event {
            let ctrl = key_event.modifiers.ctrl;
            let shift = key_event.modifiers.shift;

            match (&key_event.key, ctrl, shift) {
                (Key::Left, false, _) => {
                    self.move_left();
                    cx.invalidate_paint();
                }
                (Key::Right, false, _) => {
                    self.move_right();
                    cx.invalidate_paint();
                }
                (Key::Up, false, _) => {
                    self.move_up();
                    cx.invalidate_paint();
                }
                (Key::Down, false, _) => {
                    self.move_down();
                    cx.invalidate_paint();
                }
                (Key::Home, false, _) => {
                    self.move_home();
                    cx.invalidate_paint();
                }
                (Key::End, false, _) => {
                    self.move_end();
                    cx.invalidate_paint();
                }
                (Key::PageUp, false, _) => {
                    let page = self.height.saturating_sub(1) as usize;
                    self.move_page_up(page);
                    cx.invalidate_paint();
                }
                (Key::PageDown, false, _) => {
                    let page = self.height.saturating_sub(1) as usize;
                    self.move_page_down(page);
                    cx.invalidate_paint();
                }
                (Key::Char(c), false, false) if *c != '\n' => {
                    self.insert_char(*c);
                    cx.invalidate_paint();
                }
                (Key::Backspace, false, _) => {
                    self.delete_backward();
                    cx.invalidate_paint();
                }
                (Key::Delete, false, _) => {
                    self.delete_forward();
                    cx.invalidate_paint();
                }
                (Key::Enter, false, _) => {
                    self.insert_newline();
                    cx.invalidate_paint();
                }
                // Ctrl+Z: undo
                (Key::Char('z'), true, _) => {
                    self.undo();
                    cx.invalidate_paint();
                }
                // Ctrl+Y: redo
                (Key::Char('y'), true, _) => {
                    self.redo();
                    cx.invalidate_paint();
                }
                _ => {}
            }
        }
    }

    fn layout(&mut self, rect: Rect, _cx: &mut crate::component::LayoutCx) {
        self.rect = rect;
    }

    fn focusable(&self) -> bool {
        true
    }

    fn style(&self) -> Style {
        self.style.clone()
    }
}

/// Calculate display width of &str.
pub(crate) fn str_width(s: &str) -> u16 {
    s.chars()
        .map(|c| unicode_width::UnicodeWidthChar::width(c).unwrap_or(0) as u16)
        .sum()
}
