#![allow(dead_code)] // Phase 3: consumed by view/mod.rs screen dispatch

//! Search screen view — query bar, mode indicator, and results list.
//!
//! Layout:
//! ```text
//! +--[ FTS ]--- Search ──────────────────────+
//! | > query text here_                        |
//! +───────────────────────────────────────────+
//! | #42  Fix login bug          group/proj    |
//! | !99  Add retry logic        group/proj    |
//! | #10  Update docs            other/repo    |
//! +───────────────────────────────────────────+
//! | Tab: mode  /: focus  j/k: nav  Enter: go |
//! +───────────────────────────────────────────+
//! ```

use ftui::core::geometry::Rect;
use ftui::render::cell::Cell;
use ftui::render::drawing::Draw;
use ftui::render::frame::Frame;

use crate::message::EntityKind;
use crate::state::search::SearchState;
use crate::text_width::cursor_cell_offset;

use super::{ACCENT, BG_SURFACE, BORDER, TEXT, TEXT_MUTED};

// ---------------------------------------------------------------------------
// render_search
// ---------------------------------------------------------------------------

/// Render the search screen.
///
/// Composes: mode indicator + query bar (row 0), separator (row 1),
/// results list (fill), and a hint bar at the bottom.
pub fn render_search(frame: &mut Frame<'_>, state: &SearchState, area: Rect) {
    if area.height < 4 || area.width < 20 {
        return;
    }

    let mut y = area.y;
    let max_x = area.right();

    // -- Mode indicator + query bar ------------------------------------------
    y = render_query_bar(frame, state, area.x, y, area.width, max_x);

    // -- Separator -----------------------------------------------------------
    if y >= area.bottom() {
        return;
    }
    let sep_cell = Cell {
        fg: BORDER,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    let sep_line = "─".repeat(area.width as usize);
    frame.print_text_clipped(area.x, y, &sep_line, sep_cell, max_x);
    y += 1;

    // -- No-index warning ----------------------------------------------------
    if !state.capabilities.has_any_index() {
        if y >= area.bottom() {
            return;
        }
        let warn_cell = Cell {
            fg: ACCENT,
            bg: BG_SURFACE,
            ..Cell::default()
        };
        frame.print_text_clipped(area.x + 1, y, "No search indexes found.", warn_cell, max_x);
        y += 1;
        if y < area.bottom() {
            let hint_cell = Cell {
                fg: TEXT_MUTED,
                bg: BG_SURFACE,
                ..Cell::default()
            };
            frame.print_text_clipped(
                area.x + 1,
                y,
                "Run: lore generate-docs && lore embed",
                hint_cell,
                max_x,
            );
        }
        return;
    }

    // -- Results list --------------------------------------------------------
    let bottom_hint_row = area.bottom().saturating_sub(1);
    let list_bottom = bottom_hint_row;
    let list_height = list_bottom.saturating_sub(y) as usize;

    if list_height == 0 {
        return;
    }

    if state.results.is_empty() {
        render_empty_state(frame, state, area.x + 1, y, max_x);
    } else {
        render_result_list(frame, state, area.x, y, area.width, list_height);
    }

    // -- Bottom hint bar -----------------------------------------------------
    if bottom_hint_row < area.bottom() {
        render_hint_bar(frame, state, area.x, bottom_hint_row, max_x);
    }
}

// ---------------------------------------------------------------------------
// Query bar
// ---------------------------------------------------------------------------

/// Render the mode badge and query input. Returns the next y position.
fn render_query_bar(
    frame: &mut Frame<'_>,
    state: &SearchState,
    x: u16,
    y: u16,
    width: u16,
    max_x: u16,
) -> u16 {
    // Mode badge: [ FTS ] or [ Hybrid ] or [ Vec ]
    let mode_label = format!("[ {} ]", state.mode.label());
    let mode_cell = Cell {
        fg: ACCENT,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    let after_mode = frame.print_text_clipped(x, y, &mode_label, mode_cell, max_x);

    // Space separator.
    let after_sep = frame.print_text_clipped(
        after_mode,
        y,
        " ",
        Cell {
            bg: BG_SURFACE,
            ..Cell::default()
        },
        max_x,
    );

    // Prompt.
    let prompt = "> ";
    let prompt_cell = Cell {
        fg: ACCENT,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    let after_prompt = frame.print_text_clipped(after_sep, y, prompt, prompt_cell, max_x);

    // Query text (or placeholder).
    let (display_text, text_fg) = if state.query.is_empty() {
        ("Type to search...", TEXT_MUTED)
    } else {
        (state.query.as_str(), TEXT)
    };
    let text_cell = Cell {
        fg: text_fg,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    frame.print_text_clipped(after_prompt, y, display_text, text_cell, max_x);

    // Cursor (only when focused and has query text).
    if state.query_focused && !state.query.is_empty() {
        let cursor_x = after_prompt + cursor_cell_offset(&state.query, state.cursor);
        if cursor_x < max_x {
            let cursor_cell = Cell {
                fg: BG_SURFACE,
                bg: TEXT,
                ..Cell::default()
            };
            let cursor_char = state
                .query
                .get(state.cursor..)
                .and_then(|s| s.chars().next())
                .unwrap_or(' ');
            frame.print_text_clipped(cursor_x, y, &cursor_char.to_string(), cursor_cell, max_x);
        }
    }

    // Loading indicator (right-aligned).
    if state.loading {
        let loading_text = " searching... ";
        let loading_x = (x + width).saturating_sub(loading_text.len() as u16);
        let loading_cell = Cell {
            fg: TEXT_MUTED,
            bg: BG_SURFACE,
            ..Cell::default()
        };
        frame.print_text_clipped(loading_x, y, loading_text, loading_cell, max_x);
    }

    y + 1
}

// ---------------------------------------------------------------------------
// Empty state
// ---------------------------------------------------------------------------

/// Show a message when there are no results.
fn render_empty_state(frame: &mut Frame<'_>, state: &SearchState, x: u16, y: u16, max_x: u16) {
    let msg = if state.query.is_empty() {
        "Enter a search query above"
    } else {
        "No results found"
    };
    let cell = Cell {
        fg: TEXT_MUTED,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    frame.print_text_clipped(x, y, msg, cell, max_x);
}

// ---------------------------------------------------------------------------
// Result list
// ---------------------------------------------------------------------------

/// Render the scrollable list of search results.
fn render_result_list(
    frame: &mut Frame<'_>,
    state: &SearchState,
    x: u16,
    start_y: u16,
    width: u16,
    list_height: usize,
) {
    let max_x = x + width;

    // Scroll so selected item is always visible.
    let scroll_offset = if state.selected_index >= list_height {
        state.selected_index - list_height + 1
    } else {
        0
    };

    let normal = Cell {
        fg: TEXT,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    let selected = Cell {
        fg: BG_SURFACE,
        bg: ACCENT,
        ..Cell::default()
    };
    let muted = Cell {
        fg: TEXT_MUTED,
        bg: BG_SURFACE,
        ..Cell::default()
    };
    let muted_selected = Cell {
        fg: BG_SURFACE,
        bg: ACCENT,
        ..Cell::default()
    };

    for (i, result) in state
        .results
        .iter()
        .skip(scroll_offset)
        .enumerate()
        .take(list_height)
    {
        let y = start_y + i as u16;
        let is_selected = i + scroll_offset == state.selected_index;

        let (label_style, detail_style) = if is_selected {
            (selected, muted_selected)
        } else {
            (normal, muted)
        };

        // Fill row background for selected item.
        if is_selected {
            for col in x..max_x {
                frame.buffer.set(col, y, selected);
            }
        }

        // Entity prefix: # for issues, ! for MRs.
        let prefix = match result.key.kind {
            EntityKind::Issue => "#",
            EntityKind::MergeRequest => "!",
        };
        let iid_str = format!("{}{}", prefix, result.key.iid);
        let after_iid = frame.print_text_clipped(x + 1, y, &iid_str, label_style, max_x);

        // Title.
        let after_title =
            frame.print_text_clipped(after_iid + 1, y, &result.title, label_style, max_x);

        // Project path (right-aligned).
        let path_width = result.project_path.len() as u16 + 2;
        let path_x = max_x.saturating_sub(path_width);
        if path_x > after_title + 1 {
            frame.print_text_clipped(path_x, y, &result.project_path, detail_style, max_x);
        }
    }

    // Scroll indicator (overlaid on last visible row when results overflow).
    if state.results.len() > list_height && list_height > 0 {
        let indicator = format!(
            " {}/{} ",
            (scroll_offset + list_height).min(state.results.len()),
            state.results.len()
        );
        let ind_x = max_x.saturating_sub(indicator.len() as u16);
        let ind_y = start_y + list_height.saturating_sub(1) as u16;
        let ind_cell = Cell {
            fg: TEXT_MUTED,
            bg: BG_SURFACE,
            ..Cell::default()
        };
        frame.print_text_clipped(ind_x, ind_y, &indicator, ind_cell, max_x);
    }
}

// ---------------------------------------------------------------------------
// Hint bar
// ---------------------------------------------------------------------------

/// Render keybinding hints at the bottom of the search screen.
fn render_hint_bar(frame: &mut Frame<'_>, state: &SearchState, x: u16, y: u16, max_x: u16) {
    let hint_cell = Cell {
        fg: TEXT_MUTED,
        bg: BG_SURFACE,
        ..Cell::default()
    };

    let hints = if state.query_focused {
        "Tab: mode  Esc: blur  Enter: search"
    } else {
        "Tab: mode  /: focus  j/k: nav  Enter: open"
    };

    frame.print_text_clipped(x + 1, y, hints, hint_cell, max_x);
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;
    use crate::message::{EntityKey, SearchResult};
    use crate::state::search::{SearchCapabilities, SearchState};
    use ftui::render::grapheme_pool::GraphemePool;

    macro_rules! with_frame {
        ($width:expr, $height:expr, |$frame:ident| $body:block) => {{
            let mut pool = GraphemePool::new();
            let mut $frame = Frame::new($width, $height, &mut pool);
            $body
        }};
    }

    fn fts_caps() -> SearchCapabilities {
        SearchCapabilities {
            has_fts: true,
            has_embeddings: false,
            embedding_coverage_pct: 0.0,
        }
    }

    fn sample_results(count: usize) -> Vec<SearchResult> {
        (0..count)
            .map(|i| SearchResult {
                key: EntityKey::issue(1, (i + 1) as i64),
                title: format!("Result {}", i + 1),
                score: 1.0 - (i as f64 * 0.1),
                snippet: "matched text".into(),
                project_path: "group/project".into(),
            })
            .collect()
    }

    #[test]
    fn test_render_search_empty_no_panic() {
        with_frame!(80, 24, |frame| {
            let state = SearchState::default();
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 24));
        });
    }

    #[test]
    fn test_render_search_with_capabilities_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 24));
        });
    }

    #[test]
    fn test_render_search_with_results_no_panic() {
        with_frame!(100, 30, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            state.results = sample_results(5);
            render_search(&mut frame, &state, Rect::new(0, 0, 100, 30));
        });
    }

    #[test]
    fn test_render_search_with_query_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            state.insert_char('h');
            state.insert_char('e');
            state.insert_char('l');
            state.insert_char('l');
            state.insert_char('o');
            state.results = sample_results(3);
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 24));
        });
    }

    #[test]
    fn test_render_search_with_selection_no_panic() {
        with_frame!(80, 24, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            state.results = sample_results(10);
            state.select_next();
            state.select_next();
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 24));
        });
    }

    #[test]
    fn test_render_search_tiny_terminal_noop() {
        with_frame!(15, 3, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            render_search(&mut frame, &state, Rect::new(0, 0, 15, 3));
        });
    }

    #[test]
    fn test_render_search_no_indexes_warning() {
        with_frame!(80, 24, |frame| {
            let state = SearchState::default();
            // capabilities are default (no indexes)
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 24));
            // Should show "No search indexes found" without panicking.
        });
    }

    #[test]
    fn test_render_search_loading_indicator() {
        with_frame!(80, 24, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            state.loading = true;
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 24));
        });
    }

    #[test]
    fn test_render_search_scrollable_results() {
        with_frame!(80, 10, |frame| {
            let mut state = SearchState::default();
            state.enter(fts_caps());
            state.results = sample_results(20);
            // Select item near the bottom to trigger scrolling.
            for _ in 0..15 {
                state.select_next();
            }
            render_search(&mut frame, &state, Rect::new(0, 0, 80, 10));
        });
    }
}
