//! koala-core CLI — clap definitions only. Each subcommand handler
//! lives in `crates/koala-cli/src/commands/<module>.rs`.

mod commands;

use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::process::ExitCode;

/// `<crate-version> (<short-sha> <commit-iso-date>)`. Build-time
/// metadata comes from `crates/koala-cli/build.rs`; falls back to
/// `unknown` when the source isn't a git checkout.
const LONG_VERSION: &str = concat!(
    env!("CARGO_PKG_VERSION"),
    " (",
    env!("KOALA_BUILD_SHA"),
    " ",
    env!("KOALA_BUILD_DATE"),
    ")"
);

#[derive(Parser)]
#[command(name = "koala-core", version = LONG_VERSION, about = "koala framework CLI")]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    /// Inspect or run the invariant suite.
    #[command(subcommand)]
    Invariant(InvariantCmd),
    /// Detect drift between wiki claims and the actual repo state.
    Drift {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
        /// Print full claims and emphasise fix hints (verbose mode).
        #[arg(long)]
        explain: bool,
        /// Output format: `text` (default) or `github` (Actions annotations).
        #[arg(long, value_parser = ["text", "github"], default_value = "text")]
        format: String,
    },
    /// Record or verify reviewer artifacts (ADR-0003 / ADR-0005).
    #[command(subcommand)]
    Artifact(ArtifactCmd),
    /// Aggregate gate: invariants → drift → artifact verify → redlines.
    Check {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
        #[arg(long, conflicts_with_all = ["drift_only", "no_artifact"])]
        invariant_only: bool,
        #[arg(long, conflicts_with_all = ["invariant_only", "no_artifact"])]
        drift_only: bool,
        #[arg(long)]
        no_artifact: bool,
        #[arg(long, default_value_t = 10)]
        sample: u32,
        #[arg(long)]
        seed: Option<String>,
        /// `light` (default) or `strict` (drift advisories block, redlines block).
        #[arg(long, value_parser = ["light", "strict"], default_value = "light")]
        profile: String,
    },
    /// 30-second project-state snapshot.
    Status {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Snapshot health metrics; `update` rewrites `wiki/health.md`.
    #[command(subcommand)]
    Health(HealthCmd),
    /// Pruning sprint scaffolding (ADR-0010).
    #[command(subcommand)]
    Pruning(PruningCmd),
    /// Onboarding probe scaffolding.
    #[command(subcommand)]
    Onboard(OnboardCmd),
    /// ADR lifecycle: new / list / supersede / validate.
    #[command(subcommand)]
    Adr(AdrCmd),
    /// Tier 1 wiki rewrite: `wiki/_tags/*.md`,
    /// `wiki/features/_index.md`, `wiki/decisions/_index.md`.
    #[command(subcommand)]
    Wiki(WikiCmd),
    /// PR size + required gates (`adaptive-workflow.md`).
    #[command(subcommand)]
    Workflow(WorkflowCmd),
    /// Scaffold a new project from the bundled templates.
    Init {
        /// Target directory (defaults to current directory).
        #[arg(long, value_name = "PATH")]
        path: Option<PathBuf>,
        /// Overwrite existing files instead of skipping them.
        #[arg(long)]
        force: bool,
    },
}

