updt 0.2.0

Cross-platform update helper for npm, cargo, rustup, fnm, scoop, Homebrew, paru, flatpak, pacman, and pkg.
use super::{AppState, TargetStateFlags};

pub const TARGET_IDS: [&str; 11] = [
    "brew", "npm", "cargo", "nvim", "rustup", "fnm", "scoop", "paru", "flatpak", "pacman", "pkg",
];

struct TargetMeta {
    id: &'static str,
    label: &'static str,
    section: &'static str,
    update_summary: &'static str,
    flags: fn(&AppState) -> TargetStateFlags,
    items: fn(&AppState) -> Vec<String>,
}

const TARGET_META: [TargetMeta; 11] = [
    TargetMeta {
        id: "brew",
        label: "Homebrew",
        section: "Homebrew",
        update_summary: "发现可升级项",
        flags: brew_flags,
        items: brew_items,
    },
    TargetMeta {
        id: "npm",
        label: "npm",
        section: "npm (global)",
        update_summary: "发现可升级项",
        flags: npm_flags,
        items: npm_items,
    },
    TargetMeta {
        id: "cargo",
        label: "cargo",
        section: "cargo",
        update_summary: "发现可升级项",
        flags: cargo_flags,
        items: cargo_items,
    },
    TargetMeta {
        id: "nvim",
        label: "Neovim",
        section: "Neovim (Lazy/Mason)",
        update_summary: "可执行更新",
        flags: nvim_flags,
        items: nvim_items,
    },
    TargetMeta {
        id: "rustup",
        label: "rustup",
        section: "rustup",
        update_summary: "发现可升级项",
        flags: rustup_flags,
        items: rustup_items,
    },
    TargetMeta {
        id: "fnm",
        label: "fnm",
        section: "fnm (Node.js runtime)",
        update_summary: "发现可升级项",
        flags: fnm_flags,
        items: fnm_items,
    },
    TargetMeta {
        id: "scoop",
        label: "scoop",
        section: "scoop",
        update_summary: "发现可升级项",
        flags: scoop_flags,
        items: scoop_items,
    },
    TargetMeta {
        id: "paru",
        label: "paru",
        section: "paru (AUR)",
        update_summary: "发现可升级项",
        flags: paru_flags,
        items: paru_items,
    },
    TargetMeta {
        id: "flatpak",
        label: "flatpak",
        section: "flatpak",
        update_summary: "发现可升级项",
        flags: flatpak_flags,
        items: flatpak_items,
    },
    TargetMeta {
        id: "pacman",
        label: "pacman",
        section: "pacman",
        update_summary: "发现可升级项",
        flags: pacman_flags,
        items: pacman_items,
    },
    TargetMeta {
        id: "pkg",
        label: "pkg",
        section: "pkg (Termux)",
        update_summary: "发现可升级项",
        flags: pkg_flags,
        items: pkg_items,
    },
];

fn target_meta(id: &str) -> Option<&'static TargetMeta> {
    TARGET_META.iter().find(|meta| meta.id == id)
}

pub fn target_label(id: &str) -> &'static str {
    target_meta(id).map(|m| m.label).unwrap_or("unknown")
}

pub fn section_title(target: &str) -> &'static str {
    target_meta(target).map(|m| m.section).unwrap_or("unknown")
}

pub fn target_update_summary(target: &str) -> &'static str {
    target_meta(target)
        .map(|m| m.update_summary)
        .unwrap_or("发现可升级项")
}

pub fn target_state_flags(state: &AppState, target: &str) -> Option<TargetStateFlags> {
    target_meta(target).map(|meta| (meta.flags)(state))
}

pub fn target_enabled(state: &AppState, target: &str) -> bool {
    target_state_flags(state, target)
        .map(|flags| flags.enabled)
        .unwrap_or(false)
}

pub fn updatable_items_for_target(state: &AppState, target: &str) -> Vec<String> {
    target_meta(target)
        .map(|meta| (meta.items)(state))
        .unwrap_or_default()
}

fn bucket_flags(
    enabled: bool,
    installed: bool,
    check_failed: bool,
    has_updates: bool,
) -> TargetStateFlags {
    TargetStateFlags {
        enabled,
        installed,
        check_failed,
        has_updates,
        needs_cargo_updater: false,
    }
}

fn brew_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.brew,
        state.brew.installed,
        state.brew.check_failed,
        state.brew.has_updates,
    )
}

fn npm_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.npm,
        state.npm.installed,
        state.npm.check_failed,
        state.npm.has_updates,
    )
}

