"""
Row 19 — FRG toy-slice beta functions (single source of truth).

This module intentionally starts with the explicit two-coupling toy slice
documented in main.tex:

  η_N ≃ a1*g/(1 - a2*λ)
  β_g ≃ (2 + η_N) g
  β_λ ≃ (-2 + η_N) λ + (c/(2π)) g

The harness is written so you can later swap in the full projected FRG
( B1/B2, threshold functions, larger coupling vector ) without changing
the Jacobian-test plumbing.
"""
from __future__ import annotations
import math
import numpy as np
from dataclasses import dataclass

@dataclass(frozen=True)
class ToyParams:
    a1: float = 1.0
    a2: float = 1.0
    c:  float = 1.0

def eta_N(g: float, lam: float, p: ToyParams) -> float:
    denom = (1.0 - p.a2 * lam)
    return (p.a1 * g) / denom

def beta(x: np.ndarray, p: ToyParams = ToyParams()) -> np.ndarray:
    x = np.asarray(x, dtype=float)
    if x.shape[0] != 2:
        raise ValueError("Toy slice expects x = [g, lambda]")
    g, lam = float(x[0]), float(x[1])
    et = eta_N(g, lam, p)
    bg = (2.0 + et) * g
    bl = (-2.0 + et) * lam + (p.c / (2.0 * math.pi)) * g
    return np.array([bg, bl], dtype=float)

def jacobian_analytic(x: np.ndarray, p: ToyParams = ToyParams()) -> np.ndarray:
    """
    J_ij = ∂β_i/∂x_j for x=[g,λ]
    """
    x = np.asarray(x, dtype=float)
    g, lam = float(x[0]), float(x[1])

    denom = (1.0 - p.a2 * lam)
    denom2 = denom * denom

    et = (p.a1 * g) / denom
    # derivatives of η
    det_dg  = p.a1 / denom
    det_dlam = (p.a1 * g) * (p.a2) / denom2  # because d(1/denom)/dλ = a2/denom^2

    # β_g = (2+η) g
    # dβg/dg = (2+η) + g*dη/dg
    d_bg_dg   = (2.0 + et) + g * det_dg
    # dβg/dλ = g*dη/dλ
    d_bg_dlam = g * det_dlam

    # β_λ = (-2+η) λ + (c/(2π)) g
    d_bl_dg   = lam * det_dg + (p.c / (2.0 * math.pi))
    d_bl_dlam = (-2.0 + et) + lam * det_dlam

    return np.array([[d_bg_dg, d_bg_dlam],
                     [d_bl_dg, d_bl_dlam]], dtype=float)
