#![allow(dead_code)] // Phase 2: consumed by Issue List + MR List screens

//! Filter bar widget for list screens.
//!
//! Wraps a text input with DSL parsing, inline diagnostics for unknown
//! fields, and rendered filter chips below the input.

use ftui::core::geometry::Rect;
use ftui::render::cell::{Cell, PackedRgba};
use ftui::render::drawing::Draw;
use ftui::render::frame::Frame;

use crate::filter_dsl::{self, FilterToken};

// ---------------------------------------------------------------------------
// Filter bar state
// ---------------------------------------------------------------------------

/// State for the filter bar widget.
#[derive(Debug, Clone, Default)]
pub struct FilterBarState {
    /// Current filter input text.
    pub input: String,
    /// Cursor position within the input string (byte offset).
    pub cursor: usize,
    /// Whether the filter bar has focus.
    pub focused: bool,
    /// Parsed tokens from the current input.
    pub tokens: Vec<FilterToken>,
    /// Fields that are unknown for the current entity type.
    pub unknown_fields: Vec<String>,
}

impl FilterBarState {
    /// Update parsed tokens from the current input text.
    pub fn reparse(&mut self, known_fields: &[&str]) {
        self.tokens = filter_dsl::parse_filter_tokens(&self.input);
        self.unknown_fields = filter_dsl::unknown_fields(&self.tokens, known_fields)
            .into_iter()
            .map(String::from)
            .collect();
    }

    /// Insert a character at the cursor position.
    pub fn insert_char(&mut self, ch: char) {
        if self.cursor > self.input.len() {
            self.cursor = self.input.len();
        }
        self.input.insert(self.cursor, ch);
        self.cursor += ch.len_utf8();
    }

    /// Delete the character before the cursor (backspace).
    pub fn delete_back(&mut self) {
        if self.cursor > 0 && !self.input.is_empty() {
            // Find the previous character boundary.
            let prev = self.input[..self.cursor]
                .char_indices()
                .next_back()
                .map(|(i, _)| i)
                .unwrap_or(0);
            self.input.remove(prev);
            self.cursor = prev;
        }
    }

    /// Delete the character at the cursor (delete key).
    pub fn delete_forward(&mut self) {
        if self.cursor < self.input.len() {
            self.input.remove(self.cursor);
        }
    }

    /// Move cursor left by one character.
    pub fn move_left(&mut self) {
        if self.cursor > 0 {
            self.cursor = self.input[..self.cursor]
                .char_indices()
                .next_back()
                .map(|(i, _)| i)
                .unwrap_or(0);
        }
    }

    /// Move cursor right by one character.
    pub fn move_right(&mut self) {
        if self.cursor < self.input.len() {
            self.cursor = self.input[self.cursor..]
                .chars()
                .next()
                .map(|ch| self.cursor + ch.len_utf8())
                .unwrap_or(self.input.len());
        }
    }

    /// Move cursor to start.
    pub fn move_home(&mut self) {
        self.cursor = 0;
    }

    /// Move cursor to end.
    pub fn move_end(&mut self) {
        self.cursor = self.input.len();
    }

    /// Clear the input.
    pub fn clear(&mut self) {
        self.input.clear();
        self.cursor = 0;
        self.tokens.clear();
        self.unknown_fields.clear();
    }

    /// Whether the filter has any active tokens.
    pub fn is_active(&self) -> bool {
        !self.tokens.is_empty()
    }
}

// ---------------------------------------------------------------------------
// Colors
// ---------------------------------------------------------------------------

/// Colors for the filter bar.
pub struct FilterBarColors {
    pub input_fg: PackedRgba,
    pub input_bg: PackedRgba,
    pub cursor_fg: PackedRgba,
    pub cursor_bg: PackedRgba,
    pub chip_fg: PackedRgba,
    pub chip_bg: PackedRgba,
    pub error_fg: PackedRgba,
    pub label_fg: PackedRgba,
}

// ---------------------------------------------------------------------------
// Render
// ---------------------------------------------------------------------------

