//! Interactive dot menu for managing dotfile repositories

pub mod add_repo;
pub mod repo_actions;
mod subdir_actions;

use anyhow::Result;

use crate::dot::config::DotfileConfig;
use crate::dot::db::Database;
use crate::menu_utils::{FzfResult, FzfSelectable, FzfWrapper, Header, MenuCursor};
use crate::ui::catppuccin::{colors, format_back_icon, format_icon_colored, fzf_mocha_args};
use crate::ui::nerd_font::NerdFont;
use crate::ui::preview::PreviewBuilder;

use add_repo::handle_add_repo;
use repo_actions::{build_repo_preview, handle_repo_actions};

/// Menu entry for dotfile main menu
#[derive(Debug, Clone)]
pub enum DotMenuEntry {
    Repo(String, bool), // name, is_external
    AddRepo,
    AlternateFiles,
    GlobalUnits,
    CloseMenu,
}

impl FzfSelectable for DotMenuEntry {
    fn fzf_display_text(&self) -> String {
        match self {
            DotMenuEntry::Repo(name, is_external) => {
                let badge = if *is_external {
                    " \x1b[33m[external]\x1b[0m".to_string()
                } else {
                    String::new()
                };
                format!(
                    "{} {}{}",
                    format_icon_colored(NerdFont::Folder, colors::MAUVE),
                    name,
                    badge
                )
            }
            DotMenuEntry::AddRepo => {
                format!(
                    "{} Add Repo",
                    format_icon_colored(NerdFont::Plus, colors::GREEN)
                )
            }
            DotMenuEntry::AlternateFiles => {
                format!(
                    "{} Alternate Files",
                    format_icon_colored(NerdFont::List, colors::PEACH)
                )
            }
            DotMenuEntry::GlobalUnits => format!(
                "{} Global Units",
                format_icon_colored(NerdFont::FolderConfig, colors::TEAL)
            ),
            DotMenuEntry::CloseMenu => format!("{} Close Menu", format_back_icon()),
        }
    }

    fn fzf_key(&self) -> String {
        match self {
            DotMenuEntry::Repo(name, _) => name.clone(),
            DotMenuEntry::AddRepo => "!__add_repo__".to_string(),
            DotMenuEntry::AlternateFiles => "!__alternate_files__".to_string(),
            DotMenuEntry::GlobalUnits => "!__global_units__".to_string(),
            DotMenuEntry::CloseMenu => "!__close_menu__".to_string(),
        }
    }

    fn fzf_preview(&self) -> crate::menu::protocol::FzfPreview {
        use crate::menu::protocol::FzfPreview;

        match self {
            DotMenuEntry::Repo(_, _) => FzfPreview::Text("Repository information".to_string()),
            DotMenuEntry::AddRepo => PreviewBuilder::new()
                .header(NerdFont::Plus, "Add Repository")
                .text("Clone a new dotfile repository")
                .blank()
                .text("This will:")
                .bullet("Prompt for repository URL")
                .bullet("Clone the repository")
                .bullet("Apply dotfiles from the new repo")
                .build(),
            DotMenuEntry::AlternateFiles => PreviewBuilder::new()
                .header(NerdFont::List, "Alternate Files")
                .text("Browse dotfiles with multiple sources")
                .blank()
                .text("Select which repository or subdirectory")
                .text("a dotfile should be sourced from when")
                .text("multiple versions exist.")
                .build(),
            DotMenuEntry::GlobalUnits => PreviewBuilder::new()
                .header(NerdFont::FolderConfig, "Global Units")
                .text("Manage global dotfile units shared across repos.")
                .blank()
                .text("Units are atomic directories; modify one file")
                .text("and the entire unit is protected from updates.")
                .blank()
                .subtext("Repo authors should prefer repo-scoped units.")
                .build(),
            DotMenuEntry::CloseMenu => PreviewBuilder::new()
                .header(NerdFont::Cross, "Close Menu")
                .text("Close the dotfile menu")
                .blank()
                .text("This will exit the interactive menu")
                .text("and return to the command prompt")
                .build(),
        }
    }
}

