#!/usr/bin/env python3
"""Platform-neutral Deep Research CLI for Codex and other adapters.

This CLI intentionally keeps deterministic orchestration in Python while
allowing Codex/OpenCode/Gemini/Claude Code to provide the agentic layer.
"""

from __future__ import annotations

import argparse
import json
import re
import subprocess
import sys
from pathlib import Path


REPO_ROOT = Path(__file__).resolve().parent.parent
if str(REPO_ROOT) not in sys.path:
    sys.path.insert(0, str(REPO_ROOT))

from scripts.lib.model_config import (
    ModelConfigError,
    list_model_profiles,
    parse_model_overrides,
    resolve_model_profile,
)


PROJECTS_DIR = REPO_ROOT / "projects"
CODEX_COMMANDS_DIR = REPO_ROOT / ".codex" / "commands"
CODEX_COMMAND_TEMPLATES_DIR = REPO_ROOT / "codex_adapter_templates" / "codex" / "commands"


def resolve_project(project: str | None) -> Path:
    if project:
        p = Path(project)
        if p.is_dir():
            return p.resolve()
        cand = PROJECTS_DIR / project
        if cand.is_dir():
            return cand.resolve()
        raise SystemExit(f"project not found: {project}")

    manifests = sorted(
        PROJECTS_DIR.glob("*/manifest.json"),
        key=lambda p: p.stat().st_mtime,
        reverse=True,
    )
    if not manifests:
        raise SystemExit("no projects found")
    return manifests[0].parent.resolve()


def load_manifest(project_root: Path) -> dict:
    path = project_root / "manifest.json"
    if not path.exists():
        raise SystemExit(f"manifest not found: {path}")
    return json.loads(path.read_text(encoding="utf-8"))


def count_words(text: str) -> int:
    return len(re.findall(r"[A-Za-z]+(?:[-'][A-Za-z]+)*", text))


def count_chinese_chars(text: str) -> int:
    return sum(1 for c in text if "\u4e00" <= c <= "\u9fff")


def file_state(path: Path) -> str:
    return "yes" if path.exists() else "no"


def run_cmd(cmd: list[str], *, dry_run: bool) -> int:
    printable = " ".join(cmd)
    print(f"$ {printable}")
    if dry_run:
        return 0
    return subprocess.run(cmd, cwd=REPO_ROOT, check=False).returncode


def cmd_status(args: argparse.Namespace) -> int:
    project_root = resolve_project(args.project)
    manifest = load_manifest(project_root)
    slug = project_root.name

    drafts = sorted((project_root / "phase2" / "drafts").glob("ch*.md"))
    evidence = sorted((project_root / "phase2" / "evidence").glob("ch*-evidence.md"))
    sources = project_root / "phase2" / "sources.jsonl"
    final_en = project_root / "phase4" / "final_en.md"
    final_zh = project_root / "phase4" / "final_zh.md"
    final_zh_polished = project_root / "phase4" / "final_zh_polished.md"
    glossary = project_root / "phase4" / "glossary.json"

    en_words = count_words(final_en.read_text(encoding="utf-8")) if final_en.exists() else 0
    zh_chars = count_chinese_chars(final_zh_polished.read_text(encoding="utf-8")) if final_zh_polished.exists() else 0
    source_count = 0
    if sources.exists():
        source_count = sum(1 for line in sources.read_text(encoding="utf-8").splitlines() if line.strip())

    print(f"Project: {manifest.get('topic', slug)}")
    print(f"Slug: {slug}")
    print(f"Title: {manifest.get('report_title', '(unset)')}")
    print(f"Type: {manifest.get('type', '(unset)')}")
    print()
    print("Phases:")
    for phase in ("phase1", "phase2", "phase3", "phase4"):
        p = manifest.get(phase, {})
        print(f"  {phase}: {p.get('status', 'pending')}  approved={p.get('approved', False)}")
    print()
    print("Artifacts:")
    print(f"  framework: {file_state(project_root / 'phase1' / 'framework.md')}")
    print(f"  drafts: {len(drafts)}")
    print(f"  evidence files: {len(evidence)}")
    print(f"  sources: {source_count}")
    print(f"  final_en.md: {file_state(final_en)} ({en_words:,} words)")
    print(f"  final_zh.md: {file_state(final_zh)}")
    print(f"  final_zh_polished.md: {file_state(final_zh_polished)} ({zh_chars:,} Chinese chars)")
    print(f"  glossary.json: {file_state(glossary)}")
    return 0


def cmd_prompt(args: argparse.Namespace) -> int:
    name = args.command
    if not name.startswith("dr-"):
        name = f"dr-{name}"
    path = CODEX_COMMANDS_DIR / f"{name}.md"
    if not path.exists():
        fallback = CODEX_COMMAND_TEMPLATES_DIR / f"{name}.md"
        if fallback.exists():
            path = fallback
        else:
            raise SystemExit(f"Codex command template not found: {path}")

    text = path.read_text(encoding="utf-8")
    if args.argument:
        text = text.replace("$ARGUMENTS", args.argument)
    else:
        text = text.replace("$ARGUMENTS", "")
    print(text)
    return 0


def cmd_glossary(args: argparse.Namespace) -> int:
    project_root = resolve_project(args.project)
    cmd = [
        sys.executable,
        str(REPO_ROOT / "scripts" / "build_glossary.py"),
        str(project_root),
        "--workers",
        str(args.workers),
    ]
    if args.force:
        cmd.append("--force")
    if args.only:
        cmd += ["--only", args.only]
    if args.input:
        cmd += ["--input", args.input]
    if args.output:
        cmd += ["--output", args.output]
    return run_cmd(cmd, dry_run=args.dry_run)


