from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any


def read_json(path: Path) -> dict[str, Any]:
    return json.loads(path.read_text(encoding="utf-8"))


def dig(mapping: dict[str, Any] | None, *keys: str) -> Any:
    current: Any = mapping or {}
    for key in keys:
        if not isinstance(current, dict):
            return None
        current = current.get(key)
    return current


def short_next_record(match: dict[str, Any] | None) -> dict[str, Any] | None:
    node = dig(match, "next_moxel_record")
    if not isinstance(node, dict):
        return None
    result: dict[str, Any] = {"type": node.get("type")}
    for key in ("head", "value", "scalar_prefix", "tree_position"):
        if key in node:
            result[key] = node.get(key)
    return result


def compact_item(item: dict[str, Any]) -> dict[str, Any]:
    named = ((item.get("named_range_matches") or [{}])[0]) if item.get("named_range_matches") else {}
    text = ((item.get("text_matches") or [{}])[0]) if item.get("text_matches") else {}
    return {
        "file_name": item.get("file_name"),
        "bytes": item.get("bytes"),
        "modified": item.get("modified"),
        "counts": item.get("counts") or {},
        "named_range": {
            "name": named.get("name"),
            "tree_position": named.get("tree_position"),
            "one_based": dig(named, "range", "one_based"),
            "raw_scalars": dig(named, "range_candidate", "raw_scalars"),
        },
        "text_match": {
            "text": text.get("text"),
            "cell_id": text.get("cell_id"),
            "tree_position": text.get("tree_position"),
            "next_moxel_record": short_next_record(text),
            "immediate_preceding_values": dig(text, "style_evidence", "immediate_preceding_values"),
            "last_7_preceding_values": dig(text, "style_evidence", "last_7_preceding_values"),
        },
    }


def diff_dict(before: dict[str, Any], after: dict[str, Any]) -> dict[str, dict[str, Any]]:
    changed: dict[str, dict[str, Any]] = {}
    for key in sorted(set(before) | set(after)):
        if before.get(key) != after.get(key):
            changed[key] = {"before": before.get(key), "after": after.get(key)}
    return changed


def build_transitions(items: list[dict[str, Any]]) -> list[dict[str, Any]]:
    compact = [compact_item(item) for item in reversed(items)]
    transitions: list[dict[str, Any]] = []
    for before, after in zip(compact, compact[1:]):
        named_before = before.get("named_range") if isinstance(before.get("named_range"), dict) else {}
        named_after = after.get("named_range") if isinstance(after.get("named_range"), dict) else {}
        text_before = before.get("text_match") if isinstance(before.get("text_match"), dict) else {}
        text_after = after.get("text_match") if isinstance(after.get("text_match"), dict) else {}
        transition = {
            "from_file": before.get("file_name"),
            "to_file": after.get("file_name"),
            "from_modified": before.get("modified"),
            "to_modified": after.get("modified"),
            "from_bytes": before.get("bytes"),
            "to_bytes": after.get("bytes"),
            "count_changes": diff_dict(before.get("counts") or {}, after.get("counts") or {}),
            "named_range_changes": diff_dict(named_before, named_after),
            "text_match_changes": diff_dict(text_before, text_after),
        }
        transitions.append(transition)
    return transitions


def build_signature_groups(items: list[dict[str, Any]]) -> dict[str, Any]:
    signatures: dict[str, list[dict[str, Any]]] = {}
    for item in items:
        for match in item.get("text_matches") or []:
            if not isinstance(match, dict):
                continue
            signature = json.dumps(short_next_record(match), ensure_ascii=False, sort_keys=True)
            signatures.setdefault(signature, []).append(
                {
                    "file_name": item.get("file_name"),
                    "modified": item.get("modified"),
                    "text": match.get("text"),
                    "cell_id": match.get("cell_id"),
                    "tree_position": match.get("tree_position"),
                    "immediate_preceding_values": dig(match, "style_evidence", "immediate_preceding_values"),
                    "last_7_preceding_values": dig(match, "style_evidence", "last_7_preceding_values"),
                }
            )
    result: list[dict[str, Any]] = []
    for signature, occurrences in signatures.items():
        result.append(
            {
                "signature": json.loads(signature),
                "occurrences": occurrences,
                "count": len(occurrences),
            }
        )
    result.sort(key=lambda item: (-int(item.get("count") or 0), json.dumps(item.get("signature"), ensure_ascii=False)))
    return {"next_moxel_record_signatures": result}


def build_markdown(history: dict[str, Any], transitions: list[dict[str, Any]], signature_groups: dict[str, Any]) -> str:
    lines: list[str] = []
    lines.append("# 1C template history matrix")
    lines.append("")
    lines.append(f"- Base: `{history.get('base_id')}`")
    lines.append(f"- Track name: `{history.get('track_name')}`")
    lines.append(f"- Track text: `{history.get('track_text')}`")
    lines.append(f"- Snapshots: `{len(history.get('items') or [])}`")
    lines.append("")
    lines.append("## Current state")
    current = compact_item((history.get("items") or [{}])[0] if history.get("items") else {})
    lines.append("")
    lines.append("```json")
    lines.append(json.dumps(current, ensure_ascii=False, indent=2))
    lines.append("```")
    lines.append("")
    lines.append("## Transitions")
    for transition in transitions:
        lines.append("")
        lines.append(
            f"- `{transition['from_file']}` -> `{transition['to_file']}` "
            f"({transition['from_modified']} -> {transition['to_modified']})"
        )
        lines.append("")
        lines.append("```json")
        lines.append(json.dumps(transition, ensure_ascii=False, indent=2))
        lines.append("```")
    lines.append("")
    lines.append("## Signatures")
    lines.append("")
    lines.append("```json")
    lines.append(json.dumps(signature_groups, ensure_ascii=False, indent=2))
    lines.append("```")
    lines.append("")
    return "\n".join(lines)


def main() -> int:
    parser = argparse.ArgumentParser(description="Build a transition matrix from tracked 1C template history.")
    parser.add_argument("history_json", help="Path to JSON generated by track_1c_template_history.py")
    parser.add_argument("--json-output", help="Optional JSON output path.")
    parser.add_argument("--markdown-output", help="Optional Markdown output path.")
    args = parser.parse_args()

    history_path = Path(args.history_json)
    history = read_json(history_path)
    items = history.get("items") or []
    transitions = build_transitions(items)
    signature_groups = build_signature_groups(items)
    payload = {
        "schema": "codex_1c_template_history_matrix.v1",
        "source": str(history_path),
        "track_name": history.get("track_name"),
        "track_text": history.get("track_text"),
        "current": compact_item(items[0] if items else {}),
        "transitions": transitions,
        "signatures": signature_groups,
    }
    rendered = json.dumps(payload, ensure_ascii=False, indent=2)
    if args.json_output:
        Path(args.json_output).write_text(rendered, encoding="utf-8")
    markdown = build_markdown(history, transitions, signature_groups)
    if args.markdown_output:
        Path(args.markdown_output).write_text(markdown, encoding="utf-8")
    print(rendered)
    return 0


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