//! Task Archive View — scrollable list of all IDE background tasks with
//! inline log viewer.
//!
//! ## Modes
//!
//! * **List** — shows all tasks (running first, then newest-first).
//!   Filter by typing; navigate with ↑↓/PgUp/PgDn; Enter opens detail.
//!   `c` cancels the selected running task.  Esc → CloseModal.
//!
//! * **Detail** — shows task output lines loaded from `LogStore`.
//!   ↑↓/PgUp/PgDn scroll; Esc goes back to List mode.
//!
//! Layout (List mode):
//! ```text
//!   > filter query_
//!   ● build: cargo build              00:03   Running
//!   ✓ test: cargo test                00:12   Success
//!   ✗ lint: cargo clippy              00:01   Error
//! ```
//!
//! Layout (Detail mode):
//! ```text
//!   ← build: cargo build ─── Success 00:12
//!   Compiling foo v0.1.0
//!   Finished release target in 12.3s
//!   …
//!   [line 1-30/30 — ↑↓ scroll, Esc back]
//! ```

use std::time::Duration;

use ratatui::{
    Frame,
    layout::{Constraint, Direction, Layout, Rect},
    style::{Modifier, Style},
    text::{Line, Span},
    widgets::{Block, Borders, List, ListItem, Paragraph, Wrap},
};

use crate::prelude::*;

use input::{Key, KeyEvent};
use operation::{Event, Operation, TaskArchiveOp};
use settings::Settings;
use views::View;
use widgets::focus::FocusRing;
use widgets::focusable::FocusOp;
use widgets::input_field::InputField;
use crate::task_registry::{TaskId, TaskStatus};

const FOCUS_FILTER: &str = "filter";
const FOCUS_LIST: &str = "list";
const FOCUS_DETAIL: &str = "detail";

// ---------------------------------------------------------------------------
// ViewMode
// ---------------------------------------------------------------------------

#[derive(Debug)]
#[allow(dead_code)]
enum ViewMode {
    List,
    Detail {
        task_id: TaskId,
        title:   String,
        status:  TaskStatus,
        lines:   Vec<String>,
        scroll:  usize,
    },
}

// ---------------------------------------------------------------------------
// TaskArchiveView
// ---------------------------------------------------------------------------

#[derive(Debug)]
pub struct TaskArchiveView {
    filter: InputField,
    focus:  FocusRing,
    cursor: usize,
    scroll: usize,
    mode:   ViewMode,
}

impl TaskArchiveView {
    pub fn new() -> Self {
        Self {
            filter: InputField::new("Filter"),
            focus:  FocusRing::new(vec![FOCUS_FILTER, FOCUS_LIST]),
            cursor: 0,
            scroll: 0,
            mode:   ViewMode::List,
        }
    }

    fn move_up(&mut self, n: usize) {
        self.cursor = self.cursor.saturating_sub(n);
    }

    fn move_down(&mut self, n: usize, list_len: usize) {
        if list_len == 0 { return; }
        self.cursor = (self.cursor + n).min(list_len - 1);
    }

    pub fn clamp_cursor(&mut self, list_len: usize) {
        if list_len == 0 { self.cursor = 0; }
        else { self.cursor = self.cursor.min(list_len - 1); }
    }

    /// Show output lines for a task (switches to Detail mode).
    pub fn show_detail(
        &mut self,
        task_id: TaskId,
        title: String,
        status: TaskStatus,
        lines: Vec<String>,
    ) {
        self.mode = ViewMode::Detail { task_id, title, status, lines, scroll: 0 };
        self.focus.set_focus(FOCUS_DETAIL);
    }

    pub fn close_detail(&mut self) {
        self.mode = ViewMode::List;
        self.focus = FocusRing::new(vec![FOCUS_FILTER, FOCUS_LIST]);
    }

    pub fn is_detail(&self) -> bool {
        matches!(self.mode, ViewMode::Detail { .. })
    }
}

// ---------------------------------------------------------------------------
// View trait
// ---------------------------------------------------------------------------

impl View for TaskArchiveView {
    // Modal so that Esc/CloseModal restores the stashed primary screen.
    const KIND: crate::views::ViewKind = crate::views::ViewKind::Modal;

    fn save_state(&mut self, _app: &mut crate::app_state::AppState) {}

