mod edit_menu;
mod editors;
mod state;

use anyhow::{Context, Result, anyhow};
use std::path::{Path, PathBuf};
use std::process::Command;

use crate::common::TildePath;
use crate::common::shell::shell_quote;
use crate::common::terminal::TerminalLauncher;
use crate::game::GameCommands;
use crate::game::config::{Game, GameInstallation, PathContentKind};
use crate::game::config::{InstallationsConfig, InstantGameConfig};
use crate::game::games::AddGameOptions;
use crate::game::games::GameManager;
use crate::game::games::selection::{GameMenuEntry, select_game_menu_entry};
use crate::game::handle_game_command;
use crate::game::launch_command::LaunchCommand;
use crate::game::operations::desktop;
use crate::game::operations::launch_game;
use crate::game::operations::steam;
use crate::game::operations::sync::sync_game_saves;
use crate::game::platforms::LaunchCommandBuilderContext;
use crate::game::restic;
use crate::game::setup;
use crate::menu::protocol::FzfPreview;
use crate::menu_utils::{ConfirmResult, 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 state::EditState;

/// Game action selection
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum GameAction {
    Launch,
    Edit,
    OpenSaveDirectory,
    Setup,
    Move,
    Checkpoint,
    AddToSteam,
    AddToDesktop,
    Back,
}

impl std::fmt::Display for GameAction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            GameAction::Launch => write!(f, "launch"),
            GameAction::Edit => write!(f, "edit"),
            GameAction::OpenSaveDirectory => write!(f, "open-save-directory"),
            GameAction::Setup => write!(f, "setup"),
            GameAction::Move => write!(f, "move"),
            GameAction::Checkpoint => write!(f, "checkpoint"),
            GameAction::AddToSteam => write!(f, "add-to-steam"),
            GameAction::AddToDesktop => write!(f, "add-to-desktop"),
            GameAction::Back => write!(f, "back"),
        }
    }
}

#[derive(Clone)]
struct GameActionItem {
    display: String,
    action: GameAction,
    preview: FzfPreview,
    keywords: Vec<&'static str>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SaveDirectoryOpenMethod {
    Yazi,
    Terminal,
    XdgOpen,
    Back,
}

impl std::fmt::Display for SaveDirectoryOpenMethod {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SaveDirectoryOpenMethod::Yazi => write!(f, "yazi"),
            SaveDirectoryOpenMethod::Terminal => write!(f, "terminal"),
            SaveDirectoryOpenMethod::XdgOpen => write!(f, "xdg-open"),
            SaveDirectoryOpenMethod::Back => write!(f, "back"),
        }
    }
}

#[derive(Clone)]
struct SaveDirectoryOpenItem {
    display: String,
    method: SaveDirectoryOpenMethod,
    preview: FzfPreview,
}

impl FzfSelectable for SaveDirectoryOpenItem {
    fn fzf_display_text(&self) -> String {
        self.display.clone()
    }

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

    fn fzf_preview(&self) -> FzfPreview {
        self.preview.clone()
    }
}

impl FzfSelectable for GameActionItem {
    fn fzf_display_text(&self) -> String {
        self.display.clone()
    }

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

    fn fzf_preview(&self) -> FzfPreview {
        self.preview.clone()
    }

    fn fzf_search_keywords(&self) -> &[&str] {
        &self.keywords
    }
}

/// Loaded game state to avoid repeated config loading
struct GameState {
    game_config: InstantGameConfig,
    installations: InstallationsConfig,
    needs_setup: bool,
    launch_command: Option<String>,
    description: Option<String>,
    save_path: Option<String>,
}

impl GameState {
    fn load(game_name: &str) -> Result<Self> {
        let game_config = InstantGameConfig::load().context("Failed to load game configuration")?;
        let installations =
            InstallationsConfig::load().context("Failed to load installations configuration")?;

        let installation = installations
            .installations
            .iter()
            .find(|i| i.game_name.0 == game_name);

        let has_installation = installation.is_some();

        let game = game_config.games.iter().find(|g| g.name.0 == game_name);
        let game_cmd = game.and_then(|g| g.launch_command.clone());
        let description = game.and_then(|g| g.description.clone());

        let inst_cmd = installation.and_then(|i| i.launch_command.clone());

        let save_path = installation.map(|i| {
            i.save_path
                .to_tilde_string()
                .unwrap_or_else(|_| i.save_path.as_path().to_string_lossy().to_string())
        });

        // Installation command takes precedence over game command
        let launch_command = inst_cmd.or(game_cmd).map(|command| command.to_string());

        Ok(Self {
            game_config,
            installations,
            needs_setup: !has_installation,
            launch_command,
            description,
            save_path,
        })
    }
}

