r"""
Declarative racecourse SHAPE templates (decision: specify the shape, fit only the buoy positions).

A template names the distinct physical buoys and the ordered sequence of marks the fleet rounds (plus
which buoys form the start / finish gate). The fitter (`fit_template.py`) then measures only the buoy
POSITIONS from the tracks — it never has to guess the topology. This removes the old fitter's fragile
"top-4 densest clusters + east/west split" guesswork (which drifted on rotated courses and picked up
spurious turn clusters as marks).

Figure-8 (the digital-8 team-racing course) — 4 buoys A,B,C,D, each multi-use:
    A: start-line STARBOARD end / Mark 3 / finish-line PORT end
    B: start-line PORT end      / Mark 4 / finish-line STARBOARD end
    C: Mark 1 / Mark 5
    D: Mark 2 / Mark 6
So the rounded sequence is C,D,A,B,C,D (M1..M6); the start & finish are the SAME physical line A-B,
crossed in opposite directions (hence the port/starboard ends swap between start and finish). The four
buoys sit at the corners of a rectangle in clockwise order A-C-D-B (A-B and C-D the short "gate" sides,
A-C and B-D the long beats) — a validity check the fitter enforces (opposite sides within 25%).

Pure data + geometry; no Firestore, no engine imports.
"""
from __future__ import annotations

import math
from dataclasses import dataclass


@dataclass(frozen=True)
class CourseTemplate:
    name: str
    buoys: tuple[str, ...]               # distinct physical buoy ids
    mark_seq: tuple[str, ...]            # buoy rounded at M1, M2, ... (repeats allowed)
    start_gate: tuple[str, str]          # (port_buoy, starboard_buoy) of the START line
    finish_gate: tuple[str, str]         # (port_buoy, starboard_buoy) of the FINISH line
    rounding_sides: tuple[str, ...]      # "port"/"starboard" side the mark is kept on, per M1..Mn
    # rectangle validity: pairs of opposite sides that must match within `rect_tol`
    rect_sides: tuple[tuple[str, str], tuple[str, str]]   # ((A,B),(C,D)) and ((A,C),(B,D)) checked as pairs
    rect_tol: float = 0.25

    @property
    def n_marks(self) -> int:
        return len(self.mark_seq)


# The figure-8 / digital-8. mark_seq C,D,A,B,C,D; start line B(port)-A(stbd); finish line A(port)-B(stbd).
# Rounding hand: M1,M2,M5,M6 (C,D) are kept to PORT; M3,M4 (A,B) are kept to STARBOARD.
FIGURE8 = CourseTemplate(
    name="digital 8",
    buoys=("A", "B", "C", "D"),
    mark_seq=("C", "D", "A", "B", "C", "D"),
    start_gate=("B", "A"),
    finish_gate=("A", "B"),
    rounding_sides=("port", "port", "starboard", "starboard", "port", "port"),
    rect_sides=(("A", "B"), ("C", "D")),   # short gate sides — the SECOND pair (A-C,B-D) is derived below
)

TEMPLATES: dict[str, CourseTemplate] = {"digital 8": FIGURE8}
# M-shape is added here later as one more CourseTemplate entry — pure data, no fitter change.


def _dist(p: tuple[float, float], q: tuple[float, float]) -> float:
    return math.hypot(p[0] - q[0], p[1] - q[1])


def validate_rectangle(buoy_xy: dict[str, tuple[float, float]], tol: float = 0.25) -> tuple[bool, dict]:
    """Check the four figure-8 buoys form a rectangle (clockwise A-C-D-B): the two opposite SHORT sides
    A-B / C-D within `tol`, and the two opposite LONG sides A-C / B-D within `tol`. Returns (ok, metrics)."""
    A, B, C, D = buoy_xy["A"], buoy_xy["B"], buoy_xy["C"], buoy_xy["D"]
    ab, cd = _dist(A, B), _dist(C, D)          # short gate sides (start/finish and windward)
    ac, bd = _dist(A, C), _dist(B, D)          # long beat sides
    def _rel(x, y):
        return abs(x - y) / max(x, y, 1e-6)
    r_short, r_long = _rel(ab, cd), _rel(ac, bd)
    ok = r_short <= tol and r_long <= tol
    return ok, {"AB": round(ab, 1), "CD": round(cd, 1), "AC": round(ac, 1), "BD": round(bd, 1),
                "short_diff": round(r_short, 3), "long_diff": round(r_long, 3), "ok": ok}


def expand_template(tpl: CourseTemplate, buoy_ll: dict[str, tuple[float, float]],
                    rounding_sides: list[str] | None = None, *, name: str | None = None) -> dict:
    """Template + fitted buoy lat/lons -> the engine `course` block
    ({name, center, startLine, finishLine, waypoints}). A re-rounded buoy repeats at its lat/lon (the
    renderer groups buoys within 8 m into one flag). Start & finish are the same physical line for the
    figure-8, with port/starboard ends per the gate definitions."""
    sides = rounding_sides or list(tpl.rounding_sides) or ["port"] * tpl.n_marks
    wps = []
    for i, b in enumerate(tpl.mark_seq):
        la, lo = buoy_ll[b]
        wps.append({"name": "", "type": "rounding", "rounding": sides[i],
                    "lat": round(la, 7), "lon": round(lo, 7)})

    def _line(gate: tuple[str, str]) -> dict:
        (pla, plo), (sla, slo) = buoy_ll[gate[0]], buoy_ll[gate[1]]
        return {"port": {"lat": round(pla, 7), "lon": round(plo, 7)},
                "starboard": {"lat": round(sla, 7), "lon": round(slo, 7)}}

    lls = [buoy_ll[b] for b in tpl.buoys]
    center = {"lat": sum(p[0] for p in lls) / len(lls), "lon": sum(p[1] for p in lls) / len(lls)}
    return {"name": name or tpl.name, "center": center,
            "startLine": _line(tpl.start_gate), "finishLine": _line(tpl.finish_gate), "waypoints": wps}
