"""版本化落盘：``<ai_output>/output/code_gen_tree/<topic_slug>/`` + manifest。"""

from __future__ import annotations

import hashlib
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


MANIFEST_SCHEMA = "aicd_code_gen_tree_manifest"
MANIFEST_VERSION = 1
REV_PREFIX = "rev_"


def conventional_slug_dir(ai_output: Path, topic_slug: str) -> Path:
    return (ai_output / "output" / "code_gen_tree" / topic_slug).resolve()


def manifest_path(ai_output: Path, topic_slug: str) -> Path:
    return conventional_slug_dir(ai_output, topic_slug) / "manifest.json"


def _fingerprint_payload(payload: dict[str, Any]) -> str:
    nodes = payload.get("nodes") or []
    parts: list[str] = []
    for n in nodes:
        parts.append(
            f"{n.get('file','')}|{n.get('qname','')}|{n.get('line_start')}|{n.get('kind','')}"
        )
    parts.sort()
    h = hashlib.sha256("\n".join(parts).encode("utf-8")).hexdigest()
    return h[:24]


def read_manifest(ai_output: Path, topic_slug: str) -> dict[str, Any] | None:
    mp = manifest_path(ai_output, topic_slug)
    if not mp.is_file():
        return None
    try:
        return json.loads(mp.read_text(encoding="utf-8"))
    except (OSError, json.JSONDecodeError):
        return None


def write_versioned_tree(
    ai_output: Path,
    topic_slug: str,
    payload: dict[str, Any],
    *,
    scan_root: str,
    max_history: int = 32,
) -> dict[str, Any]:
    """
    每次成功同步 **index_revision 自增**；写入 ``rev_XXXX/tree.json``；更新 manifest。

    约定：多任务接续时以 **manifest.latest_revision** 与 **latest_tree_path** 为准。
    """
    base = conventional_slug_dir(ai_output, topic_slug)
    base.mkdir(parents=True, exist_ok=True)

    prev = read_manifest(ai_output, topic_slug)
    prev_rev = 0
    history: list[dict[str, Any]] = []
    if prev and isinstance(prev.get("latest_revision"), int):
        prev_rev = int(prev["latest_revision"])
    if prev and isinstance(prev.get("revision_history"), list):
        history = list(prev["revision_history"])

    new_rev = prev_rev + 1
    rev_name = f"{REV_PREFIX}{new_rev:04d}"
    rev_dir = base / rev_name
    rev_dir.mkdir(parents=True, exist_ok=True)
    tree_path = rev_dir / "tree.json"

    fp = _fingerprint_payload(payload)
    nodes = payload.get("nodes") or []
    body = {
        **{k: v for k, v in payload.items() if k != "nodes"},
        "index_revision": new_rev,
        "topic_slug": topic_slug,
        "content_fingerprint": fp,
        "nodes": nodes,
        "generated_at": datetime.now(timezone.utc).isoformat(),
    }
    tree_path.write_text(
        json.dumps(body, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )

    entry = {
        "revision": new_rev,
        "generated_at": body["generated_at"],
        "content_fingerprint": fp,
        "files_scanned": payload.get("files_scanned", 0),
        "node_count": len(nodes),
        "rel_path": f"{rev_name}/tree.json",
    }
    history = [entry] + [h for h in history if h.get("revision") != new_rev]
    history = history[:max_history]

    man = {
        "schema": MANIFEST_SCHEMA,
        "version": MANIFEST_VERSION,
        "topic_slug": topic_slug,
        "scan_root": scan_root,
        "latest_revision": new_rev,
        "latest_tree_path": entry["rel_path"],
        "latest_content_fingerprint": fp,
        "updated_at": body["generated_at"],
        "revision_history": history,
        "agent_continuation_rule": (
            "Unless the user pins an older revision, always load "
            f"{topic_slug} via latest_revision / latest_tree_path for follow-up work."
        ),
    }
    manifest_path(ai_output, topic_slug).write_text(
        json.dumps(man, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )

    return {
        "ok": True,
        "topic_slug": topic_slug,
        "index_revision": new_rev,
        "tree_json_path": str(tree_path.resolve()),
        "manifest_path": str(manifest_path(ai_output, topic_slug)),
        "content_fingerprint": fp,
        "conventional_dir": str(base),
    }