/// Build the game action menu items
fn build_action_menu(game_name: &str, state: &GameState) -> Vec<GameActionItem> {
    let mut actions: Vec<GameActionItem> = Vec::new();

    if state.needs_setup {
        actions.push(action_setup_item(game_name));
    }

    actions.push(action_launch_item(game_name, state));
    actions.push(action_edit_item(game_name, state));

    if !state.needs_setup {
        if let Some(path) = &state.save_path {
            actions.push(action_open_save_item(game_name, path));
            actions.push(action_move_item(game_name, path));
        }
        actions.push(action_checkpoint_item(game_name));
    }

    if state.launch_command.is_some() {
        actions.push(action_steam_item(game_name));
        actions.push(action_desktop_item(game_name));
    }

    actions.push(action_back_item());
    actions
}

fn action_setup_item(game_name: &str) -> GameActionItem {
    GameActionItem {
        display: format!(
            "{} Setup",
            format_icon_colored(NerdFont::Wrench, colors::PEACH)
        ),
        action: GameAction::Setup,
        preview: PreviewBuilder::new()
            .header(NerdFont::Wrench, "Setup Game")
            .text(&format!(
                "Configure save path for '{}' on this device.",
                game_name
            ))
            .blank()
            .text("This game is registered but has no save data location set up yet.")
            .build(),
        keywords: vec![],
    }
}

fn action_launch_item(game_name: &str, state: &GameState) -> GameActionItem {
    let mut builder = PreviewBuilder::new()
        .header(NerdFont::Rocket, "Launch Game")
        .text(&format!("Launch {} with automatic save sync.", game_name));

    if let Some(cmd) = &state.launch_command {
        builder = builder.blank().field("Command", cmd);
    } else {
        builder = builder
            .blank()
            .line(
                colors::YELLOW,
                Some(NerdFont::Warning),
                "No launch command configured.",
            )
            .subtext("Pressing Launch will offer to build one.");
    }

    GameActionItem {
        display: format!(
            "{} Launch",
            format_icon_colored(NerdFont::Rocket, colors::GREEN)
        ),
        action: GameAction::Launch,
        preview: builder.build(),
        keywords: vec!["play", "start", "run"],
    }
}

fn action_edit_item(game_name: &str, state: &GameState) -> GameActionItem {
    let mut builder = PreviewBuilder::new()
        .header(NerdFont::Edit, "Edit Configuration")
        .text(&format!("Edit configuration for '{}'", game_name))
        .blank()
        .separator()
        .blank()
        .line(colors::MAUVE, Some(NerdFont::FileConfig), "games.toml")
        .field_indented(
            "Description",
            state.description.as_deref().unwrap_or("<not set>"),
        )
        .field_indented(
            "Launch Command",
            state.launch_command.as_deref().unwrap_or("<not set>"),
        );

    if let Some(path) = &state.save_path {
        builder = builder
            .blank()
            .line(colors::MAUVE, Some(NerdFont::Desktop), "installations.toml")
            .field_indented("Save Path", path);
    } else {
        builder = builder.blank().subtext("No installation on this device");
    }

    GameActionItem {
        display: format!(
            "{} Edit",
            format_icon_colored(NerdFont::Edit, colors::SAPPHIRE)
        ),
        action: GameAction::Edit,
        preview: builder.build(),
        keywords: vec!["change", "modify", "configure", "settings"],
    }
}

fn action_open_save_item(game_name: &str, path: &str) -> GameActionItem {
    GameActionItem {
        display: format!(
            "{} Open Save Directory",
            format_icon_colored(NerdFont::FolderOpen, colors::GREEN)
        ),
        action: GameAction::OpenSaveDirectory,
        preview: PreviewBuilder::new()
            .header(NerdFont::FolderOpen, "Open Save Directory")
            .text(&format!("Open the save location for '{}'.", game_name))
            .blank()
            .field("Configured path", path)
            .blank()
            .text("Choose how to open it:")
            .bullet("yazi file manager")
            .bullet("Terminal window in that directory")
            .bullet("Default desktop application via xdg-open")
            .build(),
        keywords: vec!["folder", "browse", "explore", "files"],
    }
}

