#!/usr/bin/env python3
"""Hand-laid-out SVG diagrams for the cases where auto-layout produces an
unreadable aspect ratio (very long linear flows).

Each function returns SVG text sized to a viewBox of about 1000xN units, which
renders at 1 unit ≈ 1 px and stays legible when placed at 6.6 in width.
"""
import pathlib

OUT = pathlib.Path(__file__).resolve().parent

FONT = "font-family='DejaVu Sans, Helvetica, Arial, sans-serif'"


def box(x, y, w, h, label, fill="#ffffff", stroke="#1f3a5f", size=13, bold=False, sub=None):
    out = [f'<rect x="{x}" y="{y}" width="{w}" height="{h}" rx="7" fill="{fill}" '
           f'stroke="{stroke}" stroke-width="1.6"/>']
    weight = "bold" if bold else "normal"
    lines = label.split("\n")
    lh = size + 4
    total = lh * len(lines) + (lh if sub else 0)
    ty = y + (h - total) / 2 + size
    for i, line in enumerate(lines):
        out.append(f'<text x="{x + w/2}" y="{ty + i*lh}" {FONT} font-size="{size}" '
                   f'font-weight="{weight}" text-anchor="middle" fill="#12212f">{line}</text>')
    if sub:
        out.append(f'<text x="{x + w/2}" y="{ty + lh*len(lines)}" {FONT} font-size="{size-3}" '
                   f'text-anchor="middle" fill="#5a6672">{sub}</text>')
    return "\n".join(out)


def diamond(cx, cy, w, h, label, size=12):
    pts = f"{cx},{cy-h/2} {cx+w/2},{cy} {cx},{cy+h/2} {cx-w/2},{cy}"
    out = [f'<polygon points="{pts}" fill="#fdf6e3" stroke="#9b7b1f" stroke-width="1.6"/>']
    lines = label.split("\n")
    lh = size + 3
    ty = cy - (lh * len(lines)) / 2 + size - 2
    for i, line in enumerate(lines):
        out.append(f'<text x="{cx}" y="{ty + i*lh}" {FONT} font-size="{size}" '
                   f'text-anchor="middle" fill="#12212f">{line}</text>')
    return "\n".join(out)


def arrow(x1, y1, x2, y2, label=None, dashed=False, lx=None, ly=None):
    dash = ' stroke-dasharray="5 4"' if dashed else ""
    out = [f'<line x1="{x1}" y1="{y1}" x2="{x2}" y2="{y2}" stroke="#43525f" '
           f'stroke-width="1.5"{dash} marker-end="url(#arw)"/>']
    if label:
        out.append(f'<text x="{lx if lx is not None else (x1+x2)/2}" '
                   f'y="{ly if ly is not None else (y1+y2)/2 - 5}" {FONT} font-size="11" '
                   f'text-anchor="middle" fill="#43525f">{label}</text>')
    return "\n".join(out)


def path_arrow(d, label=None, lx=None, ly=None, dashed=False):
    dash = ' stroke-dasharray="5 4"' if dashed else ""
    out = [f'<path d="{d}" fill="none" stroke="#43525f" stroke-width="1.5"{dash} '
           f'marker-end="url(#arw)"/>']
    if label:
        out.append(f'<text x="{lx}" y="{ly}" {FONT} font-size="11" text-anchor="middle" '
                   f'fill="#43525f">{label}</text>')
    return "\n".join(out)


def frame(w, h, body, title=None):
    head = (f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" '
            f'viewBox="0 0 {w} {h}">\n'
            '<defs><marker id="arw" viewBox="0 0 10 10" refX="9" refY="5" '
            'markerWidth="7" markerHeight="7" orient="auto-start-reverse">'
            '<path d="M 0 0 L 10 5 L 0 10 z" fill="#43525f"/></marker></defs>\n'
            '<rect width="100%" height="100%" fill="#ffffff"/>\n')
    if title:
        head += (f'<text x="24" y="30" {FONT} font-size="15" font-weight="bold" '
                 f'fill="#1f3a5f">{title}</text>\n')
    return head + body + "\n</svg>\n"