    fn handle_key(&self, key: KeyEvent) -> Vec<Operation> {
        match &self.mode {
            ViewMode::Detail { .. } => {
                match key.key {
                    Key::ArrowUp | Key::Char('k') => vec![Operation::NavigateUp],
                    Key::ArrowDown | Key::Char('j') => vec![Operation::NavigateDown],
                    Key::PageUp    => vec![Operation::NavigatePageUp],
                    Key::PageDown  => vec![Operation::NavigatePageDown],
                    Key::Home      => vec![Operation::NavigateHome],
                    Key::End       => vec![Operation::NavigateEnd],
                    _ => vec![],
                }
            }
            ViewMode::List => {
                if key.key == Key::Tab {
                    return vec![Operation::Focus(FocusOp::Next)];
                }
                if key.key == Key::BackTab {
                    return vec![Operation::Focus(FocusOp::Prev)];
                }

                match self.focus.current() {
                    FOCUS_FILTER => {
                        if let Some(field_op) = crate::widgets::input_field::key_to_op(key) {
                            return vec![Operation::TaskArchiveLocal(TaskArchiveOp::FilterInput(field_op))];
                        }
                        match key.key {
                            Key::ArrowUp   => vec![Operation::NavigateUp],
                            Key::ArrowDown => vec![Operation::NavigateDown],
                            Key::PageUp    => vec![Operation::NavigatePageUp],
                            Key::PageDown  => vec![Operation::NavigatePageDown],
                            _ => vec![],
                        }
                    }
                    FOCUS_LIST => {
                        match key.key {
                            Key::ArrowUp   => vec![Operation::NavigateUp],
                            Key::ArrowDown => vec![Operation::NavigateDown],
                            Key::PageUp    => vec![Operation::NavigatePageUp],
                            Key::PageDown  => vec![Operation::NavigatePageDown],
                            Key::Enter     => vec![Operation::TaskArchiveLocal(TaskArchiveOp::OpenSelected)],
                            Key::Char('c') | Key::Char('C') => {
                                vec![Operation::TaskArchiveLocal(TaskArchiveOp::CancelSelected)]
                            }
                            _ => {
                                if let Some(field_op) = crate::widgets::input_field::key_to_op(key) {
                                    return vec![Operation::TaskArchiveLocal(TaskArchiveOp::FilterInput(field_op))];
                                }
                                vec![]
                            }
                        }
                    }
                    _ => vec![],
                }
            }
        }
    }

    fn handle_mouse(&self, _mouse: input::MouseEvent) -> Vec<Operation> {
        vec![]
    }

    fn handle_operation(&mut self, op: &Operation, _settings: &Settings) -> Option<Event> {
        match op {
            Operation::Focus(focus_op) => {
                match focus_op {
                    FocusOp::Next => self.focus.focus_next(),
                    FocusOp::Prev => self.focus.focus_prev(),
                    _ => {}
                }
                Some(Event::applied("task_archive", op.clone()))
            }

            Operation::TaskArchiveLocal(ta_op) => {
                match ta_op {
                    TaskArchiveOp::FilterInput(field_op) => {
                        self.filter.apply(field_op);
                        self.cursor = 0;
                        self.scroll = 0;
                    }
                    TaskArchiveOp::CloseDetail => {
                        self.close_detail();
                    }
                    TaskArchiveOp::TaskUpdated { new_len } => {
                        self.clamp_cursor(*new_len);
                    }
                    // OpenSelected / CancelSelected resolved in app.rs
                    TaskArchiveOp::OpenSelected | TaskArchiveOp::CancelSelected => {}
                    TaskArchiveOp::ShowDetail { task_id, title, status, lines } => {
                        self.show_detail(*task_id, title.clone(), status.clone(), lines.clone());
                    }
                }
                Some(Event::applied("task_archive", op.clone()))
            }

            Operation::NavigateUp => {
                match &mut self.mode {
                    ViewMode::Detail { scroll, .. } => {
                        *scroll = scroll.saturating_sub(1);
                    }
                    ViewMode::List => self.move_up(1),
                }
                Some(Event::applied("task_archive", op.clone()))
            }
            Operation::NavigateDown => {
                match &mut self.mode {
                    ViewMode::Detail { scroll, lines, .. } => {
                        *scroll = (*scroll + 1).min(lines.len().saturating_sub(1));
                    }
                    ViewMode::List => self.move_down(1, usize::MAX),
                }
                Some(Event::applied("task_archive", op.clone()))
            }
            Operation::NavigatePageUp => {
                match &mut self.mode {
                    ViewMode::Detail { scroll, .. } => {
                        *scroll = scroll.saturating_sub(20);
                    }
                    ViewMode::List => self.move_up(10),
                }
                Some(Event::applied("task_archive", op.clone()))
            }
            Operation::NavigatePageDown => {
                match &mut self.mode {
                    ViewMode::Detail { scroll, lines, .. } => {
                        *scroll = (*scroll + 20).min(lines.len().saturating_sub(1));
                    }
                    ViewMode::List => self.move_down(10, usize::MAX),
                }
                Some(Event::applied("task_archive", op.clone()))
            }
            Operation::NavigateHome => {
                if let ViewMode::Detail { scroll, .. } = &mut self.mode {
                    *scroll = 0;
                }
                Some(Event::applied("task_archive", op.clone()))
            }
            Operation::NavigateEnd => {
                if let ViewMode::Detail { scroll, lines, .. } = &mut self.mode {
                    *scroll = lines.len().saturating_sub(1);
                }
                Some(Event::applied("task_archive", op.clone()))
            }
            _ => None,
        }
    }

