use anyhow::Result;
use std::io::Write as _;

pub fn fuzzy_match(query: &str, text: &str) -> bool {
    let query = query.to_lowercase();
    let text = text.to_lowercase();
    let mut chars = query.chars().peekable();
    for c in text.chars() {
        if chars.peek() == Some(&c) {
            chars.next();
        }
    }
    chars.peek().is_none()
}

pub struct PickItem {
    pub label: String,
    pub description: String,
    pub preview: Option<String>,
}

pub fn pick_from_list(items: &[PickItem], footer_hint: &str) -> Result<Option<usize>> {
    use crossterm::{event, terminal};
    use std::io::stdout;

    let filtered = |query: &str| -> Vec<usize> {
        if query.is_empty() {
            return (0..items.len()).collect();
        }
        items
            .iter()
            .enumerate()
            .filter(|(_, item)| {
                fuzzy_match(query, &item.label) || fuzzy_match(query, &item.description)
            })
            .map(|(i, _)| i)
            .collect()
    };

    terminal::enable_raw_mode()?;
    let result = (|| -> Result<Option<usize>> {
        let mut query = String::new();
        let mut cursor_idx: usize = 0;
        let mut prev_lines: u16 = 0;

        loop {
            let matches = filtered(&query);
            if cursor_idx >= matches.len() {
                cursor_idx = matches.len().saturating_sub(1);
            }

            let mut out = stdout();

            if prev_lines > 0 {
                write!(out, "\x1b[{}A\r", prev_lines)?;
                write!(out, "\x1b[J")?;
            }

            let mut lines: u16 = 0;

            write!(
                out,
                "\x1b[36m◆\x1b[0m  Start typing to match: \x1b[4m{}\x1b[0m\r\n",
                query
            )?;
            lines += 1;

            for (i, &idx) in matches.iter().enumerate() {
                let item = &items[idx];
                let desc = if item.description.is_empty() {
                    String::new()
                } else {
                    format!(" \x1b[2m{}\x1b[0m", item.description)
                };
                if i == cursor_idx {
                    write!(
                        out,
                        "\x1b[36m│\x1b[0m  \x1b[36m●\x1b[0m \x1b[1m{}\x1b[0m{desc}\r\n",
                        item.label
                    )?;
                } else {
                    write!(out, "\x1b[36m│\x1b[0m  ○ {}{desc}\r\n", item.label)?;
                }
                lines += 1;
            }

            if matches.is_empty() {
                write!(out, "\x1b[36m│\x1b[0m  \x1b[2mNo matches\x1b[0m\r\n")?;
                lines += 1;
            }

            if let Some(&idx) = matches.get(cursor_idx)
                && let Some(ref preview) = items[idx].preview
            {
                write!(out, "\x1b[36m│\x1b[0m\r\n")?;
                lines += 1;
                for line in preview.lines() {
                    write!(out, "\x1b[36m│\x1b[0m  \x1b[33m{line}\x1b[0m\r\n")?;
                    lines += 1;
                }
            }

            write!(out, "\x1b[36m│\x1b[0m\r\n")?;
            write!(out, "\x1b[36m└\x1b[0m  \x1b[2m{footer_hint}\x1b[0m\r\n")?;
            lines += 2;

            out.flush()?;
            prev_lines = lines;

            if let event::Event::Key(key) = event::read()? {
                use event::{KeyCode, KeyModifiers};
                match key.code {
                    KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                        return Ok(None);
                    }
                    KeyCode::Esc => return Ok(None),
                    KeyCode::Enter => {
                        if let Some(&idx) = matches.get(cursor_idx) {
                            return Ok(Some(idx));
                        }
                    }
                    KeyCode::Up => {
                        cursor_idx = cursor_idx.saturating_sub(1);
                    }
                    KeyCode::Down => {
                        if !matches.is_empty() {
                            cursor_idx = (cursor_idx + 1).min(matches.len() - 1);
                        }
                    }
                    KeyCode::Backspace => {
                        query.pop();
                        cursor_idx = 0;
                    }
                    KeyCode::Char(c) => {
                        query.push(c);
                        cursor_idx = 0;
                    }
                    _ => {}
                }
            }
        }
    })();

    terminal::disable_raw_mode()?;
    print!("\x1b[J");

    result
}