def cmd_finalize(args: argparse.Namespace) -> int:
    project_root = resolve_project(args.project)
    manifest = load_manifest(project_root)
    effective_profile = args.model_profile or manifest.get("model_profile")
    try:
        resolved = resolve_model_profile(
            profile=effective_profile,
            overrides=parse_model_overrides(args.model_override),
        )
    except ModelConfigError as exc:
        raise SystemExit(f"model profile resolution failed: {exc}") from exc
    roles = resolved["roles"]

    cmd = [
        sys.executable,
        str(REPO_ROOT / "scripts" / "phase4_pipeline.py"),
        str(project_root),
        "--translate-workers",
        str(args.translate_workers),
        "--glossary-workers",
        str(args.glossary_workers),
        "--polish-workers",
        str(args.polish_workers),
        "--translate-model",
        roles.get("translate", "anthropic/claude-sonnet-4.6"),
        "--glossary-model",
        roles.get("glossary", "anthropic/claude-haiku-4.5"),
        "--polish-model",
        roles.get("polish", "anthropic/claude-sonnet-4.6"),
        "--glossary-mode",
        args.glossary_mode,
    ]
    if args.dry_run:
        cmd.append("--dry-run")
    return run_cmd(cmd, dry_run=False)


def cmd_models(args: argparse.Namespace) -> int:
    if args.list:
        for name in list_model_profiles():
            print(name)
        return 0

    try:
        resolved = resolve_model_profile(
            profile=args.profile,
            overrides=parse_model_overrides(args.model_override),
        )
    except ModelConfigError as exc:
        raise SystemExit(f"model profile resolution failed: {exc}") from exc

    if args.json:
        print(json.dumps(resolved, ensure_ascii=False, indent=2))
        return 0

    print(f"Profile: {resolved['profile']}")
    if resolved["description"]:
        print(f"Description: {resolved['description']}")
    print("Roles:")
    for role in sorted(resolved["roles"]):
        print(f"  {role}: {resolved['roles'][role]}")
    return 0


def cmd_apply_models(args: argparse.Namespace) -> int:
    cmd = [
        sys.executable,
        str(REPO_ROOT / "scripts" / "apply_model_profile.py"),
        "--profile",
        args.profile,
        "--target",
        args.target,
    ]
    for item in args.model_override:
        cmd += ["--model-override", item]
    if args.dry_run:
        cmd.append("--dry-run")
    return run_cmd(cmd, dry_run=False)


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(description="Deep Research platform-neutral CLI")
    sub = parser.add_subparsers(dest="cmd", required=True)

    status = sub.add_parser("status", help="Show project status")
    status.add_argument("project", nargs="?", help="Project slug or path")
    status.set_defaults(func=cmd_status)

    prompt = sub.add_parser("prompt", help="Print a Codex command prompt template")
    prompt.add_argument("command", help="Command name, e.g. dr-frame or frame")
    prompt.add_argument("argument", nargs="?", help="Replacement for $ARGUMENTS")
    prompt.set_defaults(func=cmd_prompt)

    glossary = sub.add_parser("glossary", help="Run glossary verification")
    glossary.add_argument("project", help="Project slug or path")
    glossary.add_argument("--workers", type=int, default=4)
    glossary.add_argument("--force", action="store_true")
    glossary.add_argument("--only")
    glossary.add_argument("--input")
    glossary.add_argument("--output")
    glossary.add_argument("--dry-run", action="store_true")
    glossary.set_defaults(func=cmd_glossary)

    finalize = sub.add_parser("finalize", help="Run Phase 4 deterministic pipeline")
    finalize.add_argument("project", help="Project slug or path")
    finalize.add_argument("--translate-workers", type=int, default=0)
    finalize.add_argument("--glossary-workers", type=int, default=4)
    finalize.add_argument("--polish-workers", type=int, default=0)
    finalize.add_argument(
        "--glossary-mode",
        choices=["off", "low-confidence", "full"],
        default="low-confidence",
    )
    finalize.add_argument("--model-profile", help="Model profile name from configs/models.yaml")
    finalize.add_argument(
        "--model-override",
        action="append",
        default=[],
        metavar="ROLE=MODEL",
        help="Override one role model, repeatable",
    )
    finalize.add_argument("--dry-run", action="store_true")
    finalize.set_defaults(func=cmd_finalize)

    models = sub.add_parser("models", help="Resolve and print model profile")
    models.add_argument("--profile", help="Profile name from configs/models.yaml")
    models.add_argument("--list", action="store_true", help="List available profiles")
    models.add_argument(
        "--model-override",
        action="append",
        default=[],
        metavar="ROLE=MODEL",
        help="Override one role model, repeatable",
    )
    models.add_argument("--json", action="store_true", help="Emit JSON")
    models.set_defaults(func=cmd_models)

    apply_models = sub.add_parser("apply-models", help="Apply profile to agent files")
    apply_models.add_argument("--profile", required=True, help="Profile name from configs/models.yaml")
    apply_models.add_argument("--target", choices=["opencode", "codex", "both"], default="both")
    apply_models.add_argument(
        "--model-override",
        action="append",
        default=[],
        metavar="ROLE=MODEL",
        help="Override one role model, repeatable",
    )
    apply_models.add_argument("--dry-run", action="store_true")
    apply_models.set_defaults(func=cmd_apply_models)

    return parser


def main() -> int:
    parser = build_parser()
    args = parser.parse_args()
    return args.func(args)


if __name__ == "__main__":
    raise SystemExit(main())
