//! TUI event loop. Owns terminal init/restore and dispatches input → state.

use std::io::{self, Stdout};
use std::path::{Path, PathBuf};
use std::time::Duration;

use anyhow::{Context, Result};
use crossterm::event::{self, Event, KeyEventKind};
use crossterm::execute;
use crossterm::terminal::{
    disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen,
};
use ratatui::backend::CrosstermBackend;
use ratatui::Terminal;

use crate::canvas::{parse, Canvas};
use crate::input::{dispatch, Action};
use crate::layout::Viewport;
use crate::render::overlay::{draw_error_banner, draw_help_overlay};
use crate::render::{render_canvas, theme::Theme};
use crate::watcher::FileWatcher;

type Tui = Terminal<CrosstermBackend<Stdout>>;

/// RAII guard that always tears down raw mode + alt screen on drop, even on panic.
pub struct TerminalGuard {
    terminal: Option<Tui>,
}

impl TerminalGuard {
    pub fn new() -> Result<Self> {
        enable_raw_mode().context("enable raw mode")?;
        let mut stdout = io::stdout();
        execute!(stdout, EnterAlternateScreen).context("enter alt screen")?;
        let terminal = Terminal::new(CrosstermBackend::new(stdout)).context("init terminal")?;
        Ok(Self {
            terminal: Some(terminal),
        })
    }

    pub fn terminal(&mut self) -> &mut Tui {
        self.terminal
            .as_mut()
            .expect("terminal accessed after drop")
    }
}

impl Drop for TerminalGuard {
    fn drop(&mut self) {
        let _ = disable_raw_mode();
        let _ = execute!(io::stdout(), LeaveAlternateScreen);
    }
}

pub struct App {
    pub path: PathBuf,
    pub canvas: Canvas,
    pub viewport: Viewport,
    pub theme: Theme,
    pub help_open: bool,
    pub should_quit: bool,
    /// Last reload error, shown as a banner. Cleared on a successful reload.
    pub error_banner: Option<String>,
}

impl App {
    pub fn new(path: PathBuf, canvas: Canvas) -> Self {
        Self {
            path,
            canvas,
            viewport: Viewport::new(80, 24),
            theme: Theme::from_env(),
            help_open: false,
            should_quit: false,
            error_banner: None,
        }
    }

    pub fn reload(&mut self) {
        match parse::load(&self.path) {
            Ok(c) => {
                self.canvas = c;
                self.error_banner = None;
                tracing::info!(path = %self.path.display(), "reloaded canvas");
            }
            Err(e) => {
                let msg = format!("{e}");
                tracing::warn!(error = %msg, "reload failed; keeping previous canvas");
                self.error_banner = Some(msg);
            }
        }
    }

    pub fn apply(&mut self, action: Action) {
        match action {
            Action::None => {}
            Action::Quit => self.should_quit = true,
            Action::Pan { dx, dy } => self.viewport.pan(dx, dy),
            Action::Zoom { factor } => self.viewport.set_zoom(self.viewport.zoom * factor),
            Action::ToggleHelp => self.help_open = !self.help_open,
            Action::CloseHelp => self.help_open = false,
        }
    }
}

pub fn run(path: &Path) -> Result<()> {
    let canvas = parse::load(path).with_context(|| format!("loading {}", path.display()))?;
    let mut app = App::new(path.to_path_buf(), canvas);

    let (watcher, reload_rx) = FileWatcher::spawn(path)?;

    let mut guard = TerminalGuard::new()?;
    let result = event_loop(&mut app, guard.terminal(), &reload_rx);
    drop(guard);
    drop(watcher);
    result
}

fn event_loop(
    app: &mut App,
    terminal: &mut Tui,
    reload_rx: &std::sync::mpsc::Receiver<crate::watcher::Reload>,
) -> Result<()> {
    while !app.should_quit {
        terminal.draw(|frame| {
            let area = frame.area();
            app.viewport.cols = area.width;
            app.viewport.rows = area.height;
            let buf = frame.buffer_mut();
            render_canvas(&app.canvas, &app.viewport, &app.theme, buf);
            if let Some(msg) = &app.error_banner {
                draw_error_banner(area, msg, &app.theme, buf);
            }
            if app.help_open {
                draw_help_overlay(area, &app.theme, buf);
            }
        })?;

        // Drain any reload events without blocking, then poll for keypress.
        while reload_rx.try_recv().is_ok() {
            app.reload();
        }
        if event::poll(Duration::from_millis(50))? {
            if let Event::Key(key) = event::read()? {
                if key.kind == KeyEventKind::Press {
                    let action = dispatch(key, app.help_open);
                    app.apply(action);
                }
            }
        }
    }
    Ok(())
}