/// Render the filter bar.
///
/// Layout:
/// ```text
/// Row 0: [Filter: ][input text with cursor___________]
/// Row 1: [chip1] [chip2] [chip3]    (if tokens present)
/// ```
///
/// Returns the number of rows consumed (1 or 2).
pub fn render_filter_bar(
    frame: &mut Frame<'_>,
    state: &FilterBarState,
    area: Rect,
    colors: &FilterBarColors,
) -> u16 {
    if area.height == 0 || area.width < 10 {
        return 0;
    }

    let max_x = area.x.saturating_add(area.width);
    let y = area.y;

    // Label.
    let label = if state.focused { "Filter: " } else { "/ " };
    let label_cell = Cell {
        fg: colors.label_fg,
        ..Cell::default()
    };
    let after_label = frame.print_text_clipped(area.x, y, label, label_cell, max_x);

    // Input text.
    let input_cell = Cell {
        fg: colors.input_fg,
        bg: if state.focused {
            colors.input_bg
        } else {
            Cell::default().bg
        },
        ..Cell::default()
    };

    if state.input.is_empty() && !state.focused {
        let muted = Cell {
            fg: colors.label_fg,
            ..Cell::default()
        };
        frame.print_text_clipped(after_label, y, "type / to filter", muted, max_x);
    } else {
        // Render input text with cursor highlight.
        render_input_with_cursor(frame, state, after_label, y, max_x, input_cell, colors);
    }

    // Error indicators for unknown fields.
    if !state.unknown_fields.is_empty() {
        let err_cell = Cell {
            fg: colors.error_fg,
            ..Cell::default()
        };
        let err_msg = format!("Unknown: {}", state.unknown_fields.join(", "));
        // Right-align the error.
        let err_x = max_x.saturating_sub(err_msg.len() as u16 + 1);
        frame.print_text_clipped(err_x, y, &err_msg, err_cell, max_x);
    }

    // Chip row (if tokens present and space available).
    if !state.tokens.is_empty() && area.height >= 2 {
        let chip_y = y.saturating_add(1);
        render_chips(frame, &state.tokens, area.x, chip_y, max_x, colors);
        return 2;
    }

    1
}

/// Render input text with cursor highlight at the correct position.
fn render_input_with_cursor(
    frame: &mut Frame<'_>,
    state: &FilterBarState,
    start_x: u16,
    y: u16,
    max_x: u16,
    base_cell: Cell,
    colors: &FilterBarColors,
) {
    if !state.focused {
        frame.print_text_clipped(start_x, y, &state.input, base_cell, max_x);
        return;
    }

    // Split at cursor position.
    let cursor = state.cursor;
    let input = &state.input;
    let (before, after) = if cursor <= input.len() {
        (&input[..cursor], &input[cursor..])
    } else {
        (input.as_str(), "")
    };

    let mut x = frame.print_text_clipped(start_x, y, before, base_cell, max_x);

    // Cursor character (or space if at end).
    let cursor_cell = Cell {
        fg: colors.cursor_fg,
        bg: colors.cursor_bg,
        ..Cell::default()
    };

    if let Some(ch) = after.chars().next() {
        let s = String::from(ch);
        x = frame.print_text_clipped(x, y, &s, cursor_cell, max_x);
        let remaining = &after[ch.len_utf8()..];
        frame.print_text_clipped(x, y, remaining, base_cell, max_x);
    } else {
        // Cursor at end — render a visible block.
        frame.print_text_clipped(x, y, " ", cursor_cell, max_x);
    }
}

/// Render filter chips as compact tags.
fn render_chips(
    frame: &mut Frame<'_>,
    tokens: &[FilterToken],
    start_x: u16,
    y: u16,
    max_x: u16,
    colors: &FilterBarColors,
) {
    let chip_cell = Cell {
        fg: colors.chip_fg,
        bg: colors.chip_bg,
        ..Cell::default()
    };

    let mut x = start_x;

    for token in tokens {
        if x >= max_x {
            break;
        }

        let label = match token {
            FilterToken::FieldValue { field, value } => format!("{field}:{value}"),
            FilterToken::Negation { field, value } => format!("-{field}:{value}"),
            FilterToken::FreeText(text) => text.clone(),
            FilterToken::QuotedValue(text) => format!("\"{text}\""),
        };

        let chip_text = format!("[{label}]");
        x = frame.print_text_clipped(x, y, &chip_text, chip_cell, max_x);
        x = x.saturating_add(1); // gap between chips
    }
}

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

#[cfg(test)]
mod tests {
    use super::*;
    use crate::filter_dsl::ISSUE_FIELDS;
    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 test_colors() -> FilterBarColors {
        FilterBarColors {
            input_fg: PackedRgba::rgb(0xCE, 0xCD, 0xC3),
            input_bg: PackedRgba::rgb(0x28, 0x28, 0x24),
            cursor_fg: PackedRgba::rgb(0x00, 0x00, 0x00),
            cursor_bg: PackedRgba::rgb(0xCE, 0xCD, 0xC3),
            chip_fg: PackedRgba::rgb(0xCE, 0xCD, 0xC3),
            chip_bg: PackedRgba::rgb(0x40, 0x40, 0x3C),
            error_fg: PackedRgba::rgb(0xAF, 0x3A, 0x29),
            label_fg: PackedRgba::rgb(0x87, 0x87, 0x80),
        }
    }