fn action_move_item(game_name: &str, path: &str) -> GameActionItem {
    GameActionItem {
        display: format!(
            "{} Change Save Path",
            format_icon_colored(NerdFont::Folder, colors::LAVENDER)
        ),
        action: GameAction::Move,
        preview: PreviewBuilder::new()
            .header(NerdFont::Folder, "Change Save Path")
            .text(&format!("Select a new save location for '{}'.", game_name))
            .blank()
            .subtext("This updates where the game manager looks for saves.")
            .subtext("Files are not moved automatically.")
            .blank()
            .field("Current path", path)
            .build(),
        keywords: vec![],
    }
}

fn action_checkpoint_item(game_name: &str) -> GameActionItem {
    GameActionItem {
        display: format!(
            "{} Checkpoint",
            format_icon_colored(NerdFont::Clock, colors::YELLOW)
        ),
        action: GameAction::Checkpoint,
        preview: PreviewBuilder::new()
            .header(NerdFont::Clock, "Restore Checkpoint")
            .text(&format!(
                "Browse and restore from a past checkpoint for '{}'.",
                game_name
            ))
            .blank()
            .text("View all available snapshots and select one to restore.")
            .text("If local saves are newer, you will be warned before overwriting.")
            .build(),
        keywords: vec!["snapshot"],
    }
}

fn action_steam_item(game_name: &str) -> GameActionItem {
    let is_in_steam = steam::is_game_in_steam(game_name).unwrap_or(false);
    let status_text = if is_in_steam {
        format!(
            "{} Already in Steam Library",
            format_icon_colored(NerdFont::Check, colors::GREEN)
        )
    } else {
        format!(
            "{} Not in Steam Library",
            format_icon_colored(NerdFont::Cross, colors::RED)
        )
    };

    let mut builder = PreviewBuilder::new()
        .header(NerdFont::Steam, "Add to Steam")
        .text(&format!("Manage Steam shortcut for '{}'.", game_name))
        .blank()
        .text(&status_text);

    if is_in_steam && let Ok(Some(shortcut)) = steam::get_game_shortcut(game_name) {
        builder = builder.blank().text("Current shortcut details:");
        builder = builder
            .subtext(&format!("Exe:        {}", shortcut.exe))
            .subtext(&format!("Start dir:  {}", shortcut.start_dir));

        if !shortcut.launch_options.is_empty() {
            builder = builder.subtext(&format!("Options:    {}", shortcut.launch_options));
        }

        let full_cmd = if shortcut.launch_options.is_empty() {
            shortcut.exe.clone()
        } else {
            format!("{} {}", shortcut.exe, shortcut.launch_options)
        };
        builder = builder
            .blank()
            .subtext(&format!("Full command: {}", full_cmd));
    }

    let preview = builder
        .blank()
        .text("The shortcut will launch the game through ins,")
        .text("which automatically syncs saves before and after.")
        .blank()
        .subtext("Note: Restart Steam after adding to see the shortcut.")
        .build();

    GameActionItem {
        display: format!(
            "{} Add to Steam",
            format_icon_colored(NerdFont::Steam, colors::SAPPHIRE)
        ),
        action: GameAction::AddToSteam,
        preview,
        keywords: vec![],
    }
}

fn action_desktop_item(game_name: &str) -> GameActionItem {
    let is_on_desktop = desktop::is_game_on_desktop(game_name).unwrap_or(false);
    let status_text = if is_on_desktop {
        format!(
            "{} Already on Desktop",
            format_icon_colored(NerdFont::Check, colors::GREEN)
        )
    } else {
        format!(
            "{} Not on Desktop",
            format_icon_colored(NerdFont::Cross, colors::RED)
        )
    };

    let mut builder = PreviewBuilder::new()
        .header(NerdFont::Desktop, "Add to Desktop")
        .text(&format!("Manage desktop shortcut for '{}'.", game_name))
        .blank()
        .text(&status_text);

    if is_on_desktop && let Ok(Some(path)) = desktop::get_game_desktop_path(game_name) {
        builder = builder
            .blank()
            .text("Current shortcut location:")
            .subtext(&path.display().to_string());
    }

    let preview = builder
        .blank()
        .text("Creates a .desktop file that launches the game through ins,")
        .text("which automatically syncs saves before and after.")
        .blank()
        .subtext("The shortcut will be placed on your Desktop if possible,")
        .subtext("otherwise in the applications menu.")
        .build();

    GameActionItem {
        display: format!(
            "{} Add to Desktop",
            format_icon_colored(NerdFont::Desktop, colors::MAUVE)
        ),
        action: GameAction::AddToDesktop,
        preview,
        keywords: vec!["shortcut", "launcher"],
    }
}

