//! Top-level editor state: buffer collection, mode, dispatch from keys.

use std::io;
use std::path::PathBuf;
use std::time::Instant;

use crossterm::event::{KeyCode, KeyEvent};

use crate::buffer::{Buffer, Pos};
use crate::keybindings::{key_to_action, Action};

/// What the editor is currently doing, which changes what keys mean.
#[derive(Debug)]
pub enum Mode {
    /// Regular editing.
    Normal,
    /// Search prompt is open.
    Search {
        /// Current query text.
        query: String,
        /// Most recent match's position (so Enter advances).
        last_match: Option<Pos>,
        /// Cursor position when search started — Esc restores it.
        anchor: Pos,
    },
    /// Save-as prompt is open.
    SavePrompt {
        /// User-entered filename so far.
        input: String,
        /// If `true`, quit after a successful save.
        then_quit: bool,
    },
    /// Quit-with-unsaved-changes confirmation.
    QuitConfirm,
    /// Help overlay is visible.
    Help,
}

/// Top-level editor.
pub struct Editor {
    /// Open buffers (each rendered as a tab when more than one).
    pub buffers: Vec<Buffer>,
    /// Index of the active buffer.
    pub current: usize,
    /// Current mode.
    pub mode: Mode,
    /// Short message shown on the second status line.
    pub status_message: String,
    /// Wall-clock time at which the current status message was set.
    pub status_time: Instant,
    /// Whether the line-number gutter is shown.
    pub show_line_numbers: bool,
    /// Width (in spaces) of one Tab keystroke.
    pub tab_size: usize,
    /// Main loop continues while this is `true`.
    pub running: bool,
    /// Last known terminal width.
    pub width: u16,
    /// Last known terminal height.
    pub height: u16,
    /// True if status / tab / mode bar contents changed since last render.
    pub chrome_dirty: bool,
}

impl Editor {
    /// Build a new editor, optionally opening a file.
    pub fn new(path: Option<PathBuf>, width: u16, height: u16) -> io::Result<Self> {
        let buffer = match path {
            Some(p) if p.exists() => Buffer::open(&p)?,
            Some(p) => {
                let mut b = Buffer::empty();
                b.language = crate::highlight::Language::from_path(&p);
                b.path = Some(p);
                b
            }
            None => Buffer::empty(),
        };
        Ok(Self {
            buffers: vec![buffer],
            current: 0,
            mode: Mode::Normal,
            status_message: "Sintexy ready — F1 for help".to_string(),
            status_time: Instant::now(),
            show_line_numbers: true,
            tab_size: 4,
            running: true,
            width,
            height,
            chrome_dirty: true,
        })
    }

    /// Currently active buffer (immutable).
    pub fn buf(&self) -> &Buffer {
        &self.buffers[self.current]
    }
    /// Currently active buffer (mutable).
    pub fn buf_mut(&mut self) -> &mut Buffer {
        &mut self.buffers[self.current]
    }

    /// Handle a terminal resize.
    pub fn on_resize(&mut self, w: u16, h: u16) {
        self.width = w;
        self.height = h;
        for b in &mut self.buffers {
            b.mark_all_dirty();
        }
        self.chrome_dirty = true;
    }

    /// Set a transient status message.
    pub fn set_status(&mut self, msg: impl Into<String>) {
        self.status_message = msg.into();
        self.status_time = Instant::now();
        self.chrome_dirty = true;
    }

    /// Number of rows reserved for chrome (tab bar + status + optional mode bar).
    pub fn chrome_rows(&self) -> u16 {
        let tab_bar = if self.buffers.len() > 1 { 1 } else { 0 };
        let status = 2;
        let mode_extra = match self.mode {
            Mode::Search { .. } | Mode::SavePrompt { .. } | Mode::QuitConfirm => 1,
            _ => 0,
        };
        tab_bar + status + mode_extra
    }

    /// Rows available for text content.
    pub fn content_rows(&self) -> u16 {
        self.height.saturating_sub(self.chrome_rows())
    }

    /// Width of the line-number gutter (zero if hidden).
    pub fn gutter_width(&self) -> u16 {
        if !self.show_line_numbers {
            return 0;
        }
        let max_line = self.buf().lines.len().max(1);
        let digits = max_line.to_string().len() as u16;
        digits + 2
    }

    /// Columns available for text content.
    pub fn content_cols(&self) -> u16 {
        self.width.saturating_sub(self.gutter_width())
    }

    /// Y-coordinate (on screen) of the first content row.
    pub fn content_start_row(&self) -> u16 {
        if self.buffers.len() > 1 { 1 } else { 0 }
    }

