//! Live play screen, frame loop, pause overlay, and result overlay.

use std::io;
use std::time::{Duration, Instant};

use ratatui::crossterm::event::{self, Event, KeyCode, KeyEventKind};
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::style::Style;
use ratatui::text::{Line, Span, Text};
use ratatui::widgets::{Block, Borders, Clear, Paragraph};
use ratatui::{DefaultTerminal, Frame};

use crate::config::{Config, Palette};
use crate::input::live::{disable_kitty, enable_kitty, kitty_supported};
use crate::input::raw::RawInput;
use crate::modes::{Game, Mode, Outcome};
use crate::tui::board_view::render_board;
use crate::tui::panels::{hold_text, next_text};

const VISIBLE_ROWS: usize = 22;
const NEXT_COUNT: usize = 5;
const FRAME: Duration = Duration::from_micros(16_667);

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PlayPhase {
    Playing,
    Paused,
    GameOver,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OverlayAction {
    None,
    Resume,
    Restart,
    ToMenu,
    Quit,
}

/// Maps overlay key input to the action it should trigger.
pub fn overlay_key(code: KeyCode, phase: PlayPhase) -> OverlayAction {
    match phase {
        PlayPhase::Playing => OverlayAction::None,
        PlayPhase::Paused => match code {
            KeyCode::Esc => OverlayAction::Resume,
            KeyCode::Char('r') | KeyCode::Char('R') => OverlayAction::Restart,
            KeyCode::Char('m') | KeyCode::Char('M') => OverlayAction::ToMenu,
            KeyCode::Char('q') | KeyCode::Char('Q') => OverlayAction::Quit,
            _ => OverlayAction::None,
        },
        PlayPhase::GameOver => match code {
            KeyCode::Char('r') | KeyCode::Char('R') => OverlayAction::Restart,
            KeyCode::Esc | KeyCode::Char('m') | KeyCode::Char('M') => OverlayAction::ToMenu,
            KeyCode::Char('q') | KeyCode::Char('Q') => OverlayAction::Quit,
            _ => OverlayAction::None,
        },
    }
}

fn random_seed() -> i64 {
    use std::time::{SystemTime, UNIX_EPOCH};
    let nanos = SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_nanos())
        .unwrap_or(1);
    ((nanos as u64) % 2_147_483_646 + 1) as i64
}

pub fn run(terminal: &mut DefaultTerminal, mode: Mode, config: &Config) -> io::Result<bool> {
    let supported = kitty_supported();
    if supported {
        let _ = enable_kitty(&mut io::stdout());
    }
    let result = run_inner(terminal, mode, config, supported);
    if supported {
        let _ = disable_kitty(&mut io::stdout());
    }
    result
}

fn run_inner(
    terminal: &mut DefaultTerminal,
    mode: Mode,
    config: &Config,
    kitty: bool,
) -> io::Result<bool> {
    let mut game = Game::new(mode, config, random_seed());
    let mut raw = if kitty { None } else { Some(RawInput::new()) };
    let mut phase = PlayPhase::Playing;
    let mut last_outcome = Outcome::Running;
    let mut last = Instant::now();

    loop {
        terminal.draw(|f| draw(f, &game, phase, last_outcome, kitty, &config.palette))?;

        if event::poll(Duration::from_millis(2))? {
            if let Event::Key(key) = event::read()? {
                match phase {
                    PlayPhase::Playing => {
                        if key.kind == KeyEventKind::Press && key.code == KeyCode::Esc {
                            phase = PlayPhase::Paused;
                        } else if kitty {
                            game.input.push(key);
                        }
                    }
                    PlayPhase::Paused | PlayPhase::GameOver => {
                        if key.kind == KeyEventKind::Press {
                            match overlay_key(key.code, phase) {
                                OverlayAction::Resume => {
                                    phase = PlayPhase::Playing;
                                    last = Instant::now();
                                    if let Some(raw) = raw.as_mut() {
                                        raw.reset();
                                    }
                                }
                                OverlayAction::Restart => {
                                    game = Game::new(mode, config, random_seed());
                                    last_outcome = Outcome::Running;
                                    phase = PlayPhase::Playing;
                                    last = Instant::now();
                                    if let Some(raw) = raw.as_mut() {
                                        raw.reset();
                                    }
                                }
                                OverlayAction::ToMenu => return Ok(false),
                                OverlayAction::Quit => return Ok(true),
                                OverlayAction::None => {}
                            }
                        }
                    }
                }
            }
        }

        if phase == PlayPhase::Playing {
            let now = Instant::now();
            while now.duration_since(last) >= FRAME {
                if let Some(raw) = raw.as_mut() {
                    for fi in raw.poll(&config.keybinds) {
                        game.input.push_frame_input(fi);
                    }
                }
                game.step();
                last += FRAME;
                let outcome = game.outcome();
                if outcome != Outcome::Running {
                    last_outcome = outcome;
                    phase = PlayPhase::GameOver;
                    break;
                }
            }
        } else {
            last = Instant::now();
        }
    }
}