def pos_checkout():
    W, H = 1020, 470
    b = []
    # lane labels
    b.append(f'<text x="24" y="62" {FONT} font-size="12" fill="#5a6672">CAPTURE</text>')
    b.append(f'<text x="24" y="196" {FONT} font-size="12" fill="#5a6672">POST /api/v1/pos — ONE TRANSACTION</text>')
    b.append(f'<text x="24" y="352" {FONT} font-size="12" fill="#5a6672">AFTER THE SALE</text>')

    b.append(box(120, 46, 170, 58, "Open shift", sub="POST /cashier/shifts"))
    b.append(box(330, 46, 170, 58, "Scan / search", sub="barcode or text"))
    b.append(box(540, 46, 190, 58, "Cart line", sub="server-resolved price"))
    b.append(box(770, 46, 200, 58, "Customer?", sub="walk-in or member"))

    b.append(arrow(290, 75, 328, 75))
    b.append(arrow(500, 75, 538, 75))
    b.append(arrow(730, 75, 768, 75))

    b.append(box(770, 120, 200, 46, "Member: tier discount snapshotted", size=11))
    b.append(box(540, 120, 190, 46, "Walk-in: list price", size=11))
    b.append(path_arrow("M 850 104 L 850 118"))
    b.append(path_arrow("M 660 104 L 660 118"))

    b.append(box(120, 180, 230, 52, "Idempotency-Key check", sub="422 when absent"))
    b.append(path_arrow("M 635 166 L 635 206 L 352 206"))
    b.append(path_arrow("M 850 166 L 850 206 L 352 206"))

    b.append(box(390, 180, 190, 52, "BEGIN transaction"))
    b.append(arrow(350, 206, 388, 206))

    b.append(box(620, 180, 180, 52, "SO confirmed", sub="order_type = pos"))
    b.append(arrow(580, 206, 618, 206))

    b.append(box(830, 180, 170, 52, "Stock deducted", sub="row lock + movement"))
    b.append(arrow(800, 206, 828, 206))

    b.append(box(830, 262, 170, 52, "Invoice issued", sub="paid | unpaid"))
    b.append(box(620, 262, 180, 52, "Payment recorded", sub="QRIS defers this"))
    b.append(box(390, 262, 190, 52, "COMMIT → 201", bold=True))
    b.append(arrow(915, 232, 915, 260))
    b.append(arrow(830, 288, 802, 288))
    b.append(arrow(620, 288, 582, 288))

    b.append(box(120, 336, 190, 52, "Receipt PDF", sub="/reports/render"))
    b.append(box(340, 336, 190, 52, "Close shift", sub="expected vs actual"))
    b.append(box(620, 336, 250, 52, "QRIS: poll / sweep", sub="settles the open invoice"))
    b.append(path_arrow("M 485 314 L 485 334"))
    b.append(arrow(310, 362, 338, 362))
    b.append(path_arrow("M 580 288 L 580 334 L 618 334", dashed=True))

    b.append(f'<text x="24" y="440" {FONT} font-size="11" fill="#5a6672">'
             'All money paths run in one transaction; a failure before COMMIT leaves no order, no stock movement and no invoice.</text>')
    return frame(W, H, "\n".join(b), "POS checkout — verified flow")


