"""argparse 표면과 명령 분배.

여기 있는 것은 인자 정의와 `main` 의 분기뿐이다. 각 명령의 실제 동작은
`materialize` 와 `results` 에 있다. `_CLI_EPILOG` 는 이 명령의 사용법 정본이다 —
TypeScript 포워더가 들고 있던 USAGE 문자열이 여기로 왔다.
"""
from __future__ import annotations

import argparse
from pathlib import Path
import sys

from ..invocation import AgentInvocationError
from ...worker_prompt_headers import WorkerPromptHeaderError
from ...error_log_write import record_runtime_failure
from ...dispatch_state import BACKEND_CLI_WRAPPER, BACKEND_CMUX_PANE, DispatchError
from .emit import _emit, _prepared_payload
from .inputs import AgentPromptCliError
from .materialize import _apply_corrections, _check_corrections, _materialize
from .jobs import generate_jobs
from .results import (
    _complete,
    _link_result,
    _materialize_result,
    _record_dispatch,
    _abandon_attempt,
    _reject_result,
    _verify,
    _verify_completion,
)


_CLI_EPILOG = r"""Usage:
  okstra agent-prompt materialize [options]
  okstra agent-prompt jobs --project-root <dir> --run-manifest <path> --dispatch-kind <kind> --metadata <path> [--metadata <path>] --out <path>
  okstra agent-prompt check-corrections --project-root <dir> --run-manifest <path> --corrections <file>
  okstra agent-prompt apply-corrections --project-root <dir> --run-manifest <path> --corrections <file>
  okstra agent-prompt verify [options]
  okstra agent-prompt materialize-result [options]
  okstra agent-prompt complete [options]
  okstra agent-prompt verify-completion [options]
  okstra agent-prompt record-dispatch [options]
  okstra agent-prompt link-result [options]
"""


_CLI_DESCRIPTION = "Materialize and verify auditable agent invocations."


def _parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        description=_CLI_DESCRIPTION,
        epilog=_CLI_EPILOG,
        formatter_class=argparse.RawDescriptionHelpFormatter,
        prog="okstra agent-prompt")
    commands = parser.add_subparsers(dest="command", required=True)
    _add_materialize_parser(commands)
    _add_completion_parsers(commands)
    _add_dispatch_parsers(commands)
    jobs = commands.add_parser("jobs", help="generate a verified jobs file without dispatching")
    _common_paths(jobs)
    jobs.add_argument("--run-manifest", required=True)
    jobs.add_argument("--dispatch-kind", required=True)
    jobs.add_argument("--metadata", action="append", required=True)
    jobs.add_argument("--out", required=True)
    jobs.add_argument("--json", action="store_true")
    return parser


