"""Load wafer sensor layouts from bundled YAML files.

YAML schema mirrors the replay app's wafer_desc.py format:
  X/Y      — sensor positions in mm relative to x_origin/y_origin
  size     — wafer diameter/edge in mm
  shape    — "round" or "square"
  start_sn — first sensor number (usually 1)
  x_origin — "left" | "right" | "center"
  y_origin — "bottom" | "top" | "center"

Returned Sensor coords are center-origin mm (negative values toward the edge).
"""
from __future__ import annotations

from pathlib import Path

import yaml

from pygui.backend.wafer.zwafer_models import Sensor

_LAYOUTS_DIR = Path(__file__).parent.parent.parent / "assets" / "layouts"


class WaferLayout(list):
    """Subclass of list that carries wafer shape and size metadata."""
    def __init__(self, sensors: list[Sensor], shape: str = "round", size: float = 300.0):
        super().__init__(sensors)
        self.shape = shape
        self.size = size


def resolve_shape_and_size(sensors: list[Sensor], wafer_id: str = "") -> tuple[str, float]:
    """Helper to determine the shape and size of a sensor layout.

    Tries matching by wafer_id prefix, falling back to the number of sensors.
    """
    if wafer_id:
        prefix = wafer_id[0].upper()
        for path in _LAYOUTS_DIR.glob("*.yaml"):
            try:
                data = _load_yaml(path)
                if prefix in data.get("wafers", []):
                    return data.get("shape", "round"), float(data.get("size", 300.0))
            except Exception:
                continue

    # Fallback to sensor count mapping
    n = len(sensors)
    if n == 80:
        return "square", 310.0
    elif n == 65:
        return "round", 300.0
    elif n == 48:
        return "round", 300.0
    elif n == 29:
        return "round", 200.0
    elif n == 22:
        return "round", 200.0

    return "round", 300.0


def _family_name(raw_name: str) -> str:
    return raw_name.replace("wafer", "").strip("_")


def _load_yaml(path: Path) -> dict:
    with path.open(encoding="utf-8") as f:
        loaded: dict = yaml.safe_load(f)
        return loaded


# Edge→center sensor pair tables, ported verbatim from the C# original
# (Form1.cs ec_map_*). Indices are 0-origin. Domain data, not derivable from
# ring geometry — see docs/adr/0002-edge-center-pair-tables.md.
_EC_MAPS: dict[str, tuple[tuple[int, int], ...]] = {
    "AEP": tuple((e, 40 + e // 2) for e in range(16)),
    "BCD": tuple((e, 28) for e in range(12)),
    "F": ((0, 18), (1, 19), (2, 19), (3, 19), (4, 20), (5, 20),
          (6, 20), (7, 21), (8, 21), (9, 21), (10, 18), (11, 18)),
    "X": ((5, 76), (0, 76), (39, 76), (6, 77), (11, 77), (16, 77),
          (17, 78), (22, 78), (27, 78), (28, 79), (33, 79), (38, 79)),
    "Z": tuple((e, 0) for e in range(49, 65)),
}


def ec_pairs_for_wafer_id(wafer_id: str) -> tuple[tuple[int, int], ...]:
    """Edge-center pairs for a wafer id's family letter; empty if unknown.

    Unknown families deliberately get no pairs (C# fell through to Z).
    """
    prefix = wafer_id[0].upper() if wafer_id else ""
    for families, key in (("AEP", "AEP"), ("BCD", "BCD"), ("F", "F"),
                          ("X", "X"), ("Z", "Z")):
        if prefix and prefix in families:
            return _EC_MAPS[key]
    return ()


# P6.3 (LayoutSelector) dropped 2026-07-10 — the C# "layout" buttons only
# exported coordinate spreadsheets, decided obsolete. See MIGRATION.md.
def available_families() -> list[str]:
    return [_family_name(_load_yaml(p)["name"]) for p in _LAYOUTS_DIR.glob("*.yaml")]


def load_layout(family: str) -> WaferLayout:
    for path in _LAYOUTS_DIR.glob("*.yaml"):
        data = _load_yaml(path)
        if _family_name(data["name"]) == family:
            return _to_sensors(data)
    raise KeyError(f"Unknown wafer family: {family!r}")


def load_layout_for_wafer_id(wafer_id: str) -> WaferLayout:
    """Match 'B00108' → bcdwafer by looking up the first char in each YAML's 'wafers' list."""
    prefix = wafer_id[0].upper() if wafer_id else ""
    for path in _LAYOUTS_DIR.glob("*.yaml"):
        data = _load_yaml(path)
        if prefix in data.get("wafers", []):
            return _to_sensors(data)
    raise KeyError(f"No layout found for wafer ID prefix {prefix!r}")


def _to_sensors(data: dict) -> WaferLayout:
    xs: list[float] = data["X"]
    ys: list[float] = data["Y"]
    size: float = float(data["size"])
    shape: str = data.get("shape", "round")
    start_sn: int = data.get("start_sn", 1)
    reverse_x: bool = data.get("reverse_x", False)
    reverse_y: bool = data.get("reverse_y", False)
    x_orig: str = data.get("x_origin", "left")
    y_orig: str = data.get("y_origin", "bottom")
    label_exceptions = data.get("label_exceptions", {}) or {}

    x_shift = {"left": size / 2, "right": -(size / 2), "center": 0.0}.get(x_orig, 0.0)
    y_shift = {"bottom": size / 2, "top": -(size / 2), "center": 0.0}.get(y_orig, 0.0)

    sensors: list[Sensor] = []
    for i, (x_mm, y_mm) in enumerate(zip(xs, ys)):
        if reverse_x:
            x_mm = -x_mm
        if reverse_y:
            y_mm = -y_mm

        x_coord = x_mm - x_shift
        y_coord = y_mm - y_shift

        # Determine label position defaults based on quadrants
        sn = start_sn + i
        side = "right"
        offset_x = 0.0
        offset_y = 0.0

        # Check explicit label exception from layout config
        if sn in label_exceptions:
            exc = label_exceptions[sn]
            if exc:
                side_key = list(exc.keys())[0]
                val = exc[side_key]
                side = side_key
                if isinstance(val, list) and len(val) >= 2:
                    offset_x = float(val[0])
                    offset_y = float(val[1])
        else:
            # Smart default quadrant positioning to keep labels pointing inward
            if x_coord > 30.0 and y_coord > 30.0:
                side = "left"
                offset_y = -0.5
            elif x_coord < -30.0 and y_coord > 30.0:
                side = "right"
                offset_y = -0.5
            elif x_coord < -30.0 and y_coord < -30.0:
                side = "right"
                offset_y = 0.5
            elif x_coord > 30.0 and y_coord < -30.0:
                side = "left"
                offset_y = 0.5
            elif x_coord > 15.0:
                side = "left"
            elif x_coord < -15.0:
                side = "right"

        sensors.append(Sensor(
            label=str(sn),
            x=x_coord,
            y=y_coord,
            side=side,
            offset_x=offset_x,
            offset_y=offset_y,
        ))
    return WaferLayout(sensors, shape=shape, size=size)

