//! 顶层 CLI 类型与运行入口。

use clap::{Parser, Subcommand};
use color_eyre::eyre::{WrapErr, bail};
use tokio::runtime::Runtime;

use crate::subcommands::action;
use crate::subcommands::cache::{self, CacheCommand};
use crate::subcommands::channel::{self, ChannelArgs};
use crate::subcommands::config::{self, ConfigCommand};
use crate::subcommands::stats::{self, StatsCommand};
use crate::subcommands::{status, stop};

/// 多源终端音乐播放器。无子命令时进入 TUI。
#[derive(Debug, Parser)]
#[command(
    name = "mineral",
    version,
    about = "Mineral — 多源终端音乐播放器",
    long_about = None,
)]
pub struct Args {
    /// 子命令;省略则进入 TUI。
    #[command(subcommand)]
    pub command: Option<Command>,

    /// 强制连接到已经在跑的后台 daemon(由 `mineral serve` 起),连不上即报错;
    /// 关闭 TUI 时不停 daemon,音乐继续播。默认(不带此 flag)会在没有 daemon 时
    /// 自动 spawn 一个。
    #[arg(long, conflicts_with = "in_proc")]
    pub connect: bool,

    /// in-proc 模式:TUI 自己在同进程内起 server,不走 daemon / socket。
    /// 调试与离线开发用;关闭 TUI = 进程退 = server 一起退。
    #[arg(long)]
    pub in_proc: bool,
}

/// 顶层子命令。
#[derive(Debug, Subcommand)]
pub enum Command {
    /// 触发 config.lua 里 `mineral.action` 注册的具名动作(连 daemon)。
    Action {
        /// 动作注册名,如 "my.skip_short"。
        name: String,

        /// 传给动作回调的位置实参(Lua 侧经 `ctx.args` 读取);可省。
        #[arg(trailing_var_arg = true)]
        args: Vec<String>,
    },

    /// 缓存管理
    Cache {
        /// cache 下的具体子命令。
        #[command(subcommand)]
        cmd: CacheCommand,
    },

    /// 管理音乐源
    Channel(ChannelArgs),

    /// 用户配置
    Config {
        /// config 下的具体子命令
        #[command(subcommand)]
        cmd: ConfigCommand,
    },

    /// 启动后台播放 daemon
    Serve,

    /// 埋点数据查询
    Stats {
        /// stats 下的具体子命令
        #[command(subcommand)]
        cmd: StatsCommand,
    },

    /// 显示当前播放状态
    Status,

    /// 请求后台 daemon 优雅退出;
    Stop,
}

/// 执行解析后的 CLI 命令。**不处理 [`Command::Serve`]**——那个需要 channels,
/// 由 caller(main.rs) 拦截后自己调 [`crate::serve_run`]。
///
/// # Params:
///   - `command`: 已经从命令行解析出的顶层命令。
///
/// # Return:
///   命令执行结果。
pub fn run(command: Command) -> color_eyre::Result<()> {
    let runtime = Runtime::new().wrap_err("create tokio runtime failed")?;
    runtime.block_on(async move { run_async(command).await })?;
    Ok(())
}

/// 在 tokio 上下文里按 [`Command`] 分发到具体子命令;`Serve` 由 caller(binary)拦截不该到这里。
async fn run_async(command: Command) -> color_eyre::Result<()> {
    match command {
        Command::Action { name, args } => action::run(&name, &args).await,
        Command::Cache { cmd } => cache::run(cmd).await,
        Command::Channel(args) => channel::run(args).await,
        Command::Config { cmd } => config::run(cmd).await,
        Command::Stats { cmd } => stats::run(cmd).await,
        Command::Status => status::run().await,
        Command::Stop => stop::run().await,
        Command::Serve => bail!("internal error: Command::Serve must be intercepted by caller"),
    }
}