    /// Active search query, if any.
    pub fn current_search(&self) -> Option<&str> {
        match &self.mode {
            Mode::Search { query, .. } => Some(query.as_str()),
            _ => None,
        }
    }

    /// Dispatch a key event.
    pub fn handle_key(&mut self, ev: KeyEvent) -> io::Result<()> {
        match self.mode {
            Mode::Help => {
                self.mode = Mode::Normal;
                self.chrome_dirty = true;
                for b in &mut self.buffers {
                    b.mark_all_dirty();
                }
                return Ok(());
            }
            Mode::SavePrompt { .. } => return self.handle_save_prompt(ev),
            Mode::QuitConfirm => return self.handle_quit_confirm(ev),
            Mode::Search { .. } => return self.handle_search(ev),
            Mode::Normal => {}
        }
        let action = key_to_action(ev);
        self.dispatch(action)
    }

    fn dispatch(&mut self, action: Action) -> io::Result<()> {
        match action {
            Action::Quit => self.start_quit(),
            Action::Save => self.do_save()?,
            Action::SaveAs => self.start_save_as(false),
            Action::Find => self.start_find(),
            Action::Help => {
                self.mode = Mode::Help;
                self.chrome_dirty = true;
            }
            Action::ToggleLineNumbers => {
                self.show_line_numbers = !self.show_line_numbers;
                for b in &mut self.buffers {
                    b.mark_all_dirty();
                }
                self.chrome_dirty = true;
            }
            Action::NewTab => {
                self.buffers.push(Buffer::empty());
                self.current = self.buffers.len() - 1;
                self.chrome_dirty = true;
            }
            Action::CycleTab => {
                if self.buffers.len() > 1 {
                    self.current = (self.current + 1) % self.buffers.len();
                    self.chrome_dirty = true;
                    self.buf_mut().mark_all_dirty();
                }
            }
            Action::CloseTab => self.close_current_tab(),
            Action::Undo => {
                if self.buf_mut().undo() {
                    self.set_status("Undo");
                } else {
                    self.set_status("Nothing to undo");
                }
            }
            Action::Redo => {
                if self.buf_mut().redo() {
                    self.set_status("Redo");
                } else {
                    self.set_status("Nothing to redo");
                }
            }
            Action::MoveLeft => self.buf_mut().move_left(),
            Action::MoveRight => self.buf_mut().move_right(),
            Action::MoveUp => self.buf_mut().move_up(),
            Action::MoveDown => self.buf_mut().move_down(),
            Action::MoveHome => self.buf_mut().move_home(),
            Action::MoveEnd => self.buf_mut().move_end(),
            Action::MoveWordLeft => self.buf_mut().move_word_left(),
            Action::MoveWordRight => self.buf_mut().move_word_right(),
            Action::PageUp => {
                let n = self.content_rows().max(1) as usize;
                self.buf_mut().page_up(n);
            }
            Action::PageDown => {
                let n = self.content_rows().max(1) as usize;
                self.buf_mut().page_down(n);
            }
            Action::InsertChar(c) => self.buf_mut().insert_char(c),
            Action::InsertNewline => self.buf_mut().insert_newline(),
            Action::InsertTab => {
                let s = " ".repeat(self.tab_size);
                self.buf_mut().insert_str(&s);
            }
            Action::Backspace => self.buf_mut().backspace(),
            Action::Delete => self.buf_mut().delete(),
            Action::Cancel | Action::Nothing => {}
        }
        Ok(())
    }

    fn start_quit(&mut self) {
        if self.buffers.iter().any(|b| b.modified) {
            self.mode = Mode::QuitConfirm;
            self.set_status("Unsaved changes — Save (y) / Discard (n) / Esc cancel");
        } else {
            self.running = false;
        }
    }

    fn handle_quit_confirm(&mut self, ev: KeyEvent) -> io::Result<()> {
        match ev.code {
            KeyCode::Char('y') | KeyCode::Char('Y') => {
                for i in 0..self.buffers.len() {
                    if !self.buffers[i].modified {
                        continue;
                    }
                    if self.buffers[i].path.is_some() {
                        self.buffers[i].save()?;
                    } else {
                        self.current = i;
                        self.mode = Mode::SavePrompt {
                            input: String::new(),
                            then_quit: true,
                        };
                        self.chrome_dirty = true;
                        return Ok(());
                    }
                }
                self.running = false;
            }
            KeyCode::Char('n') | KeyCode::Char('N') => {
                self.running = false;
            }
            KeyCode::Esc => {
                self.mode = Mode::Normal;
                self.chrome_dirty = true;
                self.set_status("");
            }
            _ => {}
        }
        Ok(())
    }

