#!/usr/bin/env python3
"""Isolated measurement harness: places global_labels of varying
net-name lengths in each direction, exports SVG via kicad-cli, and
computes the REAL rendered reach (distance from the pin to the far
edge of the rendered label text) for each combination -- by direct
SVG measurement, not an assumed per-character constant.

Not part of the delivered tool. Its OUTPUT (printed reach values,
one row per net-name-length/direction) is what gets fitted into a
label_reach() formula integrated into build_pixel3a_power_autolayout.py.
"""
import sys
import os
import re
import json
import math
import subprocess

sys.path.insert(0, os.path.dirname(__file__))
from kicad_gen import Schematic  # noqa: E402

KICAD_CLI = os.environ.get("KICAD_CLI", "kicad-cli")

NET_NAMES = [
    "A", "GND", "VBUS_9V", "Net-(D1-K)", "VOUT_4V2",
    "Net-(U1-VREG_1V2)", "VOUT_TO_PHONE_LONG_TEST",
]
DIRECTIONS = ["right", "left", "up", "down"]

ROW_H = 60
COL_W = 120


def build_schematic():
    sch = Schematic(title="label-measure", date="2026-08-12", rev="1")
    expected = {}  # (net, direction) -> pin_xy
    for i, net in enumerate(NET_NAMES):
        for j, direction in enumerate(DIRECTIONS):
            x = 20 + j * COL_W
            y = 20 + i * ROW_H
            pin_xy = (sch._snap(x), sch._snap(y))
            sch.label(pin_xy, net, direction)
            expected[(net, direction)] = pin_xy
    sch.paper = "A3"
    sch.write("label_measure.kicad_sch")
    return expected


def export_svg():
    cmd = KICAD_CLI.split() + ["sch", "export", "svg", "-o", "label_measure.svg", "label_measure.kicad_sch"]
    subprocess.run(cmd, check=True, capture_output=True, text=True)
    return "label_measure.svg/label_measure.svg"


TEXT_RE = re.compile(
    r'(?:<g transform="rotate\(([-0-9.]+) ([-0-9.]+) ([-0-9.]+)\)">\s*)?'
    r'<text x="([-0-9.]+)" y="([-0-9.]+)"\s*\n?'
    r'textLength="([-0-9.]+)" font-size="([-0-9.]+)"[^>]*'
    r'text-anchor="(start|end)"[^>]*>([^<]*)</text>'
)


def rotate_point(x, y, cx, cy, angle_deg):
    a = math.radians(angle_deg)
    dx, dy = x - cx, y - cy
    return (cx + dx * math.cos(a) - dy * math.sin(a),
            cy + dx * math.sin(a) + dy * math.cos(a))


def parse_text_entries(svg_path):
    with open(svg_path) as f:
        content = f.read()
    entries = []
    for m in TEXT_RE.finditer(content):
        angle, cx, cy, x, y, text_length, font_size, anchor, text = m.groups()
        x, y, text_length, font_size = float(x), float(y), float(text_length), float(font_size)
        if anchor == "start":
            local_far = (x + text_length, y)
        else:
            local_far = (x - text_length, y)
        local_near = (x, y)
        if angle is not None:
            cx, cy, angle = float(cx), float(cy), float(angle)
            far = rotate_point(local_far[0], local_far[1], cx, cy, angle)
            near = rotate_point(local_near[0], local_near[1], cx, cy, angle)
        else:
            far, near = local_far, local_near
        entries.append({"text": text, "near": near, "far": far})
    return entries


def main():
    expected = build_schematic()
    svg_path = export_svg()
    entries = parse_text_entries(svg_path)

    print(f"{'net':30s} {'dir':6s} {'reach_mm':>10s}")
    results = {}
    for (net, direction), pin_xy in expected.items():
        # match by content and proximity of the near/far span to the
        # expected pin position (within a generous tolerance -- the
        # spacing between test points, 60/120mm, is far larger than
        # any label's own real extent, so this is unambiguous).
        candidates = [e for e in entries if e["text"] == net]
        best = None
        best_d = None
        for e in candidates:
            d = math.hypot(e["near"][0] - pin_xy[0], e["near"][1] - pin_xy[1])
            if best_d is None or d < best_d:
                best_d, best = d, e
        if best is None or best_d > 60:
            print(f"{net:30s} {direction:6s} NO MATCH (best_d={best_d})")
            continue
        reach = math.hypot(best["far"][0] - pin_xy[0], best["far"][1] - pin_xy[1])
        results[(net, direction)] = reach
        print(f"{net:30s} {direction:6s} {reach:10.3f}")

    with open("label_reach_measured.json", "w") as f:
        json.dump({f"{n}|{d}": r for (n, d), r in results.items()}, f, indent=2)


if __name__ == "__main__":
    main()