fn cargo_flags(state: &AppState) -> TargetStateFlags {
    TargetStateFlags {
        enabled: state.enable.cargo,
        installed: state.cargo.installed,
        check_failed: state.cargo.check_failed,
        has_updates: state.cargo.has_updates,
        needs_cargo_updater: !state.cargo.updater_installed,
    }
}

fn nvim_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.nvim,
        state.nvim.installed,
        state.nvim.check_failed,
        state.nvim.has_updates,
    )
}

fn rustup_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.rustup,
        state.rustup.installed,
        state.rustup.check_failed,
        state.rustup.has_updates,
    )
}

fn fnm_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.fnm,
        state.fnm.installed,
        state.fnm.check_failed,
        state.fnm.has_updates,
    )
}

fn scoop_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.scoop,
        state.scoop.installed,
        state.scoop.check_failed,
        state.scoop.has_updates,
    )
}

fn paru_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.paru,
        state.paru.installed,
        state.paru.check_failed,
        state.paru.has_updates,
    )
}

fn flatpak_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.flatpak,
        state.flatpak.installed,
        state.flatpak.check_failed,
        state.flatpak.has_updates,
    )
}

fn pacman_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.pacman,
        state.pacman.installed,
        state.pacman.check_failed,
        state.pacman.has_updates,
    )
}

fn pkg_flags(state: &AppState) -> TargetStateFlags {
    bucket_flags(
        state.enable.pkg,
        state.pkg.installed,
        state.pkg.check_failed,
        state.pkg.has_updates,
    )
}

fn brew_items(state: &AppState) -> Vec<String> {
    state
        .brew
        .formula_list
        .iter()
        .chain(state.brew.cask_list.iter())
        .cloned()
        .collect()
}

fn npm_items(state: &AppState) -> Vec<String> {
    state.npm.updatable_items.clone()
}

fn cargo_items(state: &AppState) -> Vec<String> {
    state.cargo.updatable_packages.clone()
}

fn nvim_items(state: &AppState) -> Vec<String> {
    state.nvim.updatable_components.clone()
}

fn rustup_items(state: &AppState) -> Vec<String> {
    state.rustup.updatable_items.clone()
}

fn fnm_items(state: &AppState) -> Vec<String> {
    state.fnm.updatable_items.clone()
}

fn scoop_items(state: &AppState) -> Vec<String> {
    state.scoop.updatable_items.clone()
}

fn paru_items(state: &AppState) -> Vec<String> {
    state.paru.updatable_items.clone()
}

fn flatpak_items(state: &AppState) -> Vec<String> {
    state.flatpak.updatable_items.clone()
}

fn pacman_items(state: &AppState) -> Vec<String> {
    state.pacman.updatable_items.clone()
}

fn pkg_items(state: &AppState) -> Vec<String> {
    state.pkg.updatable_items.clone()
}

#[cfg(test)]
mod tests {
    use super::{
        section_title, target_enabled, target_label, target_state_flags, target_update_summary,
        updatable_items_for_target,
    };
    use crate::state::AppState;

    #[test]
    fn returns_target_metadata_and_unknown_defaults() {
        assert_eq!(target_label("brew"), "Homebrew");
        assert_eq!(section_title("pkg"), "pkg (Termux)");
        assert_eq!(target_update_summary("nvim"), "可执行更新");
        assert_eq!(target_label("missing"), "unknown");
        assert_eq!(target_update_summary("missing"), "发现可升级项");
    }

    #[test]
    fn reports_enabled_state_from_flags() {
        let mut state = AppState::default();
        state.enable.rustup = true;
        state.rustup.installed = true;

        assert!(target_enabled(&state, "rustup"));
        assert!(!target_enabled(&state, "brew"));
        assert!(!target_enabled(&state, "missing"));
    }

    #[test]
    fn cargo_flags_report_missing_cargo_update() {
        let mut state = AppState::default();
        state.enable.cargo = true;
        state.cargo.installed = true;

        let flags = target_state_flags(&state, "cargo").expect("cargo target flags");

        assert!(flags.enabled);
        assert!(flags.installed);
        assert!(flags.needs_cargo_updater);
    }

    #[test]
    fn brew_updatable_items_merge_formulae_and_casks() {
        let mut state = AppState::default();
        state.brew.formula_list = vec!["git".to_string()];
        state.brew.cask_list = vec!["visual-studio-code".to_string()];

        assert_eq!(
            updatable_items_for_target(&state, "brew"),
            vec!["git".to_string(), "visual-studio-code".to_string()]
        );
    }
}