def _add_materialize_parser(commands: argparse._SubParsersAction) -> None:
    materialize = commands.add_parser("materialize")
    _common_paths(materialize)
    materialize.add_argument("--invocation-id", required=True)
    materialize.add_argument("--audience", required=True)
    materialize.add_argument("--instruction", required=True)
    materialize.add_argument("--prompt", required=True)
    # 두 갈래의 인자를 한 파서가 받는다. 어느 갈래의 것인지 help 에 적지 않으면
    # `--help` 가 조건 없이 나열한 인자를 run 갈래가 거절해 놀라게 된다.
    run_only = "run mode only (with --run-manifest)"
    standalone_only = "standalone mode only; refused together with --run-manifest"
    materialize.add_argument(
        "--run-manifest",
        help="selects run mode: identity, paths and duty snapshot come from this "
             "manifest, and the standalone-only flags below are refused",
    )
    materialize.add_argument("--worker-id", help=run_only)
    materialize.add_argument("--dispatch-kind", help=run_only)
    materialize.add_argument("--assignment-ref", help=run_only)
    materialize.add_argument("--source-role-execution-ref", help=run_only)
    materialize.add_argument("--result", help=run_only)
    materialize.add_argument(
        "--corrections",
        help=run_only + "; report-writer only: a corrections ledger "
             "(report-writer-corrections-v1.0) checked against the base "
             "narrative before dispatch; okstra renders the prompt's "
             "## Corrections section from it. Required once a parsing "
             "narrative exists (a corrective dispatch); ## Output is rendered "
             "on every report-writer prompt",
    )
    materialize.add_argument(
        "--audit-source",
        help=run_only + "; accepted only for report-writer and translator, whose "
             "result is not their own worker result",
    )
    materialize.add_argument("--host-runtime", help=standalone_only)
    materialize.add_argument(
        "--terminal-backend",
        choices=(BACKEND_CLI_WRAPPER, BACKEND_CMUX_PANE),
        help=standalone_only,
    )
    materialize.add_argument("--provider", help=standalone_only)
    materialize.add_argument("--model-role", help=standalone_only)
    materialize.add_argument("--model", default="", help=standalone_only)
    materialize.add_argument("--purpose", help=standalone_only)
    materialize.add_argument(
        "--replace-undispatched",
        action="store_true",
        help="rewrite a prompt this invocation id already wrote, allowed only "
             "while no dispatch has referenced it — the exit for a prompt that "
             "failed a pre-dispatch gate and never ran. Not available for v2 "
             "reverify prompts (--assignment-ref reverify/…): their reservation "
             "records the prompt's input digest in the run manifest and "
             "reservations are append-only, so a rewrite needs a fresh "
             "invocation id and prompt path",
    )
    materialize.add_argument("--json", action="store_true")

    check = commands.add_parser(
        "check-corrections",
        help="check a report-writer corrections ledger against the base "
             "narrative without materializing a prompt; exit 1 lists every defect",
    )
    _common_paths(check)
    check.add_argument("--run-manifest", required=True)
    check.add_argument("--corrections", required=True)
    check.add_argument("--json", action="store_true")

    apply = commands.add_parser(
        "apply-corrections",
        help="apply a report-writer corrections ledger of replace/remove/add/move "
             "entries to the narrative without a writer round and record a "
             "lead-correction-applied activity row; rewrite entries require "
             "--rewrite-results; any remaining defect is refused",
    )
    _common_paths(apply)
    apply.add_argument("--run-manifest", required=True)
    apply.add_argument("--corrections", required=True)
    apply.add_argument("--rewrite-results", help="JSON replacement values for every rewrite id, with the base narrative SHA-256")
    apply.add_argument("--json", action="store_true")

    verify = commands.add_parser("verify")
    _common_paths(verify)
    verify.add_argument("--run-manifest")
    verify.add_argument("--metadata", required=True)
    verify.add_argument("--json", action="store_true")
    verify.add_argument("--text", action="store_true")


def _add_completion_parsers(commands: argparse._SubParsersAction) -> None:
    materialize_result = commands.add_parser("materialize-result")
    _standalone_completion_args(materialize_result)
    materialize_result.add_argument("--returned-body-file", required=True)

    complete = commands.add_parser("complete")
    _standalone_completion_args(complete)

    verify_completion = commands.add_parser("verify-completion")
    _common_paths(verify_completion)
    verify_completion.add_argument("--purpose", required=True)
    verify_completion.add_argument("--completion", required=True)
    verify_completion.add_argument("--json", action="store_true")