/// Wrapper struct for menu items with custom previews
#[derive(Clone)]
struct DotMenuItem {
    entry: DotMenuEntry,
    preview: String,
}

impl FzfSelectable for DotMenuItem {
    fn fzf_display_text(&self) -> String {
        self.entry.fzf_display_text()
    }

    fn fzf_key(&self) -> String {
        self.entry.fzf_key()
    }

    fn fzf_preview(&self) -> crate::menu::protocol::FzfPreview {
        crate::menu::protocol::FzfPreview::Text(self.preview.clone())
    }
}

/// Select a menu entry from the main dot menu
fn select_dot_menu_entry(
    config: &DotfileConfig,
    db: &Database,
    cursor: &mut MenuCursor,
) -> Result<Option<DotMenuEntry>> {
    let mut entries: Vec<DotMenuEntry> = config
        .repos
        .iter()
        .map(|r| DotMenuEntry::Repo(r.name.clone(), r.metadata.is_some()))
        .collect();

    entries.push(DotMenuEntry::AddRepo);
    entries.push(DotMenuEntry::AlternateFiles);
    entries.push(DotMenuEntry::GlobalUnits);
    entries.push(DotMenuEntry::CloseMenu);

    // Create entries with custom previews (Repo gets dynamic preview, others use trait impl)
    let menu_items: Vec<DotMenuItem> = entries
        .iter()
        .map(|entry| {
            let preview = match entry {
                DotMenuEntry::Repo(name, _) => build_repo_preview(name, config, db),
                _ => match entry.fzf_preview() {
                    crate::menu::protocol::FzfPreview::Text(s) => s,
                    crate::menu::protocol::FzfPreview::Command(s) => s,
                    crate::menu::protocol::FzfPreview::None => String::new(),
                },
            };
            DotMenuItem {
                entry: entry.clone(),
                preview,
            }
        })
        .collect();

    let mut builder = FzfWrapper::builder()
        .header(Header::fancy("Dotfile Menu"))
        .prompt("Select")
        .args(fzf_mocha_args())
        .responsive_layout();

    if let Some(index) = cursor.initial_index(&entries) {
        builder = builder.initial_index(index);
    }

    let result = builder.select(menu_items)?;

    match result {
        FzfResult::Selected(item) => {
            cursor.update(&item.entry, &entries);
            Ok(Some(item.entry))
        }
        FzfResult::Cancelled => Ok(None),
        _ => Ok(None),
    }
}

/// Main entry point for the dot menu
pub fn dot_menu(debug: bool) -> Result<()> {
    let mut cursor = MenuCursor::new();
    let mut config = DotfileConfig::load(None)?;
    let mut db = Database::new(config.database_path().to_path_buf())?;

    // Outer loop: main menu
    loop {
        let entry = match select_dot_menu_entry(&config, &db, &mut cursor)? {
            Some(entry) => entry,
            None => return Ok(()),
        };

        match entry {
            DotMenuEntry::Repo(repo_name, _) => {
                handle_repo_actions(&repo_name, &mut config, &db, debug)?;
            }
            DotMenuEntry::AddRepo => {
                handle_add_repo(&mut config, &db, debug)?;
            }
            DotMenuEntry::AlternateFiles => {
                crate::dot::operations::alternative::handle_alternative(
                    &config,
                    crate::dot::operations::alternative::AlternativeOptions {
                        path: "~",
                        reset: false,
                        create: false,
                        list: false,
                        set: None,
                        repo: None,
                        subdir: None,
                    },
                )?;
                reload_menu_state(&mut config, &mut db)?;
            }
            DotMenuEntry::GlobalUnits => {
                crate::dot::menu::repo_actions::handle_global_units_menu(&mut config, &db)?;
                reload_menu_state(&mut config, &mut db)?;
            }
            DotMenuEntry::CloseMenu => return Ok(()),
        }
    }
}

fn reload_menu_state(config: &mut DotfileConfig, db: &mut Database) -> Result<()> {
    let new_config = DotfileConfig::load(None)?;
    if new_config.database_path() != config.database_path() {
        *db = Database::new(new_config.database_path().to_path_buf())?;
    }
    *config = new_config;
    Ok(())
}
