#!/usr/bin/env python3
"""Precompute mpmath high-precision O3 vectors for engineering.tolerance.stackup.

Independent of Node/V8 binary64 and of the IUT. Basic RSS = √Σ Tᵢ²,
WCA = Σ|Tᵢ|; Engineering a=(T++T−)/2, u = a | a/3 | a/√3.
80 dps, round-to-nearest binary64. Does not pin a gmpy2 backend.

Re-run: python3 scripts/lib/cvp/oracles/generate-tolerance-stackup-o3.py
"""
from __future__ import annotations

import json
import math
import shutil
from pathlib import Path

import mpmath as mp

DPS = 80
GENERATOR_ID = "tolerance-stackup-mpmath-o3"
GENERATOR_VERSION = "1.0.0"
SEED = "20260916.stackup-o3"

ENG_SYM = [
    {"name": "A", "nom": 10, "tol_plus": 0.1, "tol_minus": 0.1, "direction": "+"},
    {"name": "B", "nom": 5, "tol_plus": 0.2, "tol_minus": 0.2, "direction": "-"},
    {"name": "C", "nom": 3, "tol_plus": 0.1, "tol_minus": 0.1, "direction": "+"},
]
ENG_ASYM = [
    {"name": "A", "nom": 10, "tol_plus": 0.05, "tol_minus": 0.15, "direction": "+"},
    {"name": "B", "nom": 5, "tol_plus": 0.2, "tol_minus": 0.2, "direction": "-"},
    {"name": "C", "nom": 3, "tol_plus": 0.1, "tol_minus": 0.1, "direction": "+"},
]


def f64(x: mp.mpf) -> float:
    return float(x)


def mp_fields(**kwargs) -> dict:
    out = {}
    for k, v in kwargs.items():
        if v is None:
            continue
        out[f"{k}_f64"] = f64(v)
        out[f"{k}_decimal"] = mp.nstr(v, 40, strip_zeros=False)
    return out


def rss_contributor(a: mp.mpf, basis: str) -> mp.mpf:
    if a <= 0:
        return mp.mpf(0)
    if basis == "uniform":
        return a / mp.sqrt(3)
    if basis == "3sigma":
        return a / 3
    return a


def basic(mode: str, tols: list[float]) -> dict:
    abs_t = [abs(mp.mpf(t)) for t in tols]
    stack_worst = sum(abs_t, mp.mpf(0))
    stack_rss = mp.sqrt(sum((t * t for t in abs_t), mp.mpf(0)))
    stack = stack_rss if mode == "rss" else stack_worst
    return mp_fields(
        stack=stack,
        stack_rss=stack_rss,
        stack_worst=stack_worst,
        stack_worst_plus=stack_worst,
        stack_worst_minus=stack_worst,
    ) | {"profile": "basic", "mode": mode}


def engineering(mode: str, dimensions: list[dict]) -> dict:
    stack_nom = mp.mpf(0)
    stack_stat_center = mp.mpf(0)
    pos_exc = mp.mpf(0)
    neg_exc = mp.mpf(0)
    rss_sq = mp.mpf(0)
    for d in dimensions:
        nom = mp.mpf(d.get("nom", 0))
        if "tol" in d and d.get("tol_plus") is None and d.get("tol_minus") is None:
            t = abs(mp.mpf(d["tol"]))
            tol_plus = t
            tol_minus = t
        else:
            tol_plus = mp.mpf(d.get("tol_plus", 0))
            tol_minus = mp.mpf(d.get("tol_minus", 0))
        direction = 1 if str(d.get("direction", "+")) in ("+", "1", "plus") else -1
        basis = str(d.get("statistical_basis") or d.get("distribution") or "magnitude")
        half = (tol_plus + tol_minus) / 2
        t_rss = rss_contributor(half, basis)
        d_max = tol_plus if direction == 1 else tol_minus
        d_min = -tol_minus if direction == 1 else -tol_plus
        center_shift = (tol_plus - tol_minus) / 2 if basis == "uniform" else mp.mpf(0)
        stack_nom += direction * nom
        stack_stat_center += direction * (nom + center_shift)
        pos_exc += d_max
        neg_exc += d_min
        rss_sq += t_rss * t_rss
    stack_max = stack_nom + pos_exc
    stack_min = stack_nom + neg_exc
    stack_worst_plus = pos_exc
    stack_worst_minus = -neg_exc
    stack_worst = max(stack_worst_plus, stack_worst_minus)
    stack_rss = mp.sqrt(rss_sq)
    stack = stack_rss if mode == "rss" else stack_worst
    return mp_fields(
        stack=stack,
        stack_rss=stack_rss,
        stack_worst=stack_worst,
        stack_worst_plus=stack_worst_plus,
        stack_worst_minus=stack_worst_minus,
        stack_nom=stack_nom,
        stack_stat_center=stack_stat_center,
        stack_min=stack_min,
        stack_max=stack_max,
    ) | {"profile": "engineering", "mode": mode}


