//! File selector view.
//!
//! Layout:
//!   ┌─ Filter ──────────────────────────────────┐
//!   │ query_                                    │
//!   ├─ Recent Files ────┬─ project/ ────────────┤
//!   │ src/app.rs        │ > src/                │
//!   │ src/main.rs       │     views/            │
//!   │ …                 │     file_selector.rs  │
//!   └───────────────────┴───────────────────────┘
//!
//! Filtering is **async**: when the user types, `FilterEdit` ops update the
//! local filter string and history list immediately, then `app.rs` spawns a
//! debounced background search task (100 ms after the last keystroke).
//! The task sends back a `FileSelectorOp::SetResults` which this view applies.

use std::{collections::HashSet, path::{Path, PathBuf}};

use ratatui::{
    layout::{Constraint, Direction, Layout, Rect},
    style::Style,
    text::Span,
    widgets::Paragraph,
    Frame,
};

use crate::prelude::*;

fn build_reveal_queue(project_root: &Path, target: &Path) -> Vec<PathBuf> {
    let mut queue = Vec::new();

    if let Ok(rel) = target.strip_prefix(project_root)
        && let Some(parent) = rel.parent() {
            let mut cur = project_root.to_path_buf();
            for comp in parent.components() {
                cur = cur.join(comp.as_os_str());
                queue.push(cur.clone());
            }
        }
    queue
}

use file_index::SharedFileIndex;
use input::{Key, KeyEvent, MouseButton, MouseEvent, MouseEventKind};
use operation::{Event, FileSelectorOp, Operation};
use settings::Settings;
use utils::fuzzy::filter_and_rank;
use views::View;
use widgets::{
    file_tree::{path_list_item, FileTreeView},
    focus::FocusRing,
    list::SelectableList,
    pane::TitledPane,
};
use widgets::focusable::FocusOp;
use widgets::input_field::InputField;

const FOCUS_FILTER: &str = "filter";
const FOCUS_HISTORY: &str = "history";
const FOCUS_TREE: &str = "tree";

#[derive(Debug)]
pub struct FileSelector {
    pub project_root: PathBuf,
    pub filter: InputField,
    focus: FocusRing,

    history_all: Vec<PathBuf>,
    history_list: SelectableList<PathBuf>,

    tree_view: FileTreeView,
    /// Shared index built in the background; `None` while walk is in progress.
    file_index: SharedFileIndex,
    /// Current filtered + ranked results shown in the tree pane.
    tree_filtered: SelectableList<PathBuf>,
    /// Monotonically increasing generation counter.  Incremented on every
    /// `FilterEdit`; checked against incoming `SetResults` to discard stale
    /// results from superseded queries.
    pub search_generation: u64,
    /// True while a search task is in flight (between `FilterEdit` and `SetResults`).
    search_pending: bool,
    /// Set when the tree requested a lazy directory load.  `app.rs` drains
    /// this after dispatching the operation and spawns the async read task.
    pub pending_load: Option<PathBuf>,
    /// Queue of ancestor directories that must be loaded to reveal a target.
    reveal_queue: Vec<PathBuf>,
    /// Target path to reveal after expansion completes.
    reveal_target: Option<PathBuf>,
    /// Absolute paths of files that have unsaved modifications (from project state).
    dirty_paths: HashSet<PathBuf>,
}

