"""CLI parser builder extracted from `hiveos.observe`."""

import argparse


def build_parser(*, add_help_verbose_flag, hive_version_getter, parser_cls=argparse.ArgumentParser):
    parser = parser_cls(
        prog="hive",
        description="HiveOS Trace harness: wrap workflows, inspect routes, and open runs.",
    )
    add_help_verbose_flag(parser)
    parser.add_argument(
        "--version",
        action="version",
        version=f"hive {hive_version_getter()}",
        help="Show installed hive CLI version.",
    )
    parser.add_argument(
        "--color",
        choices=["auto", "always", "never"],
        default="auto",
        help="Terminal color mode (default: auto).",
    )
    top = parser.add_subparsers(dest="command")

    quickstart_parser = top.add_parser(
        "quickstart",
        help="Run a 60-second local smoke flow (run -> ls hint -> open hint).",
        description="Run a lightweight end-to-end trace smoke test and print next-step commands.",
    )
    add_help_verbose_flag(quickstart_parser)
    quickstart_parser.add_argument("--open", action="store_true", help="Open local UI after run completion.")
    quickstart_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    quickstart_parser.add_argument("--ui-url", default="", help="Override local UI URL.")

    doctor_parser = top.add_parser(
        "doctor",
        help="Run local preflight checks for hive trace.",
        description="Validate local runtime readiness: python, trace home, and optional UI reachability.",
    )
    add_help_verbose_flag(doctor_parser)
    doctor_parser.add_argument("--ui-url", default="", help="Optional UI URL to check (default: http://localhost:5173).")

    trace_parser = top.add_parser(
        "trace",
        help="Trace harness commands.",
        description="Trace command group: capture runs, inspect output/events, replay, diagnose, and manage lifecycle.",
        epilog="* denotes a namespace with nested commands.",
    )
    add_help_verbose_flag(trace_parser)
    trace_sub = trace_parser.add_subparsers(dest="trace_command")

    run_parser = trace_sub.add_parser(
        "run",
        help="Run a command with trace capture.",
        description="Execute a command under trace capture and emit run/step/output events.",
    )
    add_help_verbose_flag(run_parser)
    run_parser.add_argument("--name", dest="run_name", default="", help="Optional run label.")
    run_parser.add_argument("--cwd", default="", help="Working directory for the command.")
    run_parser.add_argument("--timeout-sec", type=int, default=0, help="Command timeout in seconds.")
    run_parser.add_argument("--open", action="store_true", help="Open local UI after run completion.")
    run_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    run_parser.add_argument("--ui-url", default="", help="Override local UI URL.")
    run_parser.add_argument("--proxy", action="store_true", help="Enable OpenAI-compatible local proxy capture.")
    run_parser.add_argument(
        "--proxy-upstream",
        default="",
        help="Upstream OpenAI-compatible base URL (default: OPENAI_BASE_URL env or https://api.openai.com/v1).",
    )
    run_parser.add_argument("--proxy-port", type=int, default=0, help="Local proxy port (default: auto).")
    run_parser.add_argument("argv", nargs=argparse.REMAINDER, help="Command after '--', e.g. -- python agent.py")

    replay_parser = trace_sub.add_parser(
        "replay",
        help="Replay a prior run using its captured command.",
        description="Re-execute a prior run command, optionally anchored from a specific step/checkpoint.",
    )
    add_help_verbose_flag(replay_parser)
    replay_parser.add_argument("run_id", help="Run ID to replay.")
    replay_parser.add_argument("--name", dest="run_name", default="", help="Optional replay run label.")
    replay_parser.add_argument("--cwd", default="", help="Override working directory.")
    replay_parser.add_argument("--timeout-sec", type=int, default=0, help="Replay timeout in seconds.")
    replay_parser.add_argument("--open", action="store_true", help="Open local UI after replay completion.")
    replay_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    replay_parser.add_argument("--ui-url", default="", help="Override local UI URL.")
    replay_parser.add_argument("--from-step-id", default="", help="Optional lineage anchor step_id from source run.")
    replay_parser.add_argument(
        "--from-checkpoint-id",
        default="",
        help="Optional lineage anchor checkpoint_id from source run.",
    )
    replay_parser.add_argument(
        "--validation-mode",
        default="tolerant",
        choices=["deterministic", "tolerant"],
        help="Replay validation strictness (default: tolerant).",
    )

    replay_plan_parser = trace_sub.add_parser(
        "replay-plan",
        help="Preview replay intent without executing command.",
        description="Preview replay validation details and execute only when --apply is explicitly provided.",
    )
    add_help_verbose_flag(replay_plan_parser)
    replay_plan_parser.add_argument("run_id", help="Run ID to preview replay for.")
    replay_plan_parser.add_argument("--name", dest="run_name", default="", help="Optional replay run label.")
    replay_plan_parser.add_argument("--cwd", default="", help="Override working directory.")
    replay_plan_parser.add_argument("--timeout-sec", type=int, default=0, help="Replay timeout in seconds.")
    replay_plan_parser.add_argument("--open", action="store_true", help="Open local UI after replay completion.")
    replay_plan_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    replay_plan_parser.add_argument("--ui-url", default="", help="Override local UI URL.")
    replay_plan_parser.add_argument("--from-step-id", default="", help="Optional lineage anchor step_id from source run.")
    replay_plan_parser.add_argument(
        "--from-checkpoint-id",
        default="",
        help="Optional lineage anchor checkpoint_id from source run.",
    )
    replay_plan_parser.add_argument(
        "--validation-mode",
        default="tolerant",
        choices=["deterministic", "tolerant"],
        help="Replay plan validation strictness (default: tolerant).",
    )
    replay_plan_parser.add_argument(
        "--select-step",
        action="append",
        default=[],
        help="Preview-only branch selection from source run (repeatable).",
    )
    replay_plan_parser.add_argument(
        "--recommended",
        action="store_true",
        help="Auto-select the top recommended replay anchor when no explicit anchor is provided.",
    )
    replay_plan_parser.add_argument(
        "--explain",
        action="store_true",
        help="Print anchor selection rationale and candidate comparisons in text output.",
    )
    replay_plan_parser.add_argument(
        "--emit-artifact",
        action="store_true",
        help="Persist replay-plan payload as a machine-readable artifact.",
    )
    replay_plan_parser.add_argument(
        "--artifact-path",
        default="",
        help="Optional explicit path for replay-plan artifact JSON output.",
    )
    replay_plan_parser.add_argument("--apply", action="store_true", help="Execute replay using this plan.")
    replay_plan_parser.add_argument("--json", action="store_true", help="Print replay plan payload as JSON.")

    checkpoint_parser = trace_sub.add_parser(
        "checkpoint",
        help="Emit a checkpoint marker on an existing run.",
        description="Record a resumable checkpoint boundary for replay/debugger workflows.",
    )
    add_help_verbose_flag(checkpoint_parser)
    checkpoint_parser.add_argument("run_id", help="Run ID to attach checkpoint to.")
    checkpoint_parser.add_argument("checkpoint_id", nargs="?", default="", help="Checkpoint identifier to emit.")
    checkpoint_parser.add_argument(
        "--auto-id",
        action="store_true",
        help="Generate checkpoint_id automatically (ckpt:<step_or_run>:<timestamp>).",
    )
    checkpoint_parser.add_argument(
        "--from-step-id",
        default="",
        help="Optional source step_id to attach context and derive defaults.",
    )
    checkpoint_parser.add_argument("--step-name", default="", help="Optional step name label.")
    checkpoint_parser.add_argument("--state-ref", default="", help="Optional state reference URI.")
    checkpoint_parser.add_argument("--agent-id", default="observer.sdk", help="Agent label for emitted checkpoint event.")
    checkpoint_parser.add_argument("--json", action="store_true", help="Print emitted checkpoint payload as JSON.")

    anchors_parser = trace_sub.add_parser(
        "anchors",
        help="List available step/checkpoint anchors for a run.",
        description="Discover replay anchor IDs (step/checkpoint) for a source run.",
    )
    add_help_verbose_flag(anchors_parser)
    anchors_parser.add_argument("run_id", help="Run ID to inspect.")
    anchors_parser.add_argument("--event-limit", type=int, default=5000, help="Maximum events to inspect.")
    anchors_parser.add_argument("--json", action="store_true", help="Print anchors payload as JSON.")

    ls_parser = trace_sub.add_parser("ls", help="List recent traced runs.", description="List recent traced runs with status filters.")
    add_help_verbose_flag(ls_parser)
    ls_parser.add_argument("--limit", type=int, default=30, help="Maximum runs to list.")
    ls_parser.add_argument(
        "--status",
        default="all",
        choices=["all", "active", "archived", "running", "success", "failed"],
        help="Filter listed runs by lifecycle status.",
    )
    ls_parser.add_argument("--json", action="store_true", help="Print run records as JSON lines.")

    show_parser = trace_sub.add_parser("show", help="Show detailed events for one run.", description="Print run record and associated events.")
    add_help_verbose_flag(show_parser)
    show_parser.add_argument("run_id", help="Run ID to inspect.")
    show_parser.add_argument("--limit", type=int, default=200, help="Maximum events to show.")
    show_parser.add_argument("--json", action="store_true", help="Print event documents as JSON lines.")

    tail_parser = trace_sub.add_parser(
        "tail",
        help="Tail recent trace events, optionally following updates.",
        description="Tail trace events globally or for a specific run, with optional follow mode.",
    )
    add_help_verbose_flag(tail_parser)
    tail_parser.add_argument("--run-id", default="", help="Optional run ID filter.")
    tail_parser.add_argument("--limit", type=int, default=50, help="Maximum events to emit per poll.")
    tail_parser.add_argument("--follow", action="store_true", help="Keep polling and print newly observed events.")
    tail_parser.add_argument("--json", action="store_true", help="Print event documents as JSON lines.")

    flow_parser = trace_sub.add_parser(
        "flow",
        help="* inspect flow-level lineage views.",
        description="Flow lineage commands for grouped step-level inspection and comparisons.",
    )
    add_help_verbose_flag(flow_parser)
    flow_sub = flow_parser.add_subparsers(dest="flow_command")

    flow_ls_parser = flow_sub.add_parser("ls", help="List recent flows from trace events.", description="List recent flow IDs and basic outcome stats.")
    add_help_verbose_flag(flow_ls_parser)
    flow_ls_parser.add_argument("--limit", type=int, default=30, help="Maximum flows to list.")
    flow_ls_parser.add_argument("--json", action="store_true", help="Print flow summaries as JSON lines.")

    flow_show_parser = flow_sub.add_parser("show", help="Show flow timeline for one flow_id.", description="Show ordered events for a single flow_id.")
    add_help_verbose_flag(flow_show_parser)
    flow_show_parser.add_argument("flow_id", help="Flow ID to inspect.")
    flow_show_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    flow_show_parser.add_argument("--json", action="store_true", help="Print flow timeline payload as JSON.")

    flow_steps_parser = flow_sub.add_parser("steps", help="Show derived step summary for one flow_id.", description="Show step-level derived summary for a flow_id.")
    add_help_verbose_flag(flow_steps_parser)
    flow_steps_parser.add_argument("flow_id", help="Flow ID to inspect.")
    flow_steps_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    flow_steps_parser.add_argument("--json", action="store_true", help="Print flow step summary as JSON.")

    flow_diff_parser = flow_sub.add_parser("diff", help="Compare two flows and highlight divergence.", description="Compare two flows and report divergence and retry deltas.")
    add_help_verbose_flag(flow_diff_parser)
    flow_diff_parser.add_argument("flow_id_a", help="First flow ID.")
    flow_diff_parser.add_argument("flow_id_b", help="Second flow ID.")
    flow_diff_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect per flow.")
    flow_diff_parser.add_argument("--json", action="store_true", help="Print flow diff as JSON.")

    tei_parser = trace_sub.add_parser(
        "tei",
        help="* validate TEI payloads before ingest.",
        description="TEI utility commands for framework-facing event contract validation.",
    )
    add_help_verbose_flag(tei_parser)
    tei_sub = tei_parser.add_subparsers(dest="tei_command")

    tei_validate_parser = tei_sub.add_parser(
        "validate",
        help="Validate one TEI event object or batch array.",
        description="Validate TEI JSON from --file or stdin against the TEI contract.",
    )
    add_help_verbose_flag(tei_validate_parser)
    tei_validate_parser.add_argument(
        "--file",
        default="",
        help="Path to TEI JSON (object or array). Omit to read JSON from stdin.",
    )
    tei_validate_parser.add_argument(
        "--strict-version",
        dest="strict_version",
        action="store_true",
        default=True,
        help="Require TEI version match (default: enabled).",
    )
    tei_validate_parser.add_argument(
        "--no-strict-version",
        dest="strict_version",
        action="store_false",
        help="Allow TEI version mismatch during validation.",
    )
    tei_validate_parser.add_argument("--json", action="store_true", help="Print validation result as JSON.")

    tei_ingest_parser = tei_sub.add_parser(
        "ingest",
        help="Validate and ingest TEI JSON into trace store.",
        description="Ingest TEI JSON (object or array) from --file or stdin into Hive trace.",
    )
    add_help_verbose_flag(tei_ingest_parser)
    tei_ingest_parser.add_argument(
        "--file",
        default="",
        help="Path to TEI JSON (object or array). Omit to read JSON from stdin.",
    )
    tei_ingest_parser.add_argument(
        "--strict-version",
        dest="strict_version",
        action="store_true",
        default=True,
        help="Require TEI version match during ingest (default: enabled).",
    )
    tei_ingest_parser.add_argument(
        "--no-strict-version",
        dest="strict_version",
        action="store_false",
        help="Allow TEI version mismatch during ingest.",
    )
    tei_ingest_parser.add_argument("--agent-id", default="observer.tei", help="Agent label for emitted trace events.")
    tei_ingest_parser.add_argument("--json", action="store_true", help="Print ingest result as JSON.")

    artifacts_parser = trace_sub.add_parser(
        "artifacts",
        help="* inspect persisted trace artifacts.",
        description="Artifact registry commands for replay-plan and future debugger artifacts.",
    )
    add_help_verbose_flag(artifacts_parser)
    artifacts_sub = artifacts_parser.add_subparsers(dest="artifacts_command")

    artifacts_ls_parser = artifacts_sub.add_parser("ls", help="List persisted trace artifacts.")
    add_help_verbose_flag(artifacts_ls_parser)
    artifacts_ls_parser.add_argument("--limit", type=int, default=30, help="Maximum artifacts to list.")
    artifacts_ls_parser.add_argument("--type", default="", help="Optional artifact_type filter (e.g. replay_plan).")
    artifacts_ls_parser.add_argument("--json", action="store_true", help="Print artifact rows as JSON lines.")

    artifacts_show_parser = artifacts_sub.add_parser("show", help="Show one artifact by id or path.")
    add_help_verbose_flag(artifacts_show_parser)
    artifacts_show_parser.add_argument("artifact_ref", help="Artifact id (preferred) or artifact file path.")
    artifacts_show_parser.add_argument("--json", action="store_true", help="Print full artifact JSON.")

    artifacts_replay_parser = artifacts_sub.add_parser(
        "replay",
        help="Execute replay from a persisted replay-plan artifact.",
    )
    add_help_verbose_flag(artifacts_replay_parser)
    artifacts_replay_parser.add_argument("artifact_ref", help="Artifact id (preferred) or artifact file path.")
    artifacts_replay_parser.add_argument("--name", dest="run_name", default="", help="Optional replay run label override.")
    artifacts_replay_parser.add_argument("--cwd", default="", help="Override working directory from artifact plan.")
    artifacts_replay_parser.add_argument("--timeout-sec", type=int, default=0, help="Replay timeout in seconds.")
    artifacts_replay_parser.add_argument("--open", action="store_true", help="Open local UI after replay completion.")
    artifacts_replay_parser.add_argument("--no-open", action="store_true", help="Do not open local UI automatically.")
    artifacts_replay_parser.add_argument("--ui-url", default="", help="Override local UI URL.")

    summary_parser = trace_sub.add_parser("summary", help="Show compact run summary with output tails.", description="Summarize run status, timing, event counts, and output tails.")
    add_help_verbose_flag(summary_parser)
    summary_parser.add_argument("run_id", help="Run ID to summarize.")
    summary_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    summary_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include per stream.")
    summary_parser.add_argument("--json", action="store_true", help="Print run summary as JSON.")

    diff_parser = trace_sub.add_parser("diff", help="Compare two runs and highlight key deltas.", description="Compare run inputs and outputs to identify where behavior diverged.")
    add_help_verbose_flag(diff_parser)
    diff_parser.add_argument("run_id_a", help="First run ID.")
    diff_parser.add_argument("run_id_b", help="Second run ID.")
    diff_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect per run.")
    diff_parser.add_argument("--tail-lines", type=int, default=20, help="Output tail lines to compare per stream.")
    diff_parser.add_argument("--json", action="store_true", help="Print diff summary as JSON.")

    diagnose_parser = trace_sub.add_parser("diagnose", help="Diagnose one run and suggest next actions.", description="Classify failure/success patterns and emit recommended next actions.")
    add_help_verbose_flag(diagnose_parser)
    diagnose_parser.add_argument("run_id", help="Run ID to diagnose.")
    diagnose_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    diagnose_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include in summary.")
    diagnose_parser.add_argument("--json", action="store_true", help="Print diagnosis as JSON.")

    open_parser = trace_sub.add_parser("open", help="Open a run in local UI.", description="Open a run in configured UI or local fallback page when unreachable.")
    add_help_verbose_flag(open_parser)
    open_parser.add_argument("run_id", help="Run ID to open.")
    open_parser.add_argument("--ui-url", default="", help="Override local UI URL.")

    insight_parser = trace_sub.add_parser(
        "insight",
        help="* insight macros composed from trace primitives.",
        description="Composed insight commands (glass-box macros) built from trace primitives.",
    )
    add_help_verbose_flag(insight_parser)
    insight_sub = insight_parser.add_subparsers(dest="insight_command")

    insight_explain_parser = insight_sub.add_parser(
        "explain",
        help="Explain one run with answer, evidence, and next actions.",
        description="Compose summary+diagnose primitives into a concise run explanation.",
    )
    add_help_verbose_flag(insight_explain_parser)
    insight_explain_parser.add_argument("run_id", help="Run ID to explain.")
    insight_explain_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect.")
    insight_explain_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include in analysis.")
    insight_explain_parser.add_argument("--show-source", action="store_true", help="Include source primitive payload excerpts.")
    insight_explain_parser.add_argument("--emit-primitive", action="store_true", help="Print equivalent primitive commands.")
    insight_explain_parser.add_argument("--json", action="store_true", help="Print macro payload as JSON.")

    insight_drift_parser = insight_sub.add_parser(
        "drift",
        help="Compare two runs and explain behavioral drift.",
        description="Compose primitive diff into concise drift conclusions, evidence, and actions.",
    )
    add_help_verbose_flag(insight_drift_parser)
    insight_drift_parser.add_argument("run_id_a", help="First run ID.")
    insight_drift_parser.add_argument("run_id_b", help="Second run ID.")
    insight_drift_parser.add_argument("--event-limit", type=int, default=2000, help="Maximum events to inspect per run.")
    insight_drift_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include in analysis.")
    insight_drift_parser.add_argument("--show-source", action="store_true", help="Include source primitive payload excerpts.")
    insight_drift_parser.add_argument("--emit-primitive", action="store_true", help="Print equivalent primitive commands.")
    insight_drift_parser.add_argument("--json", action="store_true", help="Print scaffold payload as JSON.")

    insight_health_parser = insight_sub.add_parser(
        "health",
        help="Report aggregate run health over a time window.",
        description="Compose ls + diagnose style signals into windowed health summary and actions.",
    )
    add_help_verbose_flag(insight_health_parser)
    insight_health_parser.add_argument("--window", default="24h", help="Rolling window (e.g. 24h, 7d).")
    insight_health_parser.add_argument("--tail-lines", type=int, default=20, help="Tail lines to include when diagnosing sample failures.")
    insight_health_parser.add_argument("--show-source", action="store_true", help="Include source records and sampled diagnoses.")
    insight_health_parser.add_argument("--emit-primitive", action="store_true", help="Print equivalent primitive commands.")
    insight_health_parser.add_argument("--json", action="store_true", help="Print macro payload as JSON.")

    ops_parser = trace_sub.add_parser(
        "ops",
        help="* ops lifecycle and portability commands.",
        description="Lifecycle/admin command namespace for archive, prune, reconcile, export, and import.",
    )
    add_help_verbose_flag(ops_parser)
    ops_sub = ops_parser.add_subparsers(dest="ops_command")

    ops_archive_parser = ops_sub.add_parser("archive", help="Archive a traced run.")
    add_help_verbose_flag(ops_archive_parser)
    ops_archive_parser.add_argument("run_id", help="Run ID to archive.")
    ops_archive_parser.add_argument("--reason", default="", help="Optional archive reason.")
    ops_archive_parser.add_argument("--actor", default="operator", help="Actor label for auditability.")

    ops_unarchive_parser = ops_sub.add_parser("unarchive", help="Restore archived run.")
    add_help_verbose_flag(ops_unarchive_parser)
    ops_unarchive_parser.add_argument("run_id", help="Run ID to unarchive.")

    ops_prune_parser = ops_sub.add_parser("prune", help="Prune old runs and optional event payloads.")
    add_help_verbose_flag(ops_prune_parser)
    ops_prune_parser.add_argument("--older-than", required=True, help="Age threshold (e.g. 30d, 12h, 45m, 3600s).")
    ops_prune_parser.add_argument(
        "--include-active",
        action="store_true",
        help="Also prune non-archived runs older than threshold (default prunes archived only).",
    )
    ops_prune_parser.add_argument(
        "--drop-events",
        action="store_true",
        help="Also drop matching run events from associated trace log files.",
    )

    ops_reconcile_parser = ops_sub.add_parser("reconcile", help="Reconcile stale running runs.")
    add_help_verbose_flag(ops_reconcile_parser)
    ops_reconcile_parser.add_argument("--stale-after", default="", help="Stale window (e.g. 5m, 120s).")
    ops_reconcile_parser.add_argument("--json", action="store_true", help="Print reconciliation summary as JSON.")

    ops_export_parser = ops_sub.add_parser("export", help="Export run bundle.")
    add_help_verbose_flag(ops_export_parser)
    ops_export_parser.add_argument("run_id", help="Run ID to export.")
    ops_export_parser.add_argument("--bundle", required=True, help="Bundle file path to write.")

    ops_import_parser = ops_sub.add_parser("import", help="Import run bundle.")
    add_help_verbose_flag(ops_import_parser)
    ops_import_parser.add_argument("--bundle", required=True, help="Bundle file path to read.")
    ops_import_parser.add_argument("--as-run-id", default="", help="Optional replacement run ID on import.")

    return parser
