#!/usr/bin/env python3
"""Summarize GPS-relevant structure in all.traffic_persistent JSON."""

from __future__ import annotations

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


FLAGS = {
    "FromRoadSpline": 1,
    "Bidirectional": 2,
    "PatrolRoute": 4,
    "Pavement": 8,
    "Road": 16,
    "Intersection": 32,
    "NeverDeadEnd": 64,
    "TrafficDisabled": 128,
    "CrossWalk": 256,
    "GPSOnly": 512,
    "ShowDebug": 1024,
    "Blockade": 2048,
    "Yield": 4096,
    "NoAIDriving": 8192,
    "Highway": 16384,
    "NoAutodrive": 32768,
}


def flag_value(flags: Any) -> int:
    if isinstance(flags, int):
        return flags
    if isinstance(flags, str):
        return int(flags)
    if isinstance(flags, dict):
        if "Value" in flags:
            return int(flags["Value"])
        if "$value" in flags:
            return int(flags["$value"])
    return int(flags or 0)


def has(flags: int, name: str) -> bool:
    return bool(flags & FLAGS[name])


def category(flags: int) -> str:
    if has(flags, "Highway"):
        return "highway"
    if has(flags, "GPSOnly"):
        return "gpsonly"
    if has(flags, "Road"):
        return "road"
    if has(flags, "Pavement"):
        return "pavement"
    return "other"


def ref_index(ref: Any) -> int | None:
    if isinstance(ref, int):
        return ref
    if isinstance(ref, str):
        try:
            return int(ref)
        except ValueError:
            return None
    if isinstance(ref, dict):
        for key in ("Index", "index", "laneIndex", "LaneIndex", "value", "$value"):
            if key in ref:
                return ref_index(ref[key])
    return None


def lane_refs(value: Any) -> list[int]:
    if value is None:
        return []
    if isinstance(value, list):
        out: list[int] = []
        for item in value:
            idx = ref_index(item)
            if idx is not None:
                out.append(idx)
        return out
    if isinstance(value, dict) and "Elements" in value:
        return lane_refs(value["Elements"])
    return []


def gps_info(lane: dict[str, Any]) -> tuple[int | None, int | None]:
    info = lane.get("playerGPSInfo") or {}
    return info.get("subGraphId"), info.get("stronglyConnectedComponentId")


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("json_file", type=Path)
    parser.add_argument("--lane-connections", type=Path)
    parser.add_argument("--samples", type=int, default=12)
    args = parser.parse_args()

    data = json.loads(args.json_file.read_text(encoding="utf-8"))
    root = data["Data"]["RootChunk"]["data"]
    lanes = root["lanes"]
    neighbor_groups = root.get("neighborGroups", [])

    categories = []
    flags_by_lane = []
    for lane in lanes:
        flags = flag_value(lane.get("flags", 0))
        flags_by_lane.append(flags)
        categories.append(category(flags))

    print(f"lanes: {len(lanes)}")
    print(f"neighborGroups: {len(neighbor_groups)}")
    print("categories:", dict(collections.Counter(categories).most_common()))

    gps_counter = collections.Counter(gps_info(lane) for lane in lanes)
    print(f"gps components: {len(gps_counter)}")
    print("top gps components:")
    for (subgraph, scc), count in gps_counter.most_common(20):
        cats = collections.Counter(
            categories[i] for i, lane in enumerate(lanes) if gps_info(lane) == (subgraph, scc)
        )
        print(f"  subGraph={subgraph} scc={scc}: {count} {dict(cats.most_common())}")

    connection_rows: list[dict[str, Any]] | None = None
    if args.lane_connections:
        connection_data = json.loads(args.lane_connections.read_text(encoding="utf-8"))
        connection_rows = connection_data["Data"]["RootChunk"]["data"]

    transitions = collections.Counter()
    probabilities: dict[tuple[str, str], collections.Counter[int]] = collections.defaultdict(
        collections.Counter
    )
    sharp_angles = collections.Counter()
    out_degree = collections.Counter()
    missing_refs = 0
    rows = enumerate(lanes) if connection_rows is None else (
        (row["index"], row["value"]) for row in connection_rows
    )
    for i, lane_or_connections in rows:
        refs = (
            lane_refs(lane_or_connections.get("outLanes"))
            if connection_rows is None
            else lane_refs(lane_or_connections.get("outlanes"))
        )
        out_degree[len(refs)] += 1
        raw_outs = (
            lane_or_connections.get("outLanes", [])
            if connection_rows is None
            else lane_or_connections.get("outlanes", [])
        )
        for out, ref in zip(raw_outs, refs):
            if ref < 0 or ref >= len(lanes):
                missing_refs += 1
                continue
            pair = (categories[i], categories[ref])
            transitions[pair] += 1
            if isinstance(out, dict) and "exitProbabilityCompressed" in out:
                probabilities[pair][out["exitProbabilityCompressed"]] += 1
            if isinstance(out, dict) and "isSharpAngle" in out:
                sharp_angles[(pair, out["isSharpAngle"])] += 1
    print("out degree:", dict(sorted(out_degree.items())))
    print(f"missing out refs: {missing_refs}")
    print("category transitions:")
    for (src, dst), count in transitions.most_common(30):
        extra = ""
        if probabilities[(src, dst)]:
            extra = f" prob={probabilities[(src, dst)].most_common(6)}"
        sharp = [(key[1], value) for key, value in sharp_angles.items() if key[0] == (src, dst)]
        if sharp:
            extra += f" sharp={sharp}"
        print(f"  {src:8s} -> {dst:8s}: {count}{extra}")

    print("highway connector samples:")
    shown = 0
    for i, lane in enumerate(lanes):
        if categories[i] != "highway":
            continue
        if connection_rows is None:
            refs = [ref for ref in lane_refs(lane.get("outLanes")) if 0 <= ref < len(lanes)]
        else:
            row = connection_rows[i]["value"]
            refs = [ref for ref in lane_refs(row.get("outlanes")) if 0 <= ref < len(lanes)]
        ref_cats = collections.Counter(categories[ref] for ref in refs)
        if ref_cats and any(cat != "highway" for cat in ref_cats):
            subgraph, scc = gps_info(lane)
            print(
                f"  lane={i} flags={flags_by_lane[i]} len={lane.get('length')} "
                f"speed={lane.get('maxSpeed')} gps=({subgraph},{scc}) out={dict(ref_cats)}"
            )
            shown += 1
            if shown >= args.samples:
                break

    return 0


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