impl FileSelector {
    pub fn new(project_root: PathBuf, history: &[PathBuf], file_index: SharedFileIndex, dirty_paths: HashSet<PathBuf>, initial_path: Option<PathBuf>) -> Self {
        let mut tree_view = FileTreeView::from_dir(&project_root);
        let history_all = history.to_vec();
        let history_list = SelectableList::new(history_all.clone());

        // Reveal logic: if an initial path is provided, precompute the ancestor
        // directories that must be loaded (top→bottom) and queue them. Do NOT
        // change the filter text or history list.
        let mut reveal_queue: Vec<PathBuf> = Vec::new();
        let mut reveal_target: Option<PathBuf> = None;
        let mut pending_load_val: Option<PathBuf> = None;
        if let Some(init) = initial_path.clone()
            && init.exists() && init.starts_with(&project_root) {
                reveal_target = Some(init.clone());
                reveal_queue = build_reveal_queue(&project_root, &init);
                if !reveal_queue.is_empty() {
                    pending_load_val = Some(reveal_queue.remove(0));
                } else {
                    // No directories need loading; attempt to set cursor immediately.
                    let _ = tree_view.set_cursor_to(&init);
                }
            }

        Self {
            project_root,
            filter: InputField::new("Filter"),
            focus: FocusRing::new(vec![FOCUS_FILTER, FOCUS_HISTORY, FOCUS_TREE]),
            history_all,
            history_list,
            tree_view,
            file_index,
            tree_filtered: SelectableList::new(vec![]),
            search_generation: 0,
            search_pending: false,
            pending_load: pending_load_val,
            reveal_queue,
            reveal_target,
            dirty_paths,
        }
    }

    /// Which content pane is currently focused (history or tree).
    fn active_pane(&self) -> &str {
        let cur = self.focus.current();
        if cur == FOCUS_HISTORY {
            FOCUS_HISTORY
        } else if cur == FOCUS_TREE {
            FOCUS_TREE
        } else {
            // When filter is focused, default to history for selection purposes
            FOCUS_HISTORY
        }
    }

    pub fn selected_path(&self) -> Option<PathBuf> {
        match self.active_pane() {
            FOCUS_HISTORY => {
                let path = self.history_list.selected()?;
                // External files are stored as absolute paths; project files are relative.
                if path.is_absolute() {
                    Some(path.clone())
                } else {
                    Some(self.project_root.join(path))
                }
            }
            _ => {
                if self.filter.is_empty() {
                    self.tree_view.selected_file()
                } else {
                    let rel = self.tree_filtered.selected()?;
                    Some(self.project_root.join(rel))
                }
            }
        }
    }

    /// Internal accessor used by tests to observe the tree's selected file even
    /// when the filter is focused. This does not change runtime behavior.
    #[cfg(test)]
    pub(crate) fn tree_selected_file(&self) -> Option<PathBuf> {
        self.tree_view.selected_file()
    }


    fn render_history_pane(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        let pane = TitledPane::new("Recent Files", self.focus.is_focused(FOCUS_HISTORY));
        let content = pane.prepare(frame, area, theme);
        let pane_bg = pane.bg(theme);
        let (sel_bg, sel_fg, fg_dim) = (theme.selection_bg(), theme.selection_fg(), theme.fg_dim());
        frame.render_widget(
            self.history_list.widget(|path, selected| {
                let abs = self.project_root.join(path);
                let dirty = self.dirty_paths.contains(&abs);
                let max_w = content.width as usize;
                path_list_item(path, selected, dirty, pane_bg, sel_bg, sel_fg, fg_dim, max_w)
            }),
            content,
        );
    }

    fn render_tree_pane(&self, frame: &mut Frame, area: Rect, theme: &crate::theme::Theme) {
        let is_active = self.focus.is_focused(FOCUS_TREE);
        let root_name = self
            .project_root
            .file_name()
            .map(|n| n.to_string_lossy().into_owned())
            .unwrap_or_else(|| self.project_root.display().to_string());
        let pane = TitledPane::new(&root_name, is_active);
        let content = pane.prepare(frame, area, theme);
        let pane_bg = pane.bg(theme);
        let (sel_bg, sel_fg, fg_dim) = (theme.selection_bg(), theme.selection_fg(), theme.fg_dim());

        if self.filter.is_empty() {
            self.tree_view.render(
                frame,
                content,
                is_active,
                pane_bg,
                sel_bg,
                sel_fg,
                theme.tree_dir(),
                theme.fg_dim(),
            );
        } else if self.tree_filtered.is_empty() {
            // "searching…" covers both: index still building AND search in flight.
            let is_busy = self.search_pending || {
                let guard = self.file_index.load();
                (**guard).is_none()
            };
            let msg = if is_busy {
                "  (searching\u{2026})"
            } else {
                "  (no matches)"
            };
            frame.render_widget(
                Paragraph::new(Span::styled(msg, Style::default().fg(fg_dim))),
                content,
            );
        } else {
            frame.render_widget(
                self.tree_filtered.widget(|path, selected| {
                    let max_w = content.width as usize;
                    path_list_item(path, selected, false, pane_bg, sel_bg, sel_fg, fg_dim, max_w)
                }),
                content,
            );
        }
    }
}