fn action_back_item() -> GameActionItem {
    GameActionItem {
        display: format!("{} Back", format_back_icon()),
        action: GameAction::Back,
        preview: PreviewBuilder::new()
            .header(NerdFont::ArrowLeft, "Back")
            .text("Return to game selection.")
            .build(),
        keywords: vec![],
    }
}

/// Result of handling a game action
enum ActionResult {
    Stay, // Stay in this game's action menu
    Back, // Go back to game selection
    Exit, // Exit the menu entirely
}

/// Handle a selected game action
fn handle_action(
    action: GameAction,
    game_name: &str,
    state: &GameState,
    exit_after: bool,
) -> Result<ActionResult> {
    match action {
        GameAction::Launch => handle_launch_action(game_name, state),
        GameAction::Edit => {
            run_edit_menu_for_game(game_name, &state.game_config, &state.installations)?;
            Ok(if exit_after {
                ActionResult::Exit
            } else {
                ActionResult::Stay
            })
        }
        GameAction::OpenSaveDirectory => {
            handle_open_save_directory_action(game_name, state)?;
            Ok(ActionResult::Stay)
        }
        GameAction::Setup => {
            setup::setup_uninstalled_games()?;
            Ok(if exit_after {
                ActionResult::Exit
            } else {
                ActionResult::Stay
            })
        }
        GameAction::Move => {
            GameManager::relocate_game(Some(game_name.to_string()), None)?;
            Ok(if exit_after {
                ActionResult::Exit
            } else {
                ActionResult::Stay
            })
        }
        GameAction::Checkpoint => {
            handle_checkpoint_action(game_name)?;
            Ok(if exit_after {
                ActionResult::Exit
            } else {
                ActionResult::Stay
            })
        }
        GameAction::AddToSteam => {
            let Some(launch_cmd) = ensure_launch_command(game_name, state)? else {
                return Ok(ActionResult::Stay);
            };
            handle_steam_action(game_name, &launch_cmd)?;
            Ok(ActionResult::Stay)
        }
        GameAction::AddToDesktop => {
            let Some(launch_cmd) = ensure_launch_command(game_name, state)? else {
                return Ok(ActionResult::Stay);
            };
            handle_desktop_action(game_name, &launch_cmd)?;
            Ok(ActionResult::Stay)
        }
        GameAction::Back => Ok(if exit_after {
            ActionResult::Exit
        } else {
            ActionResult::Back
        }),
    }
}

fn handle_launch_action(game_name: &str, state: &GameState) -> Result<ActionResult> {
    if ensure_launch_command(game_name, state)?.is_none() {
        return Ok(ActionResult::Stay);
    }
    if state.needs_setup {
        FzfWrapper::message(&format!(
            "No save path configured for '{}' on this device.\n\nUse Setup to configure the save path first.",
            game_name
        ))?;
        return Ok(ActionResult::Stay);
    }
    launch_game(Some(game_name.to_string()))?;
    Ok(ActionResult::Exit)
}

fn ensure_launch_command(game_name: &str, state: &GameState) -> Result<Option<String>> {
    if let Some(command) = state.launch_command.as_deref() {
        return Ok(Some(command.to_string()));
    }

    let installation = state
        .installations
        .installations
        .iter()
        .find(|install| install.game_name.0 == game_name);
    let context = LaunchCommandBuilderContext::from_game(
        Some(game_name),
        installation.map(|install| install.save_path.as_path()),
        installation
            .and_then(|install| install.launch_command.as_ref())
            .or_else(|| {
                state
                    .game_config
                    .games
                    .iter()
                    .find(|game| game.name.0 == game_name)
                    .and_then(|game| game.launch_command.as_ref())
            }),
    );

    let Some(command) = crate::game::platforms::build_launch_command_with_context(Some(&context))?
    else {
        return Ok(None);
    };

    let mut game_config = state.game_config.clone();
    let Some(game) = game_config.games.iter_mut().find(|g| g.name.0 == game_name) else {
        return Ok(None);
    };
    game.launch_command = Some(command.clone());
    game_config.save()?;
    FzfWrapper::message(&format!(
        "{} Launch command saved.",
        char::from(NerdFont::Check)
    ))?;

    Ok(Some(command.to_string()))
}

