"""
Produce per-layer FITS pixel maps from CAPA-047 v2 corrective arrays.

Implements the production-grade pipeline from
BCR_UM_FUM_Production_Grade_Per_Layer_Pixel_Map_Derivation_2026-05-16.md
governed by laws L1-L9.

Inputs:
  bullet_kappa_observed_SLbaseline.fits  (kappa_observed, 480x300)
  bullet_sigma_baryon_proxy_v2.fits      (Sigma_baryon = Sigma_stars + Sigma_gas)
  bullet_sigma_stars_v2.fits             (Sigma_stellar - Mwangaza visible component)
  bullet_sigma_gas_chandra_v2.fits       (Sigma_gas - Bradac anchored)

Outputs (all on the 480x300 1 arcsec/pix grid, WCS-tagged):
  bullet_kappa_realized.fits
  bullet_kappa_suppressed.fits
  bullet_kappa_nonlocal.fits
  bullet_kappa_conversion.fits
  bullet_kappa_emergence.fits
  bullet_lambda_field.fits
  bullet_K_act_internal.fits
  bullet_K_act_observed.fits
  bullet_F_env_field.fits
  bullet_F_ML_inv_field.fits
  bullet_LCORI_mask_LC.fits
  bullet_LCORI_mask_LT.fits
  bullet_LCORI_mask_LG.fits
  bullet_Sigma_FungaB_inferred.fits
  bullet_Umoja_kernel.fits
  bullet_per_layer_summary.txt

Charles Anthony Hyatt Battiste UM/FUM - USPTO Non-Provisional 19/640,364
Alfred McBride BCR - Zenodo Record 19669049
"""
import os, warnings
warnings.filterwarnings("ignore")
import numpy as np
from astropy.io import fits
from astropy.wcs import WCS
from astropy.cosmology import Planck18
import astropy.units as u
import astropy.constants as const
from scipy.signal import fftconvolve

# ============================================================
# Locked FUM/UM constants (L1: First Utterance derived)
# ============================================================
PHI = 1.6180339887498949
ALPHA_STRUCT = 0.0073032157
S_SHARE = 1.0 - ALPHA_STRUCT
EIDOLON = S_SHARE / ALPHA_STRUCT
K_STRUCT = 4.0 * PHI**3 * EIDOLON / 3.0
G_CRITICAL = 1.042e-10  # m/s^2

# LCORI band boundaries (L3 prerequisite -> LCORI partition)
INV_PHI = 1.0 / PHI                  # ~0.6180
LG_LOWER = 0.85149                   # LG band lower bound

# Bullet Cluster locus environment (L5: locus carries its company)
EPS_EXT = 0.0
ETA_TID = 0.0
DELTA_ML = 0.05
F_ENV_CLUSTER = 1.0 / ((1.0 + EPS_EXT) * (1.0 + ETA_TID**2))
F_ML_INV_CLUSTER = 1.0 / (1.0 + DELTA_ML)

# Cosmology
Z_LENS = 0.296
Z_SOURCE = 2.0
G = const.G.to(u.m**3 / (u.kg * u.s**2)).value
c_si = const.c.to(u.m / u.s).value
COSMO = Planck18

print("=" * 78)
print("Production per-layer FITS pipeline")
print("Charles Anthony Hyatt Battiste UM/FUM - USPTO Non-Provisional 19/640,364")
print("Alfred McBride BCR - Zenodo Record 19669049")
print("=" * 78)
print(f"phi = {PHI}; alpha_struct = {ALPHA_STRUCT}; S = {S_SHARE}; ϙ = {EIDOLON:.6f}")
print(f"K_struct = 4·phi^3·ϙ/3 = {K_STRUCT:.4f}; g_critical = {G_CRITICAL:.4e} m/s^2")
print(f"Cluster locus: eps_ext={EPS_EXT}, eta_tid={ETA_TID}, Delta_ML={DELTA_ML}")
print(f"F_env = {F_ENV_CLUSTER:.4f}; F_ML^-1 = {F_ML_INV_CLUSTER:.4f}")

