//! Generated `tfd` client subcommands for descriptor-backed sites — one per
//! registry entry, from the same `specs()` the CLI uses.

use clap::{ArgMatches, Command};
use night_fury_daemon_core::cli::make_req;
use night_fury_daemon_core::client;
use tail_fin_cli_core::{matches_to_params, spec_to_command};

use crate::dispatch::{acquire_then_exec, ExecArgs};
use crate::spawn::ensure_daemon_safe;

/// Build the generated top-level subcommands from the registry: one per entry.
pub fn generated_site_commands() -> Vec<Command> {
    crate::registry::all_site_entries()
        .iter()
        .map(|e| {
            let id = e.site.id();
            let mut cmd = Command::new(id).about(e.site.display_name());
            for spec in e.specs {
                cmd = cmd.subcommand(spec_to_command(spec));
            }
            cmd
        })
        .collect()
}

/// True if `name` is a generated descriptor-backed site.
pub fn is_generated_site(name: &str) -> bool {
    crate::registry::all_site_entries()
        .iter()
        .any(|e| e.site.id() == name)
}

/// Route a parsed generated-site invocation over the socket. `site_matches` is
/// the ArgMatches for the `<site>` subcommand (its own subcommand is the
/// command). Mirrors the hand-written `dispatch::<site>::run` flow: explicit
/// session → direct send; otherwise acquire → exec → release.
pub async fn run(
    site: &str,
    site_matches: &ArgMatches,
    socket: &str,
    host: &str,
    session: Option<String>,
) -> anyhow::Result<std::process::ExitCode> {
    let (cmd_name, sub_matches) = site_matches.subcommand().ok_or_else(|| {
        anyhow::anyhow!("{site}: a subcommand is required (try `tfd {site} --help`)")
    })?;

    // Find the spec for this command in the registry.
    let entries = crate::registry::all_site_entries();
    let specs = entries
        .iter()
        .find(|e| e.site.id() == site)
        .map(|e| e.specs)
        .ok_or_else(|| anyhow::anyhow!("unknown generated site '{site}'"))?;
    let spec = specs
        .iter()
        .find(|s| s.name == cmd_name)
        .ok_or_else(|| anyhow::anyhow!("unknown command '{cmd_name}' for site '{site}'"))?;

    let params = matches_to_params(spec, sub_matches).map_err(|e| anyhow::anyhow!("{e}"))?;

    if let Some(sid) = session {
        ensure_daemon_safe(socket).await?;
        let req = make_req(spec.wire, Some(&sid), params);
        let resp = client::send(socket, &req).await?;
        return Ok(super::exit_code(super::print_and_ok(&resp)));
    }

    acquire_then_exec(
        socket,
        ExecArgs {
            site,
            host,
            cmd: spec.wire,
            params,
        },
    )
    .await
}

/// Read a `global = true` argument's value, scanning the full subcommand chain
/// (fix F1). clap accepts globals at any level, so depending on where the user
/// put `--host` / `--session` / `--socket` (top-level, after `<site>`, or after
/// the command) the value can live in a nested `ArgMatches`. Reading only the
/// top-level matches would silently miss e.g. `tfd reddit hot --host X`. Walk
/// root → deepest subcommand; deepest set value wins, falling back to shallower.
pub fn global_arg<'a>(top: &'a ArgMatches, id: &str) -> Option<&'a str> {
    let mut found = top.get_one::<String>(id);
    let mut m = top;
    while let Some((_, sub)) = m.subcommand() {
        if let Some(v) = sub.get_one::<String>(id) {
            found = Some(v);
        }
        m = sub;
    }
    found.map(String::as_str)
}

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

    #[test]
    fn reddit_is_a_generated_site_with_subcommands() {
        assert!(is_generated_site("reddit"));
        assert!(is_generated_site("sa"), "sa is module-backed and generated");

        let cmds = generated_site_commands();
        let reddit = cmds
            .iter()
            .find(|c| c.get_name() == "reddit")
            .expect("reddit must be generated");
        let subs: Vec<&str> = reddit.get_subcommands().map(|c| c.get_name()).collect();
        assert!(subs.contains(&"hot"));
        assert!(subs.contains(&"search"));
        assert_eq!(subs.len(), 15);
    }

    #[test]
    fn global_arg_reads_value_from_deep_subcommand() {
        use clap::{Arg, ArgAction, Command};
        // Mimic tfd: a global --host plus the generated reddit subcommand tree.
        let cmd = Command::new("tfd")
            .arg(
                Arg::new("host")
                    .long("host")
                    .global(true)
                    .action(ArgAction::Set),
            )
            .subcommands(generated_site_commands());
        // --host given AFTER the deep command — where a user naturally puts it.
        let m = cmd
            .try_get_matches_from([
                "tfd",
                "reddit",
                "hot",
                "--host",
                "9.9.9.9:9",
                "--limit",
                "3",
            ])
            .expect("parse");
        assert_eq!(
            global_arg(&m, "host"),
            Some("9.9.9.9:9"),
            "global --host given on the generated subcommand must be read (F1)"
        );
    }
}
