#!/usr/bin/env python3
# $Id: ppvisualize 72 2026-08-27 08:32:33Z peter $
#
# ppvisualize --- a MINIMAL, deliberately simple rendering of
# auto_layout.py's own placement decisions: anchors as labeled boxes,
# passives as labeled dots, connecting lines showing which net
# attached each passive to what. Built to answer one question
# honestly -- does the placement make structural sense -- not to
# produce a production-quality diagram. Reuses none of
# Power/breadboard-gen/build_pixel3a_breadboard.py's own drawing
# primitives on purpose: this is a fast, low-risk way to SEE the
# algorithm's output without touching that already-verified,
# working generator, or reproducing its own fuller rendering
# machinery (real component symbols, discretization, rail jumpers,
# collision-aware labels) before knowing whether the underlying
# placement is even sound.

import sys
import os
import subprocess

sys.path.insert(0, os.path.dirname(__file__))
from netlist import load_from_schematic
from auto_layout import layout, classify, pin_counts

COL_W = 18


def render(nl, p, anchors, passives, path, quiet=False):
    counts = pin_counts(nl)
    all_cols = list(p.anchor_pin_col.values()) + list(p.passive_col.values())
    max_col = max(all_cols) + 3
    W = max_col * COL_W + 100
    H = 320

    svg = [f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {W} {H}" font-family="monospace">']
    svg.append(f'<rect x="0" y="0" width="{W}" height="{H}" fill="#e9e4d8"/>')

    def x_of(col):
        return 50 + col * COL_W

    anchor_y = 120

    # Anchor boxes, spanning their own pin columns.
    for i, ref in enumerate(sorted(anchors)):
        my_pin_cols = [c for (r, pin), c in p.anchor_pin_col.items() if r == ref]
        x0, x1 = x_of(min(my_pin_cols)) - 8, x_of(max(my_pin_cols)) + 8
        svg.append(f'<rect x="{x0}" y="{anchor_y-20}" width="{x1-x0}" height="40" '
                    f'fill="none" stroke="#222" stroke-width="1.6"/>')
        label_y = anchor_y - 24 - (i % 2) * 14  # stagger to avoid label collisions --
                                                  # this throwaway visualizer doesn't
                                                  # need the real generator's own full
                                                  # collision-aware placement system,
                                                  # just enough to stay readable.
        svg.append(f'<text x="{x0+4}" y="{label_y}" font-size="10" fill="#222">'
                    f'{ref} ({nl.components[ref].value})</text>')
        for (r, pin), c in p.anchor_pin_col.items():
            if r != ref:
                continue
            svg.append(f'<circle cx="{x_of(c)}" cy="{anchor_y}" r="2.5" fill="#c0392b"/>')
            svg.append(f'<text x="{x_of(c)-4}" y="{anchor_y+34}" font-size="7" fill="#555" '
                        f'transform="rotate(-90 {x_of(c)-4} {anchor_y+34})">{pin}</text>')

    # Passives: a dot below the anchor row, a label, and a connecting
    # line to whatever it attached to (parsed back out of the
    # algorithm's own recorded reason string, for a visual trace of
    # WHY each one landed where it did).
    passive_y = 220
    for ref, col in p.passive_col.items():
        x = x_of(col)
        svg.append(f'<circle cx="{x}" cy="{passive_y}" r="3" fill="#2c7a4b"/>')
        svg.append(f'<text x="{x-10}" y="{passive_y+16}" font-size="8" fill="#222">'
                    f'{ref}</text>')
        svg.append(f'<line x1="{x}" y1="{passive_y-3}" x2="{x}" y2="{anchor_y+3}" '
                    f'stroke="#888" stroke-width="0.8" stroke-dasharray="2,2"/>')
        reason = p.reason.get(ref, "")
        if " with " in reason:
            other_ref = reason.split(" with ")[1].split(".")[0]
            if other_ref in p.passive_col:
                ox = x_of(p.passive_col[other_ref])
                svg.append(f'<line x1="{x}" y1="{passive_y}" x2="{ox}" y2="{passive_y}" '
                            f'stroke="#2980b9" stroke-width="1"/>')

    svg.append(f'<text x="10" y="20" font-size="11" fill="#222">'
                f'auto_layout.py prototype -- {len(anchors)} anchors, {len(passives)} passives, '
                f'{len(p.unplaced)} unplaced</text>')
    svg.append('</svg>')

    with open(path, "w") as f:
        f.write("\n".join(svg))
    if not quiet:
        print(f"wrote {path}, {W}x{H}")


def _version():
    """This project's own current Dewey-decimal version, read from
    VERSION -- see netlist.py's own identical helper for the full
    reasoning. Duplicated per-script rather than factored into a
    shared module -- see render.py's own identical note on why."""
    here = os.path.dirname(os.path.abspath(__file__))
    try:
        with open(os.path.join(here, "VERSION")) as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith("#"):
                    return line
    except OSError:
        pass
    return "unknown"


def main(argv=None):
    """CLI entry point, ADDED 2026-08-19, this project's own SIXTH
    script brought up to DevelopmentStandard.org's own real "Options
    and Arguments" bar -- see visualize.1 (this program's own new
    manual page)."""
    import argparse

    parser = argparse.ArgumentParser(
        prog="ppvisualize",
        description="Render a minimal, fast, structural-only preview of "
                     "auto_layout.py's own placement decision for a real "
                     "schematic: anchors as labeled boxes, passives as "
                     "labeled dots, lines showing which net attached each "
                     "passive to what. Deliberately NOT a real breadboard "
                     "diagram (no component shapes, no discretization, no "
                     "collision-aware labels -- see pprender(1) for "
                     "that); built to answer one question fast, does the "
                     "placement make structural sense, without depending "
                     "on the fuller renderer's own correctness.")
    parser.add_argument("schematic", metavar="SCHEMATIC",
                         help="a real .kicad_sch file")
    parser.add_argument("-o", "--output", metavar="FILE",
                         default="auto_layout_preview.svg",
                         help="output SVG path (default: %(default)s)")
    parser.add_argument("--kicad-cli", metavar="CMD",
                         default=os.environ.get("KICAD_CLI", "kicad-cli"),
                         help="kicad-cli command (default: $KICAD_CLI if "
                              "set, else 'kicad-cli' on PATH)")
    parser.add_argument("--version", action="version",
                         version=f"%(prog)s {_version()}")
    parser.add_argument("--debug", action="store_true",
                         help="also print the same placement-decision "
                              "text ppauto_layout(1) prints (which net "
                              "attached each passive to what), the real "
                              "data this preview draws from")
    parser.add_argument("--quiet", action="store_true",
                         help="suppress the final \"wrote ...\" line on "
                              "success")
    args = parser.parse_args(argv)

    try:
        nl = load_from_schematic(args.schematic, kicad_cli=args.kicad_cli)
    except (OSError, subprocess.CalledProcessError, ValueError) as e:
        print(f"ppvisualize: {args.schematic}: {e}", file=sys.stderr)
        raise SystemExit(1)

    p, anchors, passives, rails = layout(nl)
    if args.debug:
        for ref in sorted(anchors):
            print(f"  {ref}: starts at column {p.anchor_col[ref]}")
        for ref in sorted(passives):
            if ref in p.passive_col:
                print(f"  {ref}: column {p.passive_col[ref]} -- {p.reason[ref]}")
            else:
                print(f"  {ref}: UNPLACED")
    render(nl, p, anchors, passives, args.output, quiet=args.quiet)


if __name__ == "__main__":
    main()