fn draw(
    frame: &mut Frame,
    game: &Game,
    phase: PlayPhase,
    outcome: Outcome,
    kitty: bool,
    palette: &Palette,
) {
    let outer = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Min(1), Constraint::Length(1)])
        .split(frame.area());

    let body = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(24), Constraint::Min(18)])
        .split(outer[0]);

    frame.render_widget(
        Paragraph::new(render_board(&game.engine.board, VISIBLE_ROWS, palette))
            .block(Block::default().borders(Borders::ALL).title(game.title())),
        body[0],
    );

    let side = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(NEXT_COUNT as u16 + 2),
            Constraint::Min(7),
        ])
        .split(body[1]);
    let top = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([Constraint::Length(8), Constraint::Min(8)])
        .split(side[0]);
    frame.render_widget(
        Paragraph::new(hold_text(&game.engine.board, palette))
            .block(Block::default().borders(Borders::ALL).title(" Hold ")),
        top[0],
    );
    frame.render_widget(
        Paragraph::new(next_text(&game.engine.board, NEXT_COUNT, palette))
            .block(Block::default().borders(Borders::ALL).title(" Next ")),
        top[1],
    );
    let stat_text: Vec<Line> = game.stat_lines().into_iter().map(Line::from).collect();
    frame.render_widget(
        Paragraph::new(Text::from(stat_text))
            .block(Block::default().borders(Borders::ALL).title(" Stats ")),
        side[1],
    );

    let hint = if matches!(phase, PlayPhase::Playing) {
        if kitty {
            "Left/Right move · Down soft · Space hard · Z/X rotate · A 180 · C hold · Esc pause"
        } else {
            "Left/Right move · Down soft · Space hard · Z/X rotate · A 180 · C hold · Esc pause"
        }
    } else {
        ""
    };
    frame.render_widget(
        Paragraph::new(Line::from(Span::styled(
            hint,
            Style::default().fg(palette.label),
        ))),
        outer[1],
    );

    match phase {
        PlayPhase::Paused => draw_overlay(
            frame,
            " Paused ",
            &[
                "Esc   resume".to_string(),
                "R     restart".to_string(),
                "M     menu".to_string(),
                "Q     quit".to_string(),
            ],
        ),
        PlayPhase::GameOver => draw_overlay(frame, " Result ", &game.summary_lines(outcome)),
        PlayPhase::Playing => {}
    }
}

fn draw_overlay(frame: &mut Frame, title: &str, lines: &[String]) {
    let area = centered(40, 40, frame.area());
    frame.render_widget(Clear, area);
    let text: Vec<Line> = lines.iter().map(|l| Line::from(l.clone())).collect();
    frame.render_widget(
        Paragraph::new(Text::from(text)).block(
            Block::default()
                .borders(Borders::ALL)
                .title(title.to_string()),
        ),
        area,
    );
}

fn centered(pct_x: u16, pct_y: u16, area: Rect) -> Rect {
    let v = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - pct_y) / 2),
            Constraint::Percentage(pct_y),
            Constraint::Percentage((100 - pct_y) / 2),
        ])
        .split(area);
    Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - pct_x) / 2),
            Constraint::Percentage(pct_x),
            Constraint::Percentage((100 - pct_x) / 2),
        ])
        .split(v[1])[1]
}

#[cfg(test)]
mod tests {
    use super::*;
    use ratatui::crossterm::event::KeyCode;

    #[test]
    fn pause_overlay_keys() {
        assert_eq!(
            overlay_key(KeyCode::Esc, PlayPhase::Paused),
            OverlayAction::Resume
        );
        assert_eq!(
            overlay_key(KeyCode::Char('r'), PlayPhase::Paused),
            OverlayAction::Restart
        );
        assert_eq!(
            overlay_key(KeyCode::Char('m'), PlayPhase::Paused),
            OverlayAction::ToMenu
        );
        assert_eq!(
            overlay_key(KeyCode::Char('q'), PlayPhase::Paused),
            OverlayAction::Quit
        );
    }

    #[test]
    fn game_over_keys() {
        assert_eq!(
            overlay_key(KeyCode::Char('r'), PlayPhase::GameOver),
            OverlayAction::Restart
        );
        assert_eq!(
            overlay_key(KeyCode::Esc, PlayPhase::GameOver),
            OverlayAction::ToMenu
        );
        assert_eq!(
            overlay_key(KeyCode::Char('q'), PlayPhase::GameOver),
            OverlayAction::Quit
        );
    }

    #[test]
    fn playing_ignores_overlay_keys() {
        assert_eq!(
            overlay_key(KeyCode::Char('r'), PlayPhase::Playing),
            OverlayAction::None
        );
    }
}