fn handle_steam_action(game_name: &str, launch_cmd: &str) -> Result<()> {
    if steam::is_game_in_steam(game_name).unwrap_or(false) {
        if FzfWrapper::builder()
            .confirm(format!(
                "'{}' is already in Steam.\n\nRemove it?",
                game_name
            ))
            .yes_text("Remove from Steam")
            .no_text("Keep it")
            .confirm_dialog()?
            == ConfirmResult::Yes
        {
            match steam::remove_game_from_steam(game_name) {
                Ok((true, steam_running)) => {
                    let base_msg = format!("Removed '{}' from Steam.", game_name);
                    let msg = if steam_running {
                        format!(
                            "{}\n\n{}",
                            base_msg,
                            steam::format_steam_running_warning("removed")
                        )
                    } else {
                        format!("{}\n\nRestart Steam to update your library.", base_msg)
                    };
                    FzfWrapper::message(&msg)?;
                }
                Ok((false, _)) => FzfWrapper::message(&format!(
                    "'{}' was not found in Steam (maybe already removed).",
                    game_name
                ))?,
                Err(e) => FzfWrapper::message(&format!("Failed to remove from Steam: {}", e))?,
            }
        }
    } else {
        match steam::add_game_to_steam(game_name, launch_cmd) {
            Ok((true, steam_running)) => {
                let base_msg = format!("Added '{}' to Steam as a non-Steam game.", game_name);
                let msg = if steam_running {
                    format!(
                        "{}\n\n{}",
                        base_msg,
                        steam::format_steam_running_warning("added")
                    )
                } else {
                    format!("{}\n\nRestart Steam to see it in your library.", base_msg)
                };
                FzfWrapper::message(&msg)?;
            }
            Ok((false, _)) => {
                FzfWrapper::message(&format!(
                    "'{}' is already in your Steam library.",
                    game_name
                ))?;
            }
            Err(e) => FzfWrapper::message(&format!("Failed to add to Steam: {}", e))?,
        }
    }
    Ok(())
}

fn handle_desktop_action(game_name: &str, launch_cmd: &str) -> Result<()> {
    if desktop::is_game_on_desktop(game_name).unwrap_or(false) {
        if FzfWrapper::builder()
            .confirm(format!(
                "'{}' is already on the Desktop.\n\nRemove it?",
                game_name
            ))
            .yes_text("Remove from Desktop")
            .no_text("Keep it")
            .confirm_dialog()?
            == ConfirmResult::Yes
        {
            match desktop::remove_game_from_desktop(game_name) {
                Ok(true) => FzfWrapper::message(&format!("Removed '{}' from Desktop.", game_name))?,
                Ok(false) => FzfWrapper::message(&format!(
                    "'{}' was not found on the Desktop (maybe already removed).",
                    game_name
                ))?,
                Err(e) => FzfWrapper::message(&format!("Failed to remove from Desktop: {}", e))?,
            }
        }
    } else {
        match desktop::add_game_to_desktop(game_name, launch_cmd) {
            Ok((true, Some(path))) => FzfWrapper::message(&format!(
                "Added '{}' to Desktop.\n\nLocation: {}",
                game_name,
                path.display()
            ))?,
            Ok((true, None)) => {
                FzfWrapper::message(&format!("Added '{}' to Desktop.", game_name))?;
            }
            Ok((false, _)) => {
                FzfWrapper::message(&format!("'{}' is already on your Desktop.", game_name))?;
            }
            Err(e) => FzfWrapper::message(&format!("Failed to add to Desktop: {}", e))?,
        }
    }
    Ok(())
}

/// Handle checkpoint action - select and restore from a past snapshot
fn handle_checkpoint_action(game_name: &str) -> Result<()> {
    use crate::game::restic::snapshot_selection::select_snapshot_interactive;

    // Get installation for local save comparison
    let installations = InstallationsConfig::load().context("Failed to load installations")?;
    let installation = installations
        .installations
        .iter()
        .find(|i| i.game_name.0 == game_name);

    // Select a snapshot interactively
    let snapshot_id = match select_snapshot_interactive(game_name, installation)? {
        Some(id) => id,
        None => return Ok(()), // User cancelled
    };

    // Restore the selected snapshot
    restic::restore_game_saves(Some(game_name.to_string()), Some(snapshot_id), false)
}