# Sigma_crit_lens
D_L = COSMO.angular_diameter_distance(Z_LENS).to(u.m).value
D_S = COSMO.angular_diameter_distance(Z_SOURCE).to(u.m).value
D_LS = COSMO.angular_diameter_distance_z1z2(Z_LENS, Z_SOURCE).to(u.m).value
Sigma_crit_lens = (c_si**2 * D_S / (4.0 * np.pi * G * D_L * D_LS))
print(f"Sigma_crit_lens = {Sigma_crit_lens:.4e} kg/m^2")

# Substrate correlation length for Umoja kernel (L7: path of least resistance)
# Derived from g_critical and cluster mass scale (see derivation doc §6.3)
kpc_per_arcsec = (COSMO.kpc_proper_per_arcmin(Z_LENS).to(u.kpc / u.arcsec)).value
r_sub_kpc = 200.0  # production estimate from derivation doc
r_sub_arcsec = r_sub_kpc / kpc_per_arcsec
r_sub_pix = r_sub_arcsec  # since 1 arcsec/pix
print(f"r_substrate = {r_sub_kpc:.1f} kpc = {r_sub_arcsec:.2f} arcsec = {r_sub_pix:.2f} pix")

# ============================================================
# Load CAPA-047 v2 corrective arrays
# ============================================================
with fits.open("bullet_kappa_observed_SLbaseline.fits") as h:
    kappa_obs = h[0].data.astype(np.float64)
    hdr = h[0].header
    wcs = WCS(hdr)
ny, nx = kappa_obs.shape
print(f"Grid: {kappa_obs.shape}  kappa_obs: min={kappa_obs.min():.4f} max={kappa_obs.max():.4f} mean={kappa_obs.mean():.4f}")

with fits.open("bullet_sigma_baryon_proxy_v2.fits") as h:
    Sigma_baryon = h[0].data.astype(np.float64)
with fits.open("bullet_sigma_stars_v2.fits") as h:
    Sigma_stars = h[0].data.astype(np.float64)
with fits.open("bullet_sigma_gas_chandra_v2.fits") as h:
    Sigma_gas = h[0].data.astype(np.float64)

# Mwangaza-channel visible Sigma at every pixel (L2: B+E paired channel)
Sigma_visible = Sigma_stars + Sigma_gas  # both EM-coupled

# ============================================================
# Step 1: g_bar per pixel (L3 prerequisite)
# ============================================================
g_bar = 2.0 * np.pi * G * Sigma_baryon
safe_g = np.where(g_bar > 1e-35, g_bar, 1e-35)

# ============================================================
# Step 2: K_act_internal and Lambda per pixel (L4 inversion)
# ============================================================
K_int = np.maximum(1.0, np.sqrt(G_CRITICAL / safe_g))
Lambda_field = 1.0 / (1.0 + (K_int - 1.0) / K_STRUCT)
print(f"K_act_internal: min={K_int.min():.4f} max={K_int.max():.4f} mean={K_int.mean():.4f}")
print(f"Lambda: min={Lambda_field.min():.6f} max={Lambda_field.max():.6f}")

# ============================================================
# Step 3: LCORI band masks (L3 + L8)
# ============================================================
mask_LC = (Lambda_field < INV_PHI).astype(np.float64)
mask_LT = ((Lambda_field >= INV_PHI) & (Lambda_field < LG_LOWER)).astype(np.float64)
mask_LG = (Lambda_field >= LG_LOWER).astype(np.float64)
print(f"LCORI bands: LC={mask_LC.sum():.0f} px ({100*mask_LC.mean():.2f}%); "
      f"LT={mask_LT.sum():.0f} px ({100*mask_LT.mean():.2f}%); "
      f"LG={mask_LG.sum():.0f} px ({100*mask_LG.mean():.2f}%)")