    #[test]
    fn test_filter_bar_state_insert_char() {
        let mut state = FilterBarState::default();
        state.insert_char('a');
        state.insert_char('b');
        assert_eq!(state.input, "ab");
        assert_eq!(state.cursor, 2);
    }

    #[test]
    fn test_filter_bar_state_delete_back() {
        let mut state = FilterBarState {
            input: "abc".into(),
            cursor: 3,
            ..Default::default()
        };
        state.delete_back();
        assert_eq!(state.input, "ab");
        assert_eq!(state.cursor, 2);
    }

    #[test]
    fn test_filter_bar_state_delete_back_at_start() {
        let mut state = FilterBarState {
            input: "abc".into(),
            cursor: 0,
            ..Default::default()
        };
        state.delete_back();
        assert_eq!(state.input, "abc");
        assert_eq!(state.cursor, 0);
    }

    #[test]
    fn test_filter_bar_state_move_left_right() {
        let mut state = FilterBarState {
            input: "abc".into(),
            cursor: 2,
            ..Default::default()
        };
        state.move_left();
        assert_eq!(state.cursor, 1);
        state.move_right();
        assert_eq!(state.cursor, 2);
    }

    #[test]
    fn test_filter_bar_state_home_end() {
        let mut state = FilterBarState {
            input: "hello".into(),
            cursor: 3,
            ..Default::default()
        };
        state.move_home();
        assert_eq!(state.cursor, 0);
        state.move_end();
        assert_eq!(state.cursor, 5);
    }

    #[test]
    fn test_filter_bar_state_clear() {
        let mut state = FilterBarState {
            input: "state:opened".into(),
            cursor: 12,
            tokens: vec![FilterToken::FieldValue {
                field: "state".into(),
                value: "opened".into(),
            }],
            ..Default::default()
        };
        state.clear();
        assert!(state.input.is_empty());
        assert_eq!(state.cursor, 0);
        assert!(state.tokens.is_empty());
    }

    #[test]
    fn test_filter_bar_state_reparse() {
        let mut state = FilterBarState {
            input: "state:opened bogus:val".into(),
            ..Default::default()
        };
        state.reparse(ISSUE_FIELDS);
        assert_eq!(state.tokens.len(), 2);
        assert_eq!(state.unknown_fields, vec!["bogus"]);
    }

    #[test]
    fn test_filter_bar_state_is_active() {
        let mut state = FilterBarState::default();
        assert!(!state.is_active());

        state.input = "state:opened".into();
        state.reparse(ISSUE_FIELDS);
        assert!(state.is_active());
    }

    #[test]
    fn test_render_filter_bar_unfocused_no_panic() {
        with_frame!(80, 2, |frame| {
            let state = FilterBarState::default();
            let colors = test_colors();
            let rows = render_filter_bar(&mut frame, &state, Rect::new(0, 0, 80, 2), &colors);
            assert_eq!(rows, 1);
        });
    }

    #[test]
    fn test_render_filter_bar_focused_no_panic() {
        with_frame!(80, 2, |frame| {
            let mut state = FilterBarState {
                input: "state:opened".into(),
                cursor: 12,
                focused: true,
                ..Default::default()
            };
            state.reparse(ISSUE_FIELDS);
            let colors = test_colors();
            let rows = render_filter_bar(&mut frame, &state, Rect::new(0, 0, 80, 2), &colors);
            assert_eq!(rows, 2); // chips rendered
        });
    }

    #[test]
    fn test_render_filter_bar_tiny_noop() {
        with_frame!(5, 1, |frame| {
            let state = FilterBarState::default();
            let colors = test_colors();
            let rows = render_filter_bar(&mut frame, &state, Rect::new(0, 0, 5, 1), &colors);
            assert_eq!(rows, 0);
        });
    }

    #[test]
    fn test_filter_bar_unicode_cursor() {
        let mut state = FilterBarState {
            input: "author:田中".into(),
            cursor: 7, // points at start of 田
            ..Default::default()
        };
        state.move_right();
        assert_eq!(state.cursor, 10); // past 田 (3 bytes)
        state.move_left();
        assert_eq!(state.cursor, 7); // back to 田
    }
}
