use std::{
    cmp::min,
    path::{Path, PathBuf},
};

use egui::{Key, Modifiers, RichText, Ui, Window};
use eyre::eyre;

use crate::{
    app::{Action, Jobs},
    file_editor::FileEditor,
    folder::Folder,
    util::log_error,
};

pub struct CommandPalette {
    first_frame: bool,
    input: String,
    results: Vec<OpenFileCommand>,
    list_i: usize,
}

impl Default for CommandPalette {
    fn default() -> Self {
        Self {
            first_frame: true,
            input: Default::default(),
            results: Default::default(),
            list_i: 0,
        }
    }
}

struct OpenFileCommand {
    pretty: String,
    path: PathBuf,
}

pub enum Response {
    None,
    Close,
}

impl CommandPalette {
    pub fn show(
        &mut self,
        ctx: &egui::Context,
        jobs: &mut Jobs,
        folders: &mut [Folder],
    ) -> Response {
        Window::new("Command Palette")
            .collapsible(false)
            .show(ctx, |ui| {
                let text_edit = ui.text_edit_singleline(&mut self.input);
                let mut response = Response::None;
                if self.first_frame {
                    self.first_frame = false;
                    text_edit.request_focus();
                }

                if text_edit.has_focus() || text_edit.lost_focus() {
                    ui.input_mut(|input| {
                        if input.consume_key(Modifiers::NONE, Key::ArrowUp) {
                            self.list_i = self.list_i.saturating_sub(1);
                        };
                        if input.consume_key(Modifiers::NONE, Key::ArrowDown) {
                            self.list_i =
                                min(self.list_i + 1, self.results.len().saturating_sub(1));
                        };
                        if input.consume_key(Modifiers::NONE, Key::Enter)
                            && let Some(result) = self.results.get(self.list_i)
                        {
                            let path = result.path.clone();
                            log::info!("open {}", result.pretty);
                            jobs.start(ui.ctx(), move || {
                                log_error(eyre!("Failed to open file {path:?}"), move || {
                                    FileEditor::open_file(path)
                                })
                                .map(Action::OpenFile)
                            });
                        };
                    });
                }

                if !text_edit.has_focus() {
                    log::info!("sad face");
                    response = Response::Close;
                }

                if text_edit.changed() {
                    self.results.clear();

                    if !self.input.is_empty() {
                        self.input = self.input.to_lowercase();
                        search_files(ui, &self.input, "".as_ref(), folders, &mut self.results);
                    }

                    self.list_i = min(self.list_i, self.results.len().saturating_sub(1));
                }

                for (i, result) in self.results.iter().enumerate() {
                    let highlight = i == self.list_i;
                    ui.horizontal(|ui| {
                        let mut label = RichText::new("> open");
                        if highlight {
                            label = label.strong().underline();
                        }
                        ui.label(label);
                        ui.monospace(&result.pretty);
                    });
                }

                response
            })
            .expect("Window is always open")
            .inner
            .expect("Windows is never collapsed")
    }
}

/// Search the tree of [Folder]s for files matching `query`.
fn search_files(
    ui: &mut Ui,
    query: &str,
    path: &Path,
    folders: &mut [Folder],
    out: &mut Vec<OpenFileCommand>,
) {
    for folder in folders {
        let Some(folder) = folder.load(ui) else {
            continue;
        };

        let path = path.join(&folder.name);

        for file in &folder.child_files {
            if file.name.to_lowercase().contains(query) {
                let pretty = path.join(&file.name).to_string_lossy().to_string();
                let path = file.path.clone();
                out.push(OpenFileCommand { pretty, path });
            }
        }

        search_files(ui, query, &path, &mut folder.child_folders, out);
    }
}
