use std::path::PathBuf;
use std::process::Command;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};

use memex::{build_site, build_site_with_config_path};

/// Command-line interface for memex.
#[derive(Parser)]
#[command(
    name = "memex",
    version,
    about = "Generate mdBook-ready Markdown from rustdoc JSON."
)]
struct Cli {
    /// Optional path to an memex.toml configuration file.
    #[arg(long, global = true, value_name = "PATH")]
    config: Option<PathBuf>,

    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Build the documentation site (default command).
    Build,
    /// Serve the docs surface via `mdbook serve`.
    Serve {
        /// Skip rebuilding docs before serving.
        #[arg(long)]
        no_build: bool,
        /// Do not pass `--open` to `mdbook serve`.
        #[arg(long)]
        no_open: bool,
        /// Additional arguments forwarded to `mdbook serve`.
        #[arg(long = "mdbook-arg", value_name = "ARG")]
        mdbook_args: Vec<String>,
    },
}

/// Entry point for the `memex` CLI.
fn main() {
    if let Err(err) = run() {
        eprintln!("{err:#}");
        std::process::exit(1);
    }
}

/// Parse command-line arguments and dispatch to the requested subcommand.
fn run() -> Result<()> {
    let cli = Cli::parse();
    match cli.command.unwrap_or(Commands::Build) {
        Commands::Build => run_build(cli.config),
        Commands::Serve {
            no_build,
            no_open,
            mdbook_args,
        } => run_serve(cli.config, no_build, no_open, mdbook_args),
    }
}

/// Execute the build workflow, optionally using a custom configuration path.
fn run_build(config: Option<PathBuf>) -> Result<()> {
    match config {
        Some(path) => build_site_with_config_path(path),
        None => build_site(),
    }
}

/// Run `mdbook serve`, performing a build first unless `--no-build` is set.
fn run_serve(
    config: Option<PathBuf>,
    no_build: bool,
    no_open: bool,
    mdbook_args: Vec<String>,
) -> Result<()> {
    if !no_build {
        run_build(config)?;
    }

    let mut cmd = Command::new("mdbook");
    cmd.arg("serve");
    cmd.arg("docs");
    if !no_open {
        cmd.arg("--open");
    }
    cmd.args(&mdbook_args);

    let status = cmd.status().context("failed to spawn `mdbook serve`")?;
    if status.success() {
        Ok(())
    } else {
        anyhow::bail!("command `mdbook serve` exited with status {status}");
    }
}
