#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
from collections import Counter, defaultdict
from pathlib import Path
from typing import Any


def load_entries(report: dict[str, Any]) -> list[dict[str, Any]]:
    matrix = report.get("matrix") if isinstance(report.get("matrix"), dict) else {}
    entries = matrix.get("entries") if isinstance(matrix.get("entries"), list) else []
    return [entry for entry in entries if isinstance(entry, dict)]


def gap_row(entry: dict[str, Any]) -> dict[str, Any]:
    requested = entry.get("requested_target") if isinstance(entry.get("requested_target"), dict) else {}
    effective = entry.get("effective_target") if isinstance(entry.get("effective_target"), dict) else {}
    source = entry.get("effective_source") if isinstance(entry.get("effective_source"), dict) else {}
    prop = entry.get("property") if isinstance(entry.get("property"), dict) else {}
    probe = entry.get("codec_probe") if isinstance(entry.get("codec_probe"), dict) else {}
    probe_node = probe.get("node") if isinstance(probe.get("node"), dict) else {}
    return {
        "reason": entry.get("reason") or "unknown",
        "requested_section": requested.get("section"),
        "requested_name": requested.get("name"),
        "requested_path": requested.get("path"),
        "effective_section": effective.get("section"),
        "effective_name": effective.get("name"),
        "effective_path": effective.get("path"),
        "source_kind": source.get("kind") or "local",
        "property": prop.get("semantic_name") or prop.get("canonical_property") or prop.get("property"),
        "canonical_property": prop.get("canonical_property") or prop.get("property"),
        "presentation": prop.get("presentation"),
        "semantic_name": prop.get("semantic_name"),
        "semantic_group": prop.get("semantic_group"),
        "semantic_source": prop.get("semantic_source"),
        "parameter_index": prop.get("parameter_index"),
        "value_type": prop.get("value_type"),
        "old": prop.get("old"),
        "read_path": prop.get("read_path"),
        "write_path": prop.get("write_path"),
        "verification": prop.get("verification"),
        "codec_probe": probe or None,
        "codec_probe_node_type": probe_node.get("type") or probe.get("error") if probe else None,
    }


def classify_action(reason: str, prop: str | None, value_type: str | None) -> str:
    if reason == "identity_or_binding_property":
        return "manual_only_identity_or_binding"
    if reason == "empty_local_string_requires_codec_probe":
        return "add_empty_composite_string_codec_probe"
    if reason == "composite_node_requires_semantic_rule":
        return "learn_composite_node_semantics"
    if reason == "value_type_not_smoke_safe":
        if value_type in {"enum_atom", "bool_or_enum_atom", "color_or_enum_atom"}:
            return "learn_allowed_enum_values"
        if prop in {"group"}:
            return "learn_reference_or_container_write_rule"
        return "classify_scalar_semantics"
    return "inspect"


def main() -> int:
    parser = argparse.ArgumentParser(description="Analyze not-smoked entries from a 1C saved-state write matrix report.")
    parser.add_argument("--matrix-report", type=Path, required=True, help="Report produced by scripts/smoke_1c_write_matrix.py.")
    parser.add_argument("--output", type=Path, required=True, help="Output JSON gap report.")
    parser.add_argument("--sample-limit", type=int, default=12, help="Samples per reason/action.")
    args = parser.parse_args()

    report = json.loads(args.matrix_report.read_text(encoding="utf-8"))
    gaps = []
    for entry in load_entries(report):
        if entry.get("can_smoke"):
            continue
        row = gap_row(entry)
        row["next_action"] = classify_action(str(row.get("reason") or ""), row.get("property"), row.get("value_type"))
        gaps.append(row)

    by_reason = Counter(row["reason"] for row in gaps)
    by_action = Counter(row["next_action"] for row in gaps)
    by_section = Counter(row["effective_section"] for row in gaps)
    by_property = Counter(row["property"] for row in gaps)
    by_probe_node_type = Counter(row.get("codec_probe_node_type") for row in gaps if row.get("codec_probe_node_type"))
    samples_by_reason: dict[str, list[dict[str, Any]]] = defaultdict(list)
    samples_by_action: dict[str, list[dict[str, Any]]] = defaultdict(list)
    shape_summary: dict[str, dict[str, Any]] = defaultdict(lambda: {"count": 0, "properties": Counter(), "sections": Counter(), "samples": []})
    for row in gaps:
        reason = str(row["reason"])
        action = str(row["next_action"])
        probe = row.get("codec_probe") if isinstance(row.get("codec_probe"), dict) else {}
        node = probe.get("node") if isinstance(probe.get("node"), dict) else {}
        children = node.get("children") if isinstance(node.get("children"), list) else []
        if node:
            shape = str(node.get("type") or "unknown") + "|" + ",".join(str((child or {}).get("type")) for child in children[:12])
            shape_row = shape_summary[shape]
            shape_row["count"] = int(shape_row["count"]) + 1
            shape_row["properties"][row.get("property")] += 1
            shape_row["sections"][row.get("effective_section")] += 1
            if len(shape_row["samples"]) < args.sample_limit:
                shape_row["samples"].append(
                    {
                        "target": row.get("requested_name") or row.get("requested_path"),
                        "section": row.get("effective_section"),
                        "property": row.get("presentation") or row.get("property"),
                        "semantic_name": row.get("semantic_name"),
                        "semantic_group": row.get("semantic_group"),
                        "old": row.get("old"),
                        "write_path": row.get("write_path"),
                    }
                )
        sample = {
            "target": row.get("requested_name") or row.get("requested_path"),
            "section": row.get("effective_section"),
            "property": row.get("presentation") or row.get("property"),
            "semantic_name": row.get("semantic_name"),
            "semantic_group": row.get("semantic_group"),
            "value_type": row.get("value_type"),
            "old": row.get("old"),
            "write_path": row.get("write_path"),
        }
        if len(samples_by_reason[reason]) < args.sample_limit:
            samples_by_reason[reason].append(sample)
        if len(samples_by_action[action]) < args.sample_limit:
            samples_by_action[action].append(sample)

    result = {
        "schema": "onec_form_write_matrix_gap_analysis.v1",
        "status": "ok",
        "source_report": str(args.matrix_report),
        "counts": {
            "gaps": len(gaps),
            "by_reason": dict(sorted(by_reason.items())),
            "by_next_action": dict(sorted(by_action.items())),
            "by_effective_section": dict(sorted(by_section.items())),
            "by_codec_probe_node_type": dict(sorted(by_probe_node_type.items())),
            "top_properties": by_property.most_common(40),
        },
        "samples_by_reason": dict(samples_by_reason),
        "samples_by_next_action": dict(samples_by_action),
        "codec_probe_shapes": [
            {
                "shape": shape,
                "count": row["count"],
                "properties": row["properties"].most_common(20),
                "sections": dict(row["sections"]),
                "samples": row["samples"],
            }
            for shape, row in sorted(shape_summary.items(), key=lambda item: int(item[1]["count"]), reverse=True)
        ],
        "gaps": gaps,
    }
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps({"schema": result["schema"], "status": "ok", "counts": result["counts"], "path": str(args.output)}, ensure_ascii=False, indent=2))
    return 0


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