    fn do_save(&mut self) -> io::Result<()> {
        if self.buf().path.is_some() {
            self.buf_mut().save()?;
            let name = self.buf().filename().to_string();
            self.set_status(format!("Saved {name}"));
        } else {
            self.start_save_as(false);
        }
        Ok(())
    }

    fn start_save_as(&mut self, then_quit: bool) {
        self.mode = Mode::SavePrompt {
            input: self.buf().path.as_ref().and_then(|p| p.to_str()).unwrap_or("").to_string(),
            then_quit,
        };
        self.chrome_dirty = true;
    }

    fn handle_save_prompt(&mut self, ev: KeyEvent) -> io::Result<()> {
        let (input, then_quit) = match &self.mode {
            Mode::SavePrompt { input, then_quit } => (input.clone(), *then_quit),
            _ => return Ok(()),
        };
        match ev.code {
            KeyCode::Esc => {
                self.mode = Mode::Normal;
                self.chrome_dirty = true;
                self.set_status("Save cancelled");
            }
            KeyCode::Enter => {
                if input.is_empty() {
                    self.set_status("Filename empty");
                    return Ok(());
                }
                let path = PathBuf::from(&input);
                self.buf_mut().save_as(&path)?;
                let name = self.buf().filename().to_string();
                self.mode = Mode::Normal;
                self.chrome_dirty = true;
                for b in &mut self.buffers {
                    b.mark_all_dirty();
                }
                self.set_status(format!("Saved {name}"));
                if then_quit {
                    self.start_quit();
                }
            }
            KeyCode::Backspace => {
                if let Mode::SavePrompt { input, .. } = &mut self.mode {
                    input.pop();
                }
                self.chrome_dirty = true;
            }
            KeyCode::Char(c) if !c.is_control() => {
                if let Mode::SavePrompt { input, .. } = &mut self.mode {
                    input.push(c);
                }
                self.chrome_dirty = true;
            }
            _ => {}
        }
        Ok(())
    }

    fn start_find(&mut self) {
        let anchor = self.buf().cursor;
        self.mode = Mode::Search {
            query: String::new(),
            last_match: None,
            anchor,
        };
        self.chrome_dirty = true;
    }

    fn handle_search(&mut self, ev: KeyEvent) -> io::Result<()> {
        let cur = self.current;
        match ev.code {
            KeyCode::Esc => {
                let anchor = match &self.mode {
                    Mode::Search { anchor, .. } => *anchor,
                    _ => return Ok(()),
                };
                self.buffers[cur].cursor = anchor;
                self.buffers[cur].mark_all_dirty();
                self.mode = Mode::Normal;
                self.chrome_dirty = true;
                self.set_status("Search cancelled");
            }
            KeyCode::Enter => {
                let (q, from) = match &self.mode {
                    Mode::Search { query, last_match, .. } => (
                        query.clone(),
                        last_match.unwrap_or(self.buffers[cur].cursor),
                    ),
                    _ => return Ok(()),
                };
                if q.is_empty() {
                    return Ok(());
                }
                if let Some(pos) = self.buffers[cur].find_next(&q, from) {
                    self.buffers[cur].cursor = pos;
                    self.buffers[cur].mark_all_dirty();
                    if let Mode::Search { last_match, .. } = &mut self.mode {
                        *last_match = Some(pos);
                    }
                    self.chrome_dirty = true;
                } else {
                    self.set_status("No matches");
                }
            }
            KeyCode::Backspace => {
                if let Mode::Search { query, last_match, .. } = &mut self.mode {
                    query.pop();
                    *last_match = None;
                }
                self.buffers[cur].mark_all_dirty();
                self.chrome_dirty = true;
            }
            KeyCode::Char(c) if !c.is_control() => {
                let (q, from) = match &mut self.mode {
                    Mode::Search { query, anchor, last_match } => {
                        query.push(c);
                        *last_match = None;
                        (query.clone(), *anchor)
                    }
                    _ => return Ok(()),
                };
                if let Some(pos) = self.buffers[cur].find_next(&q, from) {
                    self.buffers[cur].cursor = pos;
                    if let Mode::Search { last_match, .. } = &mut self.mode {
                        *last_match = Some(pos);
                    }
                }
                self.buffers[cur].mark_all_dirty();
                self.chrome_dirty = true;
            }
            _ => {}
        }
        Ok(())
    }

    fn close_current_tab(&mut self) {
        if self.buf().modified {
            self.set_status("Buffer modified — save (Ctrl-S) before closing");
            return;
        }
        if self.buffers.len() == 1 {
            self.running = false;
        } else {
            self.buffers.remove(self.current);
            if self.current >= self.buffers.len() {
                self.current = self.buffers.len() - 1;
            }
            self.chrome_dirty = true;
            self.buf_mut().mark_all_dirty();
        }
    }
}