#[derive(Subcommand)]
enum WorkflowCmd {
    /// Classify the diff against `<base>..HEAD` and print the size +
    /// gate table.
    Size {
        /// Base ref for the diff. Defaults to `origin/main`.
        #[arg(long, default_value = "origin/main")]
        base: String,
        /// Use merge-base diff (`base...HEAD`) instead of two-dot.
        #[arg(long)]
        merge_base: bool,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Run gate enforcement: classify size, then check that the diff
    /// satisfies every required gate (artifacts / feature-sync / ADR /
    /// human-review).
    Enforce {
        #[arg(long, default_value = "origin/main")]
        base: String,
        #[arg(long)]
        merge_base: bool,
        /// Treat the PR as having human approval (architectural PRs need this).
        #[arg(long)]
        human_review: bool,
        /// Resolve human review status from `gh pr view <N>` (overrides
        /// `--human-review`). Falls back to the flag if `gh` is missing.
        #[arg(long, value_name = "PR_NUMBER")]
        from_gh_pr: Option<u32>,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum WikiCmd {
    /// Re-render every Tier 1 file from the current repo state.
    Gen {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Walk `.wiki-draft/` and open one `gh pr create --label wiki-refresh`
    /// per draft. With `--dry-run`, prints what would be opened without
    /// invoking gh.
    RefreshPr {
        #[arg(long)]
        dry_run: bool,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum AdrCmd {
    /// Create a new ADR with auto-incrementing id.
    New {
        #[arg(long)]
        title: String,
        #[arg(long)]
        date: Option<String>,
        #[arg(long, value_delimiter = ',')]
        tags: Vec<String>,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Print every ADR with status and inbound-reference count.
    List {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Mark `<old>` as superseded by `<new>`. Writes both files.
    Supersede {
        old: u32,
        new: u32,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Validate the ADR graph (cycles, dangling links, status mismatch).
    Validate {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum PruningCmd {
    /// Generate `.koala/pruning/<sprint>.md` from active redlines + drift advisories.
    Start {
        sprint: String,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Mark a sprint closed by writing `.koala/pruning/<sprint>.closed`.
    Close {
        sprint: String,
        /// ISO-8601 timestamp; defaults to today UTC.
        #[arg(long)]
        at: Option<String>,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum OnboardCmd {
    /// Write a fresh-context probe template at `.koala/probes/probe-<date>.md`.
    Probe {
        #[arg(long)]
        date: Option<String>,
        #[arg(long)]
        label: Option<String>,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Record probe friction count; > 3 writes a redline.
    Friction {
        #[arg(long)]
        date: Option<String>,
        #[arg(long)]
        count: usize,
        /// Free-form notes; repeat the flag for multiple lines.
        #[arg(long = "note", value_name = "TEXT")]
        notes: Vec<String>,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum HealthCmd {
    /// Collect metrics and print them; no side effects.
    Show {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Collect, print, and overwrite `wiki/health.md` + history TSV + redline files.
    Update {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
    /// Render a per-metric sparkline + 3-window regression hint.
    Trend {
        #[arg(long)]
        metric: String,
        #[arg(long, default_value_t = 20)]
        window: usize,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
enum ArtifactCmd {
    /// Run a reviewer command and record `.review/round-N/<kind>-<name>.md`.
    Record {
        kind: String,
        #[arg(long)]
        name: String,
        #[arg(long, default_value_t = 1)]
        round: u32,
        #[arg(long)]
        reviewer: Option<String>,
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
        #[arg(trailing_var_arg = true, allow_hyphen_values = true, num_args = 1..)]
        command: Vec<String>,
    },
    /// Re-run a sample of recorded artifacts and verify their hashes.
    Verify {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
        #[arg(long, default_value_t = 10)]
        sample: u32,
        #[arg(long)]
        seed: Option<String>,
    },
}

#[derive(Subcommand)]
enum InvariantCmd {
    List,
    Explain {
        id: String,
    },
    Run {
        #[arg(long, value_name = "PATH")]
        root: Option<PathBuf>,
    },
}

fn main() -> ExitCode {
    let cli = Cli::parse();
    match cli.command {
        Command::Invariant(InvariantCmd::List) => commands::invariant::list(),
        Command::Invariant(InvariantCmd::Explain { id }) => commands::invariant::explain(&id),
        Command::Invariant(InvariantCmd::Run { root }) => commands::invariant::run(root),
        Command::Drift {
            root,
            explain,
            format,
        } => commands::drift::run(root, explain, format),
        Command::Artifact(ArtifactCmd::Record {
            kind,
            name,
            round,
            reviewer,
            root,
            command,
        }) => commands::artifact::record(&kind, &name, round, reviewer, root, command),
        Command::Artifact(ArtifactCmd::Verify { root, sample, seed }) => {
            commands::artifact::verify_cmd(root, sample, seed)
        }
        Command::Check {
            root,
            invariant_only,
            drift_only,
            no_artifact,
            sample,
            seed,
            profile,
        } => commands::check::run(
            root,
            invariant_only,
            drift_only,
            no_artifact,
            sample,
            seed,
            profile,
        ),
        Command::Status { root } => commands::status::run(root),
        Command::Health(HealthCmd::Show { root }) => commands::health::run(root, false),
        Command::Health(HealthCmd::Update { root }) => commands::health::run(root, true),
        Command::Health(HealthCmd::Trend {
            metric,
            window,
            root,
        }) => commands::health::trend(root, metric, window),
        Command::Pruning(PruningCmd::Start { sprint, root }) => {
            commands::pruning::start(root, sprint)
        }
        Command::Pruning(PruningCmd::Close { sprint, at, root }) => {
            commands::pruning::close(root, sprint, at)
        }
        Command::Onboard(OnboardCmd::Probe { date, label, root }) => {
            commands::onboard::probe(root, date, label)
        }
        Command::Onboard(OnboardCmd::Friction {
            date,
            count,
            notes,
            root,
        }) => commands::onboard::friction(root, date, count, notes),
        Command::Adr(AdrCmd::New {
            title,
            date,
            tags,
            root,
        }) => commands::adr::new(root, title, date, tags),
        Command::Adr(AdrCmd::List { root }) => commands::adr::list(root),
        Command::Adr(AdrCmd::Supersede { old, new, root }) => {
            commands::adr::supersede(root, old, new)
        }
        Command::Adr(AdrCmd::Validate { root }) => commands::adr::validate(root),
        Command::Wiki(WikiCmd::Gen { root }) => commands::wiki::gen_cmd(root),
        Command::Wiki(WikiCmd::RefreshPr { dry_run, root }) => {
            commands::wiki::refresh_pr_cmd(root, dry_run)
        }
        Command::Workflow(WorkflowCmd::Size {
            base,
            merge_base,
            root,
        }) => commands::workflow::size(root, base, merge_base),
        Command::Workflow(WorkflowCmd::Enforce {
            base,
            merge_base,
            human_review,
            from_gh_pr,
            root,
        }) => commands::workflow::enforce_cmd(root, base, merge_base, human_review, from_gh_pr),
        Command::Init { path, force } => commands::init::run(path, force),
    }
}