def _add_dispatch_parsers(commands: argparse._SubParsersAction) -> None:
    record_dispatch = commands.add_parser("record-dispatch")
    _common_paths(record_dispatch)
    record_dispatch.add_argument("--run-manifest", required=True)
    record_dispatch.add_argument("--metadata", required=True)
    record_dispatch.add_argument(
        "--enforcement-mode",
        required=True,
        choices=("core-pre-dispatch", "host-native-spec-link-gate"),
    )
    record_dispatch.add_argument("--json", action="store_true")

    reject_result = commands.add_parser(
        "reject-result",
        help="mark a linked result rejected so a corrective re-dispatch can "
             "claim its path. The corrective dispatch is a new invocation: an "
             "invocation whose last attempt finished with a mutation takes no "
             "further attempt (only `failed-no-mutation` may be followed), so "
             "a retry of the rejected invocation is refused by the execution "
             "manifest, not by this command",
    )
    _common_paths(reject_result)
    reject_result.add_argument("--run-manifest", required=True)
    reject_result.add_argument("--dispatch-id", required=True)
    reject_result.add_argument("--superseded-by", required=True)
    reject_result.add_argument("--reason", required=True)
    reject_result.add_argument("--json", action="store_true")

    abandon = commands.add_parser(
        "abandon-attempt",
        help="close a started source-readonly attempt whose worker died "
             "without a result, so a retry can follow it",
    )
    _common_paths(abandon)
    abandon.add_argument("--run-manifest", required=True)
    abandon.add_argument("--invocation-ref", required=True)
    abandon.add_argument("--reason", required=True)
    abandon.add_argument("--json", action="store_true")

    link_result = commands.add_parser("link-result")
    _common_paths(link_result)
    link_result.add_argument("--run-manifest", required=True)
    link_result.add_argument("--dispatch-id", required=True)
    link_result.add_argument("--result", required=True)
    link_result.add_argument("--json", action="store_true")


def _common_paths(parser: argparse.ArgumentParser) -> None:
    parser.add_argument("--project-root", required=True)


def _standalone_completion_args(parser: argparse.ArgumentParser) -> None:
    _common_paths(parser)
    parser.add_argument("--purpose", required=True)
    parser.add_argument("--metadata", required=True)
    parser.add_argument("--json", action="store_true")


def main(argv: list[str] | None = None) -> int:
    try:
        args = _parser().parse_args(argv)
        if args.command == "jobs":
            payload = generate_jobs(args)
            if args.json:
                _emit(payload, True)
            else:
                print(payload["jobsPath"])
            return 0
        if args.command == "materialize":
            prepared = _materialize(args)
            _emit(_prepared_payload(prepared, args.audience), args.json)
            return 0
        if args.command == "check-corrections":
            payload = _check_corrections(args)
            if args.json:
                _emit(payload, True)
            else:
                for defect in payload["defects"]:
                    print(defect, file=sys.stderr)
                print("ok" if payload["ok"] else "defects: " + str(len(payload["defects"])))
            return 0 if payload["ok"] else 1
        if args.command == "apply-corrections":
            payload = _apply_corrections(args)
            if args.json:
                _emit(payload, True)
            else:
                print(payload["activityLine"])
                print(payload["narrativePath"])
            return 0
        if args.command == "verify":
            _verify(args)
            _emit(
                {"ok": True, "metadataPath": str(Path(args.metadata).resolve())},
                args.json,
                args.text,
            )
            return 0
        if args.command == "materialize-result":
            _materialize_result(args)
            return 0
        if args.command == "complete":
            _complete(args)
            return 0
        if args.command == "verify-completion":
            _verify_completion(args)
            return 0
        if args.command == "record-dispatch":
            _record_dispatch(args)
            return 0
        if args.command == "reject-result":
            _reject_result(args)
            return 0
        if args.command == "abandon-attempt":
            _abandon_attempt(args)
            return 0
        _link_result(args)
        return 0
    except (
        AgentPromptCliError,
        AgentInvocationError,
        DispatchError,
        WorkerPromptHeaderError,
        OSError,
        ValueError,
    ) as exc:
        print(f"error: {exc}", file=sys.stderr)
        manifest = getattr(args, "run_manifest", None)
        if manifest:
            root = Path(args.project_root).resolve()
            logged = record_runtime_failure(
                root / manifest, project_root=root,
                command=f"agent-prompt {args.command}", exit_code=2, detail=str(exc),
            )
            if not logged["ok"]:
                print(f"error-log: {logged['reason']}", file=sys.stderr)
        return 2