impl View for FileSelector {
    const KIND: crate::views::ViewKind = crate::views::ViewKind::Modal;

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

    /// Translate key input into operations — no mutation of self.
    fn handle_key(&self, key: KeyEvent) -> Vec<Operation> {
        match (key.modifiers, key.key) {
            (_, Key::Enter) => {
                if let Some(path) = self.selected_path() {
                    vec![Operation::OpenFile { path }]
                } else if self.active_pane() == FOCUS_TREE && self.filter.is_empty() {
                    vec![Operation::FileSelectorLocal(FileSelectorOp::ToggleDir)]
                } else {
                    vec![]
                }
            }

            (_, Key::Tab) => vec![Operation::Focus(FocusOp::Next)],
            (_, Key::BackTab) => vec![Operation::Focus(FocusOp::Prev)],

            (_, Key::ArrowUp) => vec![Operation::NavigateUp],
            (_, Key::ArrowDown) => vec![Operation::NavigateDown],
            (_, Key::PageUp) => vec![Operation::NavigatePageUp],
            (_, Key::PageDown) => vec![Operation::NavigatePageDown],

            (_, Key::ArrowLeft) if self.active_pane() == FOCUS_TREE && self.filter.is_empty() => {
                vec![Operation::FileSelectorLocal(FileSelectorOp::CollapseOrLeft)]
            }
            (_, Key::ArrowRight) if self.active_pane() == FOCUS_TREE && self.filter.is_empty() => {
                vec![Operation::FileSelectorLocal(FileSelectorOp::ExpandOrRight)]
            }

            (m, Key::Char(' '))
                if m.is_empty() && self.filter.is_empty() && self.active_pane() == FOCUS_TREE =>
            {
                vec![Operation::FileSelectorLocal(FileSelectorOp::ToggleDir)]
            }

            _ => {
                if let Some(field_op) = crate::widgets::input_field::key_to_op(key) {
                    vec![Operation::FileSelectorLocal(FileSelectorOp::FilterInput(field_op))]
                } else {
                    vec![]
                }
            }
        }
    }

    fn handle_mouse(&self, mouse: MouseEvent) -> Vec<Operation> {
        if !matches!(mouse.kind, MouseEventKind::Down(MouseButton::Left)) {
            return vec![];
        }

        let is_filter_row = mouse.row == 0;
        if is_filter_row {
            return vec![];
        }

        let is_left_pane = mouse.column < 80;
        if is_left_pane {
            if !self.focus.is_focused(FOCUS_HISTORY) {
                return vec![Operation::FileSelectorLocal(FileSelectorOp::SwitchPane)];
            }
        } else if !self.focus.is_focused(FOCUS_TREE) {
            return vec![Operation::FileSelectorLocal(FileSelectorOp::SwitchPane)];
        }

        vec![]
    }