def row(vid: str, inputs: dict, *, kind: str, extra: dict) -> dict:
    return {"id": vid, "kind": kind, "inputs": inputs, **extra}


def vectors() -> list[dict]:
    return [
        row("o3-rss", {"mode": "rss", "tol": [0.1, 0.2, 0.1]}, kind="basic-rss", extra=basic("rss", [0.1, 0.2, 0.1])),
        row(
            "o3-wca",
            {"mode": "worst_case", "tol1": 0.1, "tol2": 0.2, "tol3": 0.1},
            kind="basic-wca",
            extra=basic("worst_case", [0.1, 0.2, 0.1]),
        ),
        row(
            "o3-rss-fields",
            {"mode": "rss", "tol1": 0.1, "tol2": 0.2, "tol3": 0.1},
            kind="basic-rss-fields",
            extra=basic("rss", [0.1, 0.2, 0.1]),
        ),
        row(
            "o3-eng-symmetric",
            {"mode": "rss", "dimensions": ENG_SYM},
            kind="engineering-symmetric",
            extra=engineering("rss", ENG_SYM),
        ),
        row(
            "o3-eng-asymmetric",
            {"mode": "worst_case", "dimensions": ENG_ASYM},
            kind="engineering-asymmetric-wca",
            extra=engineering("worst_case", ENG_ASYM),
        ),
        row(
            "o3-eng-asymm-rss",
            {"mode": "rss", "dimensions": ENG_ASYM},
            kind="engineering-asymmetric-rss",
            extra=engineering("rss", ENG_ASYM),
        ),
        row(
            "o3-eng-1sigma",
            {
                "mode": "rss",
                "dimensions": [{"name": "A", "nom": 0, "tol": 0.3, "direction": "+", "statistical_basis": "1sigma"}],
            },
            kind="engineering-1sigma",
            extra=engineering(
                "rss",
                [{"name": "A", "nom": 0, "tol": 0.3, "direction": "+", "statistical_basis": "1sigma"}],
            ),
        ),
        row(
            "o3-eng-3sigma",
            {
                "mode": "rss",
                "dimensions": [{"name": "A", "nom": 0, "tol": 0.3, "direction": "+", "statistical_basis": "3sigma"}],
            },
            kind="engineering-3sigma",
            extra=engineering(
                "rss",
                [{"name": "A", "nom": 0, "tol": 0.3, "direction": "+", "statistical_basis": "3sigma"}],
            ),
        ),
        row(
            "o3-eng-uniform",
            {
                "mode": "rss",
                "dimensions": [{"name": "A", "nom": 0, "tol": 0.3, "direction": "+", "statistical_basis": "uniform"}],
            },
            kind="engineering-uniform",
            extra=engineering(
                "rss",
                [{"name": "A", "nom": 0, "tol": 0.3, "direction": "+", "statistical_basis": "uniform"}],
            ),
        ),
        row(
            "o3-eng-uniform-center",
            {
                "mode": "rss",
                "dimensions": [{**row, "statistical_basis": "uniform"} for row in ENG_ASYM],
            },
            kind="engineering-uniform-center",
            extra=engineering("rss", [{**row, "statistical_basis": "uniform"} for row in ENG_ASYM]),
        ),
    ]


def main() -> None:
    mp.mp.dps = DPS
    here = Path(__file__).resolve().parent
    table = {
        "family": "tolerance_stackup",
        "generator_id": GENERATOR_ID,
        "generator_version": GENERATOR_VERSION,
        "seed": SEED,
        "mpmath_dps": DPS,
        "precision_bits": int(DPS * math.log2(10)),
        "library": f"mpmath {mp.__version__}",
        "notes": "Basic RSS/WCA and Engineering dimensions[]; independent of Node Math and the IUT.",
        "vectors": vectors(),
    }
    dest = here / "tolerance-stackup-o3-tables.json"
    dest.write_text(json.dumps(table, indent=2) + "\n", encoding="utf-8")
    public = here.parents[3] / "public/developers/cvp/reproduce"
    public.mkdir(parents=True, exist_ok=True)
    shutil.copy2(dest, public / "tolerance-stackup-o3-tables.json")
    shutil.copy2(Path(__file__), public / "generate-tolerance-stackup-o3.py")
    print(f"wrote {dest} ({len(table['vectors'])} vectors)")


if __name__ == "__main__":
    main()