def pos_payment():
    W, H = 1000, 300
    b = []
    b.append(box(30, 120, 150, 56, "payment_method", sub="request field"))
    b.append(diamond(280, 148, 150, 84, "cash?"))
    b.append(arrow(180, 148, 205, 148))
    b.append(diamond(500, 148, 170, 84, "tender ≥ total?"))
    b.append(arrow(355, 148, 415, 148, "yes"))
    b.append(box(280, 230, 150, 46, "400 rejected", size=12))
    b.append(path_arrow("M 280 190 L 280 228", label="no", lx=292, ly=212))
    b.append(box(700, 70, 250, 52, "settled in transaction", sub="invoice paid, payment row", bold=True))
    b.append(arrow(585, 140, 698, 96, "yes"))
    b.append(diamond(500, 40, 200, 70, "transfer / giro /\ncheque / kartu?", size=11))
    b.append(path_arrow("M 280 106 L 280 40 L 400 40"))
    b.append(arrow(600, 40, 698, 78, "yes"))
    b.append(diamond(500, 250, 200, 70, "qris?", size=11))
    b.append(path_arrow("M 280 106 L 280 250 L 400 250", dashed=True))
    b.append(box(700, 170, 250, 50, "503 GATEWAY_DISABLED", size=11))
    b.append(path_arrow("M 600 250 L 650 250 L 650 210 L 698 200", label="no key", lx=612, ly=268))
    b.append(box(700, 232, 250, 50, "invoice unpaid, QR issued", sub="notification or sweep settles", size=11))
    b.append(arrow(600, 250, 698, 257, "key set"))
    b.append(f'<text x="30" y="292" {FONT} font-size="11" fill="#5a6672">'
             'Cash tender below the total is rejected before any write; a deferred QRIS order keeps the invoice open.</text>')
    return frame(W, H, "\n".join(b), "POS payment branch")


def auth_model():
    W, H = 1020, 430
    b = []
    b.append(box(30, 60, 210, 64, "Bearer token", sub="sha256 hash lookup"))
    b.append(arrow(240, 92, 288, 92))
    b.append(box(290, 60, 200, 64, "users row", sub="role, permissions JSON, app access"))
    b.append(arrow(490, 92, 538, 92))
    b.append(diamond(620, 92, 150, 80, "app scope\nmatches?"))
    b.append(box(830, 66, 160, 52, "403", size=13))
    b.append(arrow(695, 92, 828, 92, "no"))
    b.append(diamond(620, 230, 150, 80, "admin or\nowner?"))
    b.append(path_arrow("M 620 132 L 620 188", label="yes", lx=632, ly=164))
    b.append(box(830, 204, 160, 52, "allow", bold=True))
    b.append(arrow(695, 230, 828, 230, "yes"))
    b.append(diamond(400, 230, 160, 80, "override\ndenies?"))
    b.append(path_arrow("M 545 230 L 480 230"))
    b.append(box(830, 290, 160, 52, "deny 403", size=13))
    b.append(path_arrow("M 400 270 L 400 350 L 828 350 L 828 316", label="yes", lx=470, ly=342))
    b.append(diamond(180, 230, 150, 80, "override\ngrants?"))
    b.append(arrow(320, 230, 105, 230))
    b.append(path_arrow("M 180 270 L 180 350 L 300 350 L 300 300", label="no", lx=205, ly=342))
    b.append(box(150, 300, 170, 52, "role globs", sub="role_permissions"))
    b.append(path_arrow("M 180 190 L 180 298", label="no", lx=192, ly=250))
    b.append(box(340, 300, 170, 52, "allow if match", sub="else deny", bold=True))
    b.append(arrow(320, 326, 338, 326))
    b.append(f'<text x="30" y="410" {FONT} font-size="11" fill="#5a6672">'
             'Evaluation order verified in internal/auth/model.go Can() and EffectivePermissions().</text>')
    return frame(W, H, "\n".join(b), "Authorisation evaluation")


def main():
    (OUT / "fig-pos-checkout.svg").write_text(pos_checkout(), encoding="utf-8")
    (OUT / "fig-pos-payment.svg").write_text(pos_payment(), encoding="utf-8")
    (OUT / "fig-authz-model.svg").write_text(auth_model(), encoding="utf-8")
    print("wrote 3 svg diagrams")


if __name__ == "__main__":
    main()