    /// Apply operations this view owns.
    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("file_selector", op.clone()))
            }
            Operation::FileSelectorLocal(local_op) => {
                match local_op {
                    FileSelectorOp::FilterInput(field_op) => {
                        self.filter.apply(field_op);
                        self.search_generation += 1;
                        self.search_pending = !self.filter.is_empty();
                        let hist = filter_and_rank(&self.history_all, self.filter.text())
                            .into_iter()
                            .cloned()
                            .collect();
                        self.history_list.set_items(hist);
                    }
                    FileSelectorOp::SetResults { generation, paths } => {
                        if *generation == self.search_generation {
                            self.search_pending = false;
                            self.tree_filtered.set_items(paths.clone());
                        }
                    }
                    FileSelectorOp::SwitchPane => {
                        // Legacy: toggle between history and tree
                        let cur = self.focus.current();
                        if cur == FOCUS_HISTORY || cur == FOCUS_FILTER {
                            self.focus.set_focus(FOCUS_TREE);
                        } else {
                            self.focus.set_focus(FOCUS_HISTORY);
                        }
                    }
                    FileSelectorOp::CollapseOrLeft => self.tree_view.key_left(),
                    FileSelectorOp::ExpandOrRight => {
                        self.pending_load = self.tree_view.key_right();
                    }
                    FileSelectorOp::ToggleDir => {
                        self.pending_load = self.tree_view.toggle_selected();
                    }
                    FileSelectorOp::DirLoaded { path, entries } => {
                        self.tree_view.inject_children(path, entries.clone());
                        // If we were revealing a target, continue with the precomputed queue.
                        if self.reveal_target.is_some() {
                            if !self.reveal_queue.is_empty() {
                                let next = self.reveal_queue.remove(0);
                                self.pending_load = Some(next);
                            } else if let Some(target) = &self.reveal_target {
                                // Final step: set cursor to target.
                                if self.tree_view.set_cursor_to(target) {
                                    self.reveal_target = None;
                                    self.reveal_queue.clear();
                                }
                            }
                        }
                    }
                }
                Some(Event::applied("file_selector", op.clone()))
            }

            Operation::NavigateUp => {
                match self.active_pane() {
                    FOCUS_HISTORY => self.history_list.move_up(),
                    _ => {
                        if self.filter.is_empty() {
                            self.tree_view.key_up();
                        } else {
                            self.tree_filtered.move_up();
                        }
                    }
                }
                Some(Event::applied("file_selector", op.clone()))
            }
            Operation::NavigateDown => {
                match self.active_pane() {
                    FOCUS_HISTORY => self.history_list.move_down(),
                    _ => {
                        if self.filter.is_empty() {
                            self.tree_view.key_down();
                        } else {
                            self.tree_filtered.move_down();
                        }
                    }
                }
                Some(Event::applied("file_selector", op.clone()))
            }
            Operation::NavigatePageUp => {
                const PAGE: usize = 10;
                match self.active_pane() {
                    FOCUS_HISTORY => {
                        for _ in 0..PAGE {
                            self.history_list.move_up();
                        }
                    }
                    _ => {
                        if self.filter.is_empty() {
                            for _ in 0..PAGE {
                                self.tree_view.key_up();
                            }
                        } else {
                            for _ in 0..PAGE {
                                self.tree_filtered.move_up();
                            }
                        }
                    }
                }
                Some(Event::applied("file_selector", op.clone()))
            }
            Operation::NavigatePageDown => {
                const PAGE: usize = 10;
                match self.active_pane() {
                    FOCUS_HISTORY => {
                        for _ in 0..PAGE {
                            self.history_list.move_down();
                        }
                    }
                    _ => {
                        if self.filter.is_empty() {
                            for _ in 0..PAGE {
                                self.tree_view.key_down();
                            }
                        } else {
                            for _ in 0..PAGE {
                                self.tree_filtered.move_down();
                            }
                        }
                    }
                }
                Some(Event::applied("file_selector", op.clone()))
            }

            // Not this view's operation.
            _ => None,
        }
    }

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

        let filter_focused = self.focus.is_focused(FOCUS_FILTER);
        self.filter.render(frame, outer[0], filter_focused, theme);

        let panes = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([Constraint::Percentage(35), Constraint::Percentage(65)])
            .split(outer[1]);
        self.render_history_pane(frame, panes[0], theme);
        self.render_tree_pane(frame, panes[1], theme);
    }
}