fn handle_open_save_directory_action(game_name: &str, state: &GameState) -> Result<()> {
    let Some(installation) = state
        .installations
        .installations
        .iter()
        .find(|install| install.game_name.0 == game_name)
    else {
        FzfWrapper::message("No installation is configured for this game on this device.")?;
        return Ok(());
    };

    let target_dir = save_directory_target(installation);
    let target_display = target_dir.display().to_string();

    let items = vec![
        SaveDirectoryOpenItem {
            display: format!(
                "{} Open in yazi",
                format_icon_colored(NerdFont::FolderOpen, colors::SAPPHIRE)
            ),
            method: SaveDirectoryOpenMethod::Yazi,
            preview: PreviewBuilder::new()
                .header(NerdFont::FolderOpen, "Open in yazi")
                .text("Open the save directory in the yazi file manager.")
                .blank()
                .field("Directory", &target_display)
                .build(),
        },
        SaveDirectoryOpenItem {
            display: format!(
                "{} Open terminal here",
                format_icon_colored(NerdFont::Terminal, colors::GREEN)
            ),
            method: SaveDirectoryOpenMethod::Terminal,
            preview: PreviewBuilder::new()
                .header(NerdFont::Terminal, "Open Terminal")
                .text("Open a terminal window in the save directory.")
                .blank()
                .field("Directory", &target_display)
                .build(),
        },
        SaveDirectoryOpenItem {
            display: format!(
                "{} Open with xdg-open",
                format_icon_colored(NerdFont::Folder, colors::YELLOW)
            ),
            method: SaveDirectoryOpenMethod::XdgOpen,
            preview: PreviewBuilder::new()
                .header(NerdFont::Folder, "Open with xdg-open")
                .text("Open the save directory with the desktop default application.")
                .blank()
                .field("Directory", &target_display)
                .build(),
        },
        SaveDirectoryOpenItem {
            display: format!("{} Back", format_back_icon()),
            method: SaveDirectoryOpenMethod::Back,
            preview: PreviewBuilder::new()
                .header(NerdFont::ArrowLeft, "Back")
                .text("Return to the game action menu.")
                .build(),
        },
    ];

    let selection = FzfWrapper::builder()
        .header(Header::fancy(&format!(
            "Open Save Directory: {}",
            game_name
        )))
        .prompt("Open with")
        .args(fzf_mocha_args())
        .responsive_layout()
        .select_padded(items)?;

    let method = match selection {
        FzfResult::Selected(item) => item.method,
        _ => SaveDirectoryOpenMethod::Back,
    };

    match method {
        SaveDirectoryOpenMethod::Yazi => open_directory_in_yazi(&target_dir),
        SaveDirectoryOpenMethod::Terminal => open_directory_in_terminal(&target_dir),
        SaveDirectoryOpenMethod::XdgOpen => open_directory_with_xdg_open(&target_dir),
        SaveDirectoryOpenMethod::Back => Ok(()),
    }
}

fn save_directory_target(installation: &GameInstallation) -> PathBuf {
    let save_path = installation.save_path.as_path();
    match installation.save_path_type {
        PathContentKind::File => save_path
            .parent()
            .map(Path::to_path_buf)
            .unwrap_or_else(|| save_path.to_path_buf()),
        _ => save_path.to_path_buf(),
    }
}

fn open_directory_in_yazi(path: &Path) -> Result<()> {
    TerminalLauncher::new("yazi")
        .arg(path.to_string_lossy().to_string())
        .title("Save Directory")
        .launch()
}

fn open_directory_in_terminal(path: &Path) -> Result<()> {
    let shell = std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
    let command = format!(
        "cd {} && exec {}",
        shell_quote(&path.to_string_lossy()),
        shell_quote(&shell)
    );

    TerminalLauncher::new(&shell)
        .args(["-lc".to_string(), command])
        .title("Save Directory")
        .launch()
}

fn open_directory_with_xdg_open(path: &Path) -> Result<()> {
    Command::new("xdg-open")
        .arg(path)
        .spawn()
        .context("Failed to open save directory with xdg-open")?;
    Ok(())
}