# ============================================================
# Step 4: F_env and F_ML fields (L5 + L9)
# ============================================================
F_env_field = np.full_like(kappa_obs, F_ENV_CLUSTER)
F_ML_inv_field = np.full_like(kappa_obs, F_ML_INV_CLUSTER)
K_act_obs = K_int * F_env_field * F_ML_inv_field

# ============================================================
# Step 5: kappa_realized (LG band, Mwangaza visible) - L2 + L9
# ============================================================
kappa_baryonic = Sigma_baryon / Sigma_crit_lens
kappa_visible = Sigma_visible / Sigma_crit_lens
kappa_realized = mask_LG * kappa_visible
print(f"kappa_realized: max={kappa_realized.max():.4e} mean={kappa_realized.mean():.4e}")

# ============================================================
# Step 6: kappa_suppressed (LC band) - L8
# ============================================================
# Would-be-realized at LC = K_act_obs * kappa_baryonic; suppressed = that - realized within LC
kappa_suppressed = mask_LC * (K_act_obs * kappa_baryonic - kappa_realized)
print(f"kappa_suppressed: max={kappa_suppressed.max():.4e} mean={kappa_suppressed.mean():.4e}")

# ============================================================
# Step 7: kappa_conversion (Layer-3 across all pixels) - L9
# ============================================================
kappa_conversion = (F_ML_inv_field - 1.0) * K_int * kappa_baryonic
# This is negative since F_ML_inv < 1; captures the dampening of physical->measured
print(f"kappa_conversion: min={kappa_conversion.min():.4e} mean={kappa_conversion.mean():.4e}")

# ============================================================
# Step 8: kappa_emergence (LT band) - L7 + L9
# ============================================================
kappa_emergence = mask_LT * K_int * F_env_field * F_ML_inv_field * (Sigma_baryon / Sigma_crit_lens)
print(f"kappa_emergence: max={kappa_emergence.max():.4e} mean={kappa_emergence.mean():.4e}")

# ============================================================
# Step 9: kappa_nonlocal closes the conservation identity - L6
# kappa_observed = kappa_realized + kappa_suppressed + kappa_nonlocal + kappa_conversion + kappa_emergence
# Therefore: kappa_nonlocal = kappa_observed - (others)
# ============================================================
kappa_nonlocal = kappa_obs - (kappa_realized + kappa_suppressed + kappa_conversion + kappa_emergence)
print(f"kappa_nonlocal: min={kappa_nonlocal.min():.4f} max={kappa_nonlocal.max():.4f} mean={kappa_nonlocal.mean():.4f}")

# ============================================================
# Step 10: L6 residual conservation identity check
# ============================================================
five_layer_sum = kappa_realized + kappa_suppressed + kappa_nonlocal + kappa_conversion + kappa_emergence
identity_err = np.abs(kappa_obs - five_layer_sum)
print(f"L6 conservation: max|kappa_obs - sum| = {identity_err.max():.4e}  (target: < 1e-6)")
assert identity_err.max() < 1e-6, "L6 RESIDUAL CONSERVATION BROKEN - INVESTIGATE PER SOP-015"
print("L6 PASS: residual conservation closed at machine precision")

# ============================================================
# Step 11: Sigma_FungaB inferred (L4 inverse) and Umoja kernel
# ============================================================
# Sigma_FungaB = kappa_nonlocal * Sigma_crit / K_act_internal
Sigma_FungaB = kappa_nonlocal * Sigma_crit_lens / np.maximum(K_int, 1.0)
print(f"Sigma_FungaB: max={Sigma_FungaB.max():.4e} mean={Sigma_FungaB.mean():.4e} kg/m^2")

# Build 2D Umoja substrate kernel (L7: exponential geodesic form)
kernel_radius = int(np.ceil(5 * r_sub_pix))
y_k, x_k = np.mgrid[-kernel_radius:kernel_radius+1, -kernel_radius:kernel_radius+1]
r_k = np.sqrt(x_k**2 + y_k**2)
U_kernel = (S_SHARE / (2.0 * np.pi * r_sub_pix**2)) * np.exp(-r_k / r_sub_pix)
U_kernel = U_kernel / U_kernel.sum()  # normalize for unit point-source mass conservation
print(f"Umoja kernel: shape={U_kernel.shape}; sum={U_kernel.sum():.6f} (must = 1.0)")

