#!/usr/bin/env python3
"""Isolated measurement harness: places components (Device:R, a real,
already-verified 2-pin symbol) at varying Value string lengths,
exports SVG via kicad-cli, and computes the REAL rendered width of
the Value property text -- by direct SVG measurement, the same
methodology label_reach() used, not an assumed per-character
constant.

Value text (not Reference) is what's measured: it's the one with
genuinely variable, unpredictable length in real use (a component's
own Reference is always short and structured, e.g. "R10"; its Value
can be anything, e.g. RV1's own real "2.2k trimmer (adjust for
4.0-4.2V out)", 39 characters).

Not part of the delivered tool. Its OUTPUT (printed width values, one
row per Value-string length) is what gets fitted into a
value_text_reach() formula for kicad_layout.py's own PWR_FLAG-vs-
component-text avoidance (see avoid_label_reach()'s own
component_clearance parameter, currently a flat, unmeasured 15mm
constant this measurement replaces).
"""
import sys
import os
import re
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")

# A spread of lengths, including RV1's own real value string (this
# project's own longest real Value seen so far) and a couple of
# synthetic ones past it, to check the fit doesn't just barely cover
# the one real case that motivated this.
VALUES = [
    "1k", "100uF", "SS34 (3A Schottky)",
    "2.2k trimmer (adjust for 4.0-4.2V out)",
    "A very long descriptive value string for stress testing purposes",
]

ROW_H = 40

PINS_R = {"1": (0, 3.81), "2": (0, -3.81)}


def build_schematic():
    sch = Schematic(title="value-text-measure", date="2026-08-13", rev="1")
    full_id = sch.use_symbol(os.environ.get("KICAD_SYM_DIR", "/home/claude/kicad-symbols"),
                              "Device", "R")
    expected = {}
    for i, value in enumerate(VALUES):
        x, y = 50, 20 + i * ROW_H
        sch.place(full_id, PINS_R, f"R{i}", value, x, y)
        expected[value] = (x, y)
    sch.paper = "A3"
    sch.write("value_text_measure.kicad_sch")
    return expected


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


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


def parse_text_entries(svg_path):
    with open(svg_path) as f:
        content = f.read()
    entries = []
    for m in TEXT_RE.finditer(content):
        x, y, text_length, font_size, anchor, text = m.groups()
        entries.append({"x": float(x), "y": float(y), "text_length": float(text_length),
                         "anchor": anchor, "text": text})
    return entries


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

    print(f"{'value':70s} {'len':>4s} {'width_mm':>10s} {'anchor':>8s}")
    results = {}
    for value, (px, py) in expected.items():
        matches = [e for e in entries if e["text"] == value]
        if not matches:
            print(f"{value!r:70s} NO MATCH")
            continue
        e = matches[0]
        results[value] = e["text_length"]
        print(f"{value:70s} {len(value):4d} {e['text_length']:10.3f} {e['anchor']:>8s}")

    xs = [len(v) for v in results]
    ys = [w for w in results.values()]
    n = len(xs)
    if n >= 2:
        sx, sy = sum(xs), sum(ys)
        sxx = sum(x * x for x in xs)
        sxy = sum(x * y for x, y in zip(xs, ys))
        slope = (n * sxy - sx * sy) / (n * sxx - sx * sx)
        intercept = (sy - slope * sx) / n
        print(f"\nfit: width_mm = {intercept:.4f} + {slope:.4f} * len(value)")
        max_resid = 0
        for v, w in results.items():
            pred = intercept + slope * len(v)
            resid = w - pred
            max_resid = max(max_resid, abs(resid))
        print(f"max abs residual: {max_resid:.3f} mm")


if __name__ == "__main__":
    main()