/// Main entry point for the game menu
pub fn game_menu(provided_game_name: Option<String>) -> Result<()> {
    // Check if game manager is initialized
    let config = InstantGameConfig::load().context("Failed to load game configuration")?;
    if !config.is_initialized() {
        // Show simplified menu with initialization option
        return show_uninitialized_menu();
    }

    let exit_after_action = provided_game_name.is_some();

    // If a game name is provided, skip the menu and go directly to actions
    if let Some(name) = &provided_game_name {
        let mut cursor = MenuCursor::new();

        loop {
            let state = GameState::load(name)?;
            let actions = build_action_menu(name, &state);

            let mut builder = FzfWrapper::builder()
                .header(Header::fancy(&format!("Game: {}", name)))
                .prompt("Select action")
                .args(fzf_mocha_args())
                .responsive_layout();

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

            let selection = builder.select_padded(actions.clone())?;

            let result = match selection {
                FzfResult::Selected(item) => {
                    cursor.update(&item, &actions);
                    handle_action(item.action, name, &state, exit_after_action)?
                }
                FzfResult::Cancelled => {
                    if exit_after_action {
                        ActionResult::Exit
                    } else {
                        ActionResult::Back
                    }
                }
                _ => ActionResult::Exit,
            };

            match result {
                ActionResult::Stay => continue,
                ActionResult::Back => break,
                ActionResult::Exit => return Ok(()),
            }
        }
        return Ok(());
    }

    // Outer loop: menu entry selection
    let mut cursor = MenuCursor::new();
    loop {
        let entry = match select_game_menu_entry(&mut cursor)? {
            Some(entry) => entry,
            None => return Ok(()),
        };

        match entry {
            GameMenuEntry::AddGame => {
                handle_game_command(
                    GameCommands::Add {
                        name: None,
                        description: None,
                        launch_command: None,
                        save_path: None,
                        create_save_path: false,
                        no_cache: false,
                    },
                    crate::ui::is_debug_enabled(),
                )?;
                // Return to menu after adding
                continue;
            }
            GameMenuEntry::SetupGames => {
                setup::setup_uninstalled_games()?;
                // Return to menu after setup
                continue;
            }
            GameMenuEntry::SyncAll => {
                match sync_game_saves(None, false) {
                    Ok(summary) => {
                        // Show summary in a message dialog
                        let message = if summary.total() == 0 {
                            "No games configured for syncing.".to_string()
                        } else {
                            format!(
                                "{} Sync Summary\n\n✅ Synced: {}\n🔶 Skipped: {}\n❌ Errors: {}",
                                char::from(NerdFont::Chart),
                                summary.synced,
                                summary.skipped,
                                summary.errors
                            )
                        };

                        FzfWrapper::message(&message)?;
                    }
                    Err(e) => {
                        FzfWrapper::message(&format!("Sync failed: {}", e))?;
                    }
                }
                // Return to menu after user dismisses the message
                continue;
            }
            GameMenuEntry::AddMenuToSteam => {
                match steam::add_game_menu_to_steam() {
                    Ok((true, steam_running)) => {
                        let base_msg = "Added 'ins game menu' to Steam.";
                        let msg = if steam_running {
                            format!(
                                "{}\n\n{}",
                                base_msg,
                                steam::format_steam_running_warning("added")
                            )
                        } else {
                            format!("{}\n\nRestart Steam to see it in your library.", base_msg)
                        };
                        FzfWrapper::message(&msg)?;
                    }
                    Ok((false, _)) => {
                        FzfWrapper::message("'ins game menu' is already in your Steam library.")?;
                    }
                    Err(e) => {
                        FzfWrapper::message(&format!("Failed to add to Steam: {}", e))?;
                    }
                }
                continue;
            }
            GameMenuEntry::AddMenuToDesktop => {
                match desktop::add_menu_to_desktop() {
                    Ok((true, Some(path))) => {
                        FzfWrapper::message(&format!(
                            "Added 'ins game menu' to Desktop.\n\nLocation: {}",
                            path.display()
                        ))?;
                    }
                    Ok((true, None)) => {
                        FzfWrapper::message("Added 'ins game menu' to Desktop.")?;
                    }
                    Ok((false, _)) => {
                        FzfWrapper::message("'ins game menu' is already on your Desktop.")?;
                    }
                    Err(e) => {
                        FzfWrapper::message(&format!("Failed to add to Desktop: {}", e))?;
                    }
                }
                continue;
            }
            GameMenuEntry::Game(game_name, _) => {
                // Inner loop: game action menu
                let mut cursor = MenuCursor::new();
                loop {
                    let state = GameState::load(&game_name)?;
                    let actions = build_action_menu(&game_name, &state);

                    let mut builder = FzfWrapper::builder()
                        .header(Header::fancy(&format!("Game: {}", game_name)))
                        .prompt("Select action")
                        .args(fzf_mocha_args())
                        .responsive_layout();

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

                    let selection = builder.select_padded(actions.clone())?;

                    let result = match selection {
                        FzfResult::Selected(item) => {
                            cursor.update(&item, &actions);
                            handle_action(item.action, &game_name, &state, false)?
                        }
                        FzfResult::Cancelled => ActionResult::Back,
                        _ => ActionResult::Exit,
                    };

                    match result {
                        ActionResult::Stay => continue,
                        ActionResult::Back => break,
                        ActionResult::Exit => return Ok(()),
                    }
                }
            }
            GameMenuEntry::CloseMenu => {
                // Exit the menu entirely
                return Ok(());
            }
        }
    }
}