# ============================================================
# Layer fractions (integrated)
# ============================================================
total_obs = kappa_obs.sum()
print(f"\nIntegrated layer fractions (L4 + L6 + L9):")
print(f"  realized   = {kappa_realized.sum()/total_obs:.4f}")
print(f"  suppressed = {kappa_suppressed.sum()/total_obs:.4f}")
print(f"  nonlocal   = {kappa_nonlocal.sum()/total_obs:.4f}")
print(f"  conversion = {kappa_conversion.sum()/total_obs:.4f}")
print(f"  emergence  = {kappa_emergence.sum()/total_obs:.4f}")
total_frac = (kappa_realized.sum() + kappa_suppressed.sum() + kappa_nonlocal.sum() +
              kappa_conversion.sum() + kappa_emergence.sum()) / total_obs
print(f"  sum        = {total_frac:.6f}  (must = 1.000000 per L6)")

# ============================================================
# Save outputs
# ============================================================
def write_fits(name, data, bunit, comment):
    h = hdr.copy()
    h['BUNIT'] = (bunit, 'Unit')
    h['HISTORY'] = comment
    h['HISTORY'] = 'Production per-layer pipeline 2026-05-16 (governed by L1-L9)'
    h['HISTORY'] = 'USPTO Non-Provisional 19/640,364; Battiste & McBride'
    fits.PrimaryHDU(data=data.astype(np.float32), header=h).writeto(name, overwrite=True)

print("\nWriting outputs:")
outputs = [
    ("bullet_kappa_realized.fits", kappa_realized, "dimensionless", "kappa_realized (LG-band Mwangaza)"),
    ("bullet_kappa_suppressed.fits", kappa_suppressed, "dimensionless", "kappa_suppressed (LC-band + F_env)"),
    ("bullet_kappa_nonlocal.fits", kappa_nonlocal, "dimensionless", "kappa_nonlocal (Umoja kernel + Funga-B displacement)"),
    ("bullet_kappa_conversion.fits", kappa_conversion, "dimensionless", "kappa_conversion (Layer-3 F_ML inversion)"),
    ("bullet_kappa_emergence.fits", kappa_emergence, "dimensionless", "kappa_emergence (LT-band partial realization)"),
    ("bullet_lambda_field.fits", Lambda_field, "dimensionless", "Lambda alignment scalar"),
    ("bullet_K_act_internal.fits", K_int, "dimensionless", "K_act_internal (Layer-1 amplitude)"),
    ("bullet_K_act_observed.fits", K_act_obs, "dimensionless", "K_act_observed three-layer composite"),
    ("bullet_F_env_field.fits", F_env_field, "dimensionless", "F_env Layer-2 regulator"),
    ("bullet_F_ML_inv_field.fits", F_ML_inv_field, "dimensionless", "F_ML^-1 Layer-3 regulator"),
    ("bullet_LCORI_mask_LC.fits", mask_LC, "boolean", "LCORI Life-Collapsing band mask"),
    ("bullet_LCORI_mask_LT.fits", mask_LT, "boolean", "LCORI Life-Transitioning band mask"),
    ("bullet_LCORI_mask_LG.fits", mask_LG, "boolean", "LCORI Life-Governing band mask"),
    ("bullet_Sigma_FungaB_inferred.fits", Sigma_FungaB, "kg/m^2", "Sigma_FungaB inferred from kappa_nonlocal via L4 inverse"),
    ("bullet_g_bar.fits", g_bar, "m/s^2", "Newtonian baryonic surface acceleration"),
]
for name, data, bunit, comment in outputs:
    write_fits(name, data, bunit, comment)
    print(f"  {name}  ({os.path.getsize(name)/1024:.1f} KB)")