    fn render(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        // Stub — real rendering done via render_with_registry from app.rs.
        let _ = (frame, area, theme);
    }

    fn status_bar(
        &self,
        _state: &crate::app_state::AppState,
        bar: &mut crate::widgets::status_bar::StatusBarBuilder,
    ) {
        match &self.mode {
            ViewMode::List => bar.label("Tasks  ↑↓ navigate · Enter open · c cancel · Esc close"),
            ViewMode::Detail { title, .. } => bar.label(format!("Log: {}  ↑↓ scroll · Esc back", title)),
        };
    }
}

impl TaskArchiveView {
    /// Full render that has access to the task registry (called from app.rs).
    pub fn render_with_registry(
        &self,
        frame: &mut Frame,
        area: Rect,
        theme: &crate::theme::Theme,
        task_registry: &crate::task_registry::TaskRegistry,
    ) {
        match &self.mode {
            ViewMode::List => self.render_list(frame, area, theme, task_registry),
            ViewMode::Detail { title, status, lines, scroll, .. } => {
                self.render_detail(frame, area, theme, title, status, lines, *scroll)
            }
        }
    }

    fn render_list(
        &self,
        frame: &mut Frame,
        area: Rect,
        theme: &crate::theme::Theme,
        task_registry: &crate::task_registry::TaskRegistry,
    ) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Length(1), Constraint::Min(1)])
            .split(area);

        let filter_area = chunks[0];
        let list_area   = chunks[1];

        let bg     = theme.bg();
        let fg     = theme.fg();
        let dim    = theme.fg_dim();
        let accent = theme.accent();
        let sel_bg = theme.selection_bg();

        // ── Filter bar ──────────────────────────────────────────────────────
        let filter_focused = self.focus.current() == FOCUS_FILTER;
        let fstyle = if filter_focused {
            Style::default().fg(accent).bg(bg)
        } else {
            Style::default().fg(dim).bg(bg)
        };
        let filter_prefix = if filter_focused { "> " } else { "  " };
        frame.render_widget(
            Paragraph::new(format!("{}{}", filter_prefix, self.filter.text())).style(fstyle),
            filter_area,
        );

        // ── Collect + filter + sort tasks ───────────────────────────────────
        let q = self.filter.text().to_lowercase();
        let mut entries: Vec<&crate::task_registry::Task> = task_registry
            .all_tasks()
            .filter(|t| {
                if q.is_empty() { return true; }
                t.command.to_lowercase().contains(&q)
                    || t.key.queue.0.to_lowercase().contains(&q)
                    || t.key.target.to_lowercase().contains(&q)
            })
            .collect();

        entries.sort_by(|a, b| {
            let a_live = matches!(a.status, TaskStatus::Running | TaskStatus::Pending);
            let b_live = matches!(b.status, TaskStatus::Running | TaskStatus::Pending);
            if a_live != b_live { return b_live.cmp(&a_live); }
            b.created_at.cmp(&a.created_at)
        });

        let list_len    = entries.len();
        let visible_rows = list_area.height as usize;
        let cursor = if list_len == 0 { 0 } else { self.cursor.min(list_len - 1) };
        let mut scroll = self.scroll;
        if list_len > 0 {
            if cursor < scroll { scroll = cursor; }
            if cursor >= scroll + visible_rows { scroll = cursor + 1 - visible_rows; }
        }

        let col_w = list_area.width as usize;

        let items: Vec<ListItem> = entries
            .iter()
            .enumerate()
            .skip(scroll)
            .take(visible_rows)
            .map(|(idx, task)| {
                let is_sel = idx == cursor;
                let (ind, ind_style) = task_indicator(task, theme);
                let dur = task_duration(task);
                let status_str = task_status_label(&task.status);

                let raw = format!("{}: {}", task.key.queue.0, task.command);
                let reserved = 2 + 1 + 1 + 5 + 12;
                let max_lbl = col_w.saturating_sub(reserved);
                let label = if unicode_width::UnicodeWidthStr::width(raw.as_str()) > max_lbl {
                    // Truncate by display width to avoid splitting wide glyphs.
                    let mut acc = String::new();
                    let mut used = 0usize;
                    for ch in raw.chars() {
                        let w = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(1);
                        if used + w > max_lbl.saturating_sub(1) { break; }
                        acc.push(ch);
                        used += w;
                    }
                    format!("{}…", acc)
                } else {
                    raw.clone()
                };
                let label_w = unicode_width::UnicodeWidthStr::width(label.as_str());
                let pad: String = " ".repeat(max_lbl.saturating_sub(label_w));

                let row_bg = if is_sel { sel_bg } else { bg };
                let row_fg = if is_sel { accent } else { fg };

                let line = Line::from(vec![
                    Span::styled(ind, ind_style.bg(row_bg)),
                    Span::styled(" ", Style::default().bg(row_bg)),
                    Span::styled(label + &pad, Style::default().fg(row_fg).bg(row_bg)),
                    Span::styled(format!(" {:>4} ", dur), Style::default().fg(dim).bg(row_bg)),
                    Span::styled(format!("{:<12}", status_str), ind_style.bg(row_bg)),
                ]);
                ListItem::new(line)
            })
            .collect();

        frame.render_widget(
            List::new(items).style(Style::default().fg(fg).bg(bg)),
            list_area,
        );

        if list_len == 0 {
            let msg = if q.is_empty() { "No tasks have been run yet." } else { "No matching tasks." };
            frame.render_widget(
                Paragraph::new(msg).style(Style::default().fg(dim).bg(bg)),
                list_area,
            );
        }
    }

    #[allow(clippy::too_many_arguments)]
    fn render_detail(
        &self,
        frame: &mut Frame,
        area: Rect,
        theme: &crate::theme::Theme,
        title:  &str,
        status: &TaskStatus,
        lines:  &[String],
        scroll: usize,
    ) {
        let bg  = theme.bg();
        let fg  = theme.fg();
        let dim = theme.fg_dim();

        let (status_str, status_style) = match status {
            TaskStatus::Running   => ("● Running",   Style::default().fg(fg)),
            TaskStatus::Pending   => ("○ Pending",   Style::default().fg(dim)),
            TaskStatus::Success   => ("✓ Success",   Style::default().fg(theme.level_success())),
            TaskStatus::Warning   => ("⚠ Warning",   Style::default().fg(theme.level_warn())),
            TaskStatus::Error     => ("✗ Error",     Style::default().fg(theme.level_error())),
            TaskStatus::Cancelled => ("— Cancelled", Style::default().fg(dim)),
        };

        let block_title = format!(" ← {} — {} ", title, status_str);
        let visible_rows = area.height.saturating_sub(2) as usize;
        let max_scroll   = lines.len().saturating_sub(visible_rows);
        let scroll       = scroll.min(max_scroll);

        let rendered_lines: Vec<Line> = lines
            .iter()
            .skip(scroll)
            .take(visible_rows)
            .map(|s| Line::from(Span::raw(s.as_str())))
            .collect();

        let block = Block::default()
            .borders(Borders::ALL)
            .title(Span::styled(block_title, status_style.add_modifier(Modifier::BOLD)))
            .style(Style::default().fg(fg).bg(bg));

        frame.render_widget(
            Paragraph::new(rendered_lines)
                .block(block)
                .wrap(Wrap { trim: false })
                .style(Style::default().fg(fg).bg(bg)),
            area,
        );

        // Scroll hint in bottom-right corner inside the border.
        if lines.len() > visible_rows {
            let hint = format!(
                " lines {}-{}/{} ",
                scroll + 1,
                (scroll + visible_rows).min(lines.len()),
                lines.len()
            );
            let hw = hint.len() as u16;
            if area.width > hw + 2 {
                let hint_rect = Rect {
                    x: area.x + area.width - hw - 1,
                    y: area.y + area.height - 1,
                    width: hw,
                    height: 1,
                };
                frame.render_widget(
                    Paragraph::new(Span::styled(hint, Style::default().fg(dim).bg(bg))),
                    hint_rect,
                );
            }
        }
    }

    /// Return the `TaskId` of the currently highlighted task.
    pub fn selected_task_id(
        &self,
        task_registry: &crate::task_registry::TaskRegistry,
    ) -> Option<TaskId> {
        let q = self.filter.text().to_lowercase();
        let mut entries: Vec<&crate::task_registry::Task> = task_registry
            .all_tasks()
            .filter(|t| {
                if q.is_empty() { return true; }
                t.command.to_lowercase().contains(&q)
                    || t.key.queue.0.to_lowercase().contains(&q)
                    || t.key.target.to_lowercase().contains(&q)
            })
            .collect();

        entries.sort_by(|a, b| {
            let a_live = matches!(a.status, TaskStatus::Running | TaskStatus::Pending);
            let b_live = matches!(b.status, TaskStatus::Running | TaskStatus::Pending);
            if a_live != b_live { return b_live.cmp(&a_live); }
            b.created_at.cmp(&a.created_at)
        });

        let idx = if entries.is_empty() { return None; } else { self.cursor.min(entries.len() - 1) };
        entries.get(idx).map(|t| t.id)
    }
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

