#!/usr/bin/env python3
"""Precompute mpmath O3 vectors for engineering.uncertainty.coverage_factor.

Two-sided k = t_{(1+p)/2}(ν). Infinite ν uses Φ⁻¹((1+p)/2).
Not U = k·u_c, not one-sided k, not Welch.

80 dps, round-to-nearest binary64.

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

import json
import math
import shutil
from pathlib import Path

import mpmath as mp

DPS = 80
GENERATOR_ID = "coverage-factor-mpmath-o3"
GENERATOR_VERSION = "1.0.0"
SEED = "20260916.coverage-factor-o3"


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


def ninv(q):
    return mp.sqrt(2) * mp.erfinv(2 * q - 1)


def t_ppf(nu, q):
    nu = mp.mpf(nu)
    if nu == 1:
        return mp.tan(mp.pi * (q - mp.mpf("0.5")))
    if nu == 2:
        a = 2 * q - 1
        return mp.sqrt(2) * a / mp.sqrt(1 - a * a)

    def f(t):
        x = nu / (nu + t * t)
        ib = mp.betainc(nu / 2, mp.mpf("0.5"), 0, x, regularized=True)
        return (1 - mp.mpf("0.5") * ib) - q

    return mp.findroot(f, ninv(q))


def k_row(nu, confidence, *, infinite: bool) -> dict:
    p = mp.mpf(confidence)
    q = (1 + p) / 2
    k = ninv(q) if infinite else t_ppf(nu, q)
    out = {
        "infinite": infinite,
        "k_source": "normal" if infinite else "student_t",
        "k_f64": f64(k),
        "k_decimal": mp.nstr(k, 40, strip_zeros=False),
        "confidence_f64": f64(p),
        "q_f64": f64(q),
    }
    if not infinite:
        out["nu_f64"] = f64(mp.mpf(nu))
    return out


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


def vectors() -> list[dict]:
    return [
        row("o3-t8", {"nu": 8, "confidence": 0.95}, k_row(8, "0.95", infinite=False), kind="student-t"),
        row("o3-inf", {"nu": "inf", "confidence": 0.95}, k_row(None, "0.95", infinite=True), kind="infinite"),
        row("o3-default", {"nu": 8}, k_row(8, "0.95", infinite=False), kind="default-p"),
        row("o3-nu1", {"nu": 1, "confidence": 0.95}, k_row(1, "0.95", infinite=False), kind="student-t"),
        row("o3-nu30", {"nu": 30, "confidence": 0.95}, k_row(30, "0.95", infinite=False), kind="student-t"),
        row("o3-p99", {"nu": 8, "confidence": 0.99}, k_row(8, "0.99", infinite=False), kind="other-p"),
        row("o3-alias", {"nu_eff": 8, "confidence": 0.95}, k_row(8, "0.95", infinite=False), kind="alias"),
        row("o3-nu2", {"nu": 2, "confidence": 0.95}, k_row(2, "0.95", infinite=False), kind="student-t"),
    ]


def main() -> None:
    mp.mp.dps = DPS
    here = Path(__file__).resolve().parent
    table = {
        "family": "coverage_factor",
        "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": "Two-sided k only. Independent of Node Math and the IUT. U=k·u_c and one-sided k are not tabulated.",
        "vectors": vectors(),
    }
    dest = here / "coverage-factor-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 / "coverage-factor-o3-tables.json")
    shutil.copy2(Path(__file__), public / "generate-coverage-factor-o3.py")
    print(f"wrote {dest} ({len(table['vectors'])} vectors)")


if __name__ == "__main__":
    main()