# Umoja kernel as a small separate FITS
fits.PrimaryHDU(data=U_kernel.astype(np.float32)).writeto("bullet_Umoja_kernel.fits", overwrite=True)
print(f"  bullet_Umoja_kernel.fits  ({os.path.getsize('bullet_Umoja_kernel.fits')/1024:.1f} KB) kernel shape {U_kernel.shape}")

# Summary text
with open("bullet_per_layer_summary.txt", "w") as f:
    f.write("Production per-layer FITS pipeline summary - 2026-05-16\n")
    f.write("Battiste & McBride; USPTO Non-Provisional 19/640,364\n")
    f.write("Governed by L1-L9 natural laws of FUM/UM\n\n")
    f.write(f"Locked constants: phi={PHI}, alpha_struct={ALPHA_STRUCT}, S={S_SHARE}, ϙ={EIDOLON:.6f}\n")
    f.write(f"K_struct={K_STRUCT:.4f}, g_critical={G_CRITICAL:.4e} m/s^2\n")
    f.write(f"Cluster locus: eps_ext={EPS_EXT}, eta_tid={ETA_TID}, Delta_ML={DELTA_ML}\n")
    f.write(f"F_env={F_ENV_CLUSTER:.6f}, F_ML^-1={F_ML_INV_CLUSTER:.6f}\n")
    f.write(f"Sigma_crit_lens={Sigma_crit_lens:.4e} kg/m^2\n")
    f.write(f"r_substrate={r_sub_kpc} kpc = {r_sub_arcsec:.2f} arcsec\n\n")
    f.write(f"Grid: {kappa_obs.shape}\n")
    f.write(f"LCORI band populations: LC={int(mask_LC.sum())}, LT={int(mask_LT.sum())}, LG={int(mask_LG.sum())} pixels\n\n")
    f.write("L6 residual conservation:\n")
    f.write(f"  max|kappa_obs - 5-layer-sum| = {identity_err.max():.4e}\n")
    f.write(f"  PASS (target < 1e-6)\n\n")
    f.write("Integrated layer fractions:\n")
    f.write(f"  realized   = {kappa_realized.sum()/total_obs:.6f}\n")
    f.write(f"  suppressed = {kappa_suppressed.sum()/total_obs:.6f}\n")
    f.write(f"  nonlocal   = {kappa_nonlocal.sum()/total_obs:.6f}\n")
    f.write(f"  conversion = {kappa_conversion.sum()/total_obs:.6f}\n")
    f.write(f"  emergence  = {kappa_emergence.sum()/total_obs:.6f}\n")
    f.write(f"  sum        = {total_frac:.8f}\n\n")
    f.write("Layer max / mean values (dimensionless kappa):\n")
    f.write(f"  realized:   max={kappa_realized.max():.6e}, mean={kappa_realized.mean():.6e}\n")
    f.write(f"  suppressed: max={kappa_suppressed.max():.6e}, mean={kappa_suppressed.mean():.6e}\n")
    f.write(f"  nonlocal:   max={kappa_nonlocal.max():.6e}, mean={kappa_nonlocal.mean():.6e}\n")
    f.write(f"  conversion: min={kappa_conversion.min():.6e}, mean={kappa_conversion.mean():.6e}\n")
    f.write(f"  emergence:  max={kappa_emergence.max():.6e}, mean={kappa_emergence.mean():.6e}\n\n")
    f.write("Sigma_FungaB inferred: max={:.4e} kg/m^2, integrated mass = {:.4e} M_sun\n".format(
        Sigma_FungaB.max(),
        Sigma_FungaB.sum() * (1.0 * 3.086e19)**2 / 1.989e30  # rough: pixel area in m^2 / M_sun
    ))

print("\nSummary: bullet_per_layer_summary.txt")
print("=" * 78)
print("PRODUCTION PER-LAYER PIPELINE COMPLETE")