fn task_indicator<'a>(task: &crate::task_registry::Task, theme: &crate::theme::Theme) -> (&'a str, Style) {
    match task.status {
        TaskStatus::Running   => ("●", Style::default().fg(theme.fg())),
        TaskStatus::Pending   => ("○", Style::default().fg(theme.fg_dim())),
        TaskStatus::Success   => ("✓", Style::default().fg(theme.level_success())),
        TaskStatus::Warning   => ("⚠", Style::default().fg(theme.level_warn())),
        TaskStatus::Error     => ("✗", Style::default().fg(theme.level_error())),
        TaskStatus::Cancelled => ("—", Style::default().fg(theme.fg_dim())),
    }
}

fn task_status_label(status: &TaskStatus) -> &'static str {
    match status {
        TaskStatus::Running   => "Running",
        TaskStatus::Pending   => "Pending",
        TaskStatus::Success   => "Success",
        TaskStatus::Warning   => "Warning",
        TaskStatus::Error     => "Error",
        TaskStatus::Cancelled => "Cancelled",
    }
}

fn task_duration(task: &crate::task_registry::Task) -> String {
    let start = match task.started_at { Some(s) => s, None => return "--".into() };
    let end   = task.finished_at.unwrap_or_else(std::time::Instant::now);
    format_duration(end.duration_since(start))
}

fn format_duration(d: Duration) -> String {
    let s = d.as_secs();
    if s < 60 { format!("{:02}s", s) } else { format!("{:02}m", s / 60) }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::input::{KeyEvent, Key, Modifiers};

    #[test]
    fn enter_on_list_returns_open_selected() {
        let mut view = TaskArchiveView::new();
        view.focus.set_focus(FOCUS_LIST);
        let key = KeyEvent { modifiers: Modifiers::empty(), key: Key::Enter };
        let ops = view.handle_key(key);
        assert_eq!(ops.len(), 1);
        match &ops[0] {
            Operation::TaskArchiveLocal(TaskArchiveOp::OpenSelected) => {}
            other => panic!("expected OpenSelected, got {:?}", other),
        }
    }

    #[test]
    fn selected_task_id_returns_first_running_task() {
        let mut reg = crate::task_registry::TaskRegistry::new();
        let k = crate::task_registry::TaskKey { queue: crate::task_registry::TaskQueueId("build".into()), target: "x".into() };
        let id = reg.schedule_task(k, crate::task_registry::TaskTrigger::Manual, "cmd".into());
        let view = TaskArchiveView::new();
        assert_eq!(view.selected_task_id(&reg), Some(id));
    }
}