/// Run the edit menu for a specific game
fn run_edit_menu_for_game(
    game_name: &str,
    game_config: &InstantGameConfig,
    installations: &InstallationsConfig,
) -> Result<()> {
    let game_index = game_config
        .games
        .iter()
        .position(|g| g.name.0 == game_name)
        .ok_or_else(|| anyhow!("Game '{}' not found in games.toml", game_name))?;

    let installation_index = installations
        .installations
        .iter()
        .position(|i| i.game_name.0 == game_name);

    let mut state = EditState::new(
        game_config.clone(),
        installations.clone(),
        game_index,
        installation_index,
    );
    edit_menu::run_edit_menu(game_name, &mut state)
}

pub fn open_prefilled_add_editor(options: AddGameOptions) -> Result<()> {
    let mut game_config = InstantGameConfig::load().context("Failed to load game configuration")?;
    let mut installations =
        InstallationsConfig::load().context("Failed to load installations configuration")?;

    let game_name = options.name.unwrap_or_default().trim().to_string();
    if game_name.is_empty() {
        return Err(anyhow!("Discovered game is missing a name"));
    }

    if game_config
        .games
        .iter()
        .any(|game| game.name.0 == game_name)
    {
        return Err(anyhow!("Game '{}' already exists", game_name));
    }

    let mut game = Game::new(game_name.clone());
    game.description = options.description.filter(|value| !value.trim().is_empty());
    game.launch_command = options
        .launch_command
        .filter(|value| !value.trim().is_empty())
        .map(LaunchCommand::from_shell_or_manual);
    game_config.games.push(game);

    let save_path = options
        .save_path
        .filter(|value| !value.trim().is_empty())
        .map(|path| {
            TildePath::from_str(path.trim()).map_err(|e| anyhow!("Invalid save path: {}", e))
        })
        .transpose()?
        .unwrap_or_else(|| TildePath::new(std::path::PathBuf::new()));

    let save_path_type = if save_path.as_path().exists() {
        std::fs::metadata(save_path.as_path())
            .map(PathContentKind::from)
            .context("Failed to inspect discovered save path")?
    } else {
        PathContentKind::Directory
    };

    let installation = GameInstallation::with_kind(game_name.clone(), save_path, save_path_type);
    installations.installations.push(installation);

    let game_index = game_config.games.len() - 1;
    let installation_index = Some(installations.installations.len() - 1);
    let mut state = EditState::new(game_config, installations, game_index, installation_index);
    state.mark_dirty();

    edit_menu::run_edit_menu(&game_name, &mut state)
}

/// Show menu when game manager is not initialized
fn show_uninitialized_menu() -> Result<()> {
    let options = vec![
        format!(
            "{} Initialize Game Manager",
            format_icon_colored(NerdFont::Play, colors::GREEN)
        ),
        format!("{} Cancel", format_back_icon()),
    ];

    let selection = FzfWrapper::builder()
        .header(Header::fancy("Game save manager is not initialized"))
        .prompt("Select action")
        .args(fzf_mocha_args())
        .responsive_layout()
        .select_padded(options)?;

    match selection {
        FzfResult::Selected(item)
            if item
                == format!(
                    "{} Initialize Game Manager",
                    format_icon_colored(NerdFont::Play, colors::GREEN)
                ) =>
        {
            // Run initialization
            let init_result =
                crate::game::repository::manager::GameRepositoryManager::initialize_game_manager(
                    false,
                    Default::default(),
                );
            match init_result {
                Ok(()) => {
                    FzfWrapper::message(
                        "Game save manager initialized successfully!\n\nYou can now add games with 'ins game add' or open the menu again.",
                    )?;
                }
                Err(e) => {
                    FzfWrapper::message(&format!("Initialization failed: {}", e))?;
                }
            }
            Ok(())
        }
        _ => Ok(()),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn save_directory_target_uses_parent_for_file_paths() {
        let installation = GameInstallation::with_kind(
            "Test Game",
            TildePath::new(PathBuf::from("/tmp/game/saves/slot1.sav")),
            PathContentKind::File,
        );

        assert_eq!(
            save_directory_target(&installation),
            PathBuf::from("/tmp/game/saves")
        );
    }

    #[test]
    fn save_directory_target_keeps_directory_paths() {
        let installation = GameInstallation::with_kind(
            "Test Game",
            TildePath::new(PathBuf::from("/tmp/game/saves")),
            PathContentKind::Directory,
        );

        assert_eq!(
            save_directory_target(&installation),
            PathBuf::from("/tmp/game/saves")
        );
    }
}
