"""Reproduce an illustrative NASA 2000 MHD-cycle exit-pressure sensitivity.

This varies a chosen exit-pressure boundary in one existing conditional cycle
case. It derives area from continuity independently for each exit state, so it
is not a fixed-geometry nozzle prediction or a reproduction of NASA author code.
"""
from __future__ import annotations

import csv
import json
import math
from pathlib import Path

HERE = Path(__file__).resolve().parent
INPUT = HERE / "nasa-mhd-cycle-model-results.json"
Q_VALUES = (0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.4)
GAMMA = 1.4
R_AIR_J_PER_KG_K = 287.05


def build_rows() -> tuple[dict, list[dict]]:
    cases = json.loads(INPUT.read_text(encoding="utf-8"))
    matches = [
        row for row in cases
        if row.get("model_case") == "figure2-burner-cap-case"
        and row.get("mach") == 6.0
        and row.get("extraction") == 0.25
        and row.get("status") == "calculated"
    ]
    if len(matches) != 1:
        raise ValueError("Expected exactly one calculated Mach-6, extraction-0.25 reference row")
    base = matches[0]
    output = []
    for q in Q_VALUES:
        p0e_over_pa = base["p0e_over_pa"]
        me_sq = 2 / (GAMMA - 1) * ((p0e_over_pa / q) ** ((GAMMA - 1) / GAMMA) - 1)
        if me_sq <= 0:
            raise ValueError(f"No positive exit Mach for q={q}")
        me = math.sqrt(me_sq)
        te = base["t06_K"] / (1 + (GAMMA - 1) * me_sq / 2)
        ue = me * math.sqrt(GAMMA * R_AIR_J_PER_KG_K * te)
        f = base["fuel_air_ratio"]
        # Continuity, assuming all air and fuel cross the same exit plane.
        exit_area_per_air_mass_flow = (1 + f) * R_AIR_J_PER_KG_K * te / (q * ue)
        momentum = (1 + f) * ue - base["freestream_speed_m_s"]
        pressure = exit_area_per_air_mass_flow * (q - 1)
        output.append({
            "exit_pressure_over_ambient_q": q,
            "exit_mach": me,
            "exit_static_temperature_K": te,
            "exit_velocity_m_s": ue,
            "exit_area_over_inlet_air_mass_flow_m2_s_per_kg": exit_area_per_air_mass_flow,
            "momentum_specific_thrust_N_s_per_kg_air": momentum,
            "pressure_specific_thrust_N_s_per_kg_air": pressure,
            "total_specific_thrust_N_s_per_kg_air": momentum + pressure,
        })
    return base, output


def write_csv(rows: list[dict]) -> None:
    with (HERE / "nasa-mhd-nozzle-boundary-sensitivity.csv").open("w", newline="", encoding="utf-8") as stream:
        writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
        writer.writeheader()
        writer.writerows(rows)


def write_svg(rows: list[dict]) -> None:
    width, height = 1200, 800
    left, right = 120, 90
    x0, x1 = left, width - right
    q0, q1 = Q_VALUES[0], Q_VALUES[-1]
    px = lambda q: x0 + (q - q0) / (q1 - q0) * (x1 - x0)
    top0, top1 = 135, 390
    bot0, bot1 = 515, 690
    thrust_min, thrust_max = 650.0, 740.0
    pressure_min, pressure_max = -40.0, 40.0
    y_thrust = lambda value: top1 - (value - thrust_min) / (thrust_max - thrust_min) * (top1 - top0)
    y_pressure = lambda value: bot1 - (value - pressure_min) / (pressure_max - pressure_min) * (bot1 - bot0)
    colors = {
        "momentum_specific_thrust_N_s_per_kg_air": "#62c6ff",
        "pressure_specific_thrust_N_s_per_kg_air": "#ffd166",
        "total_specific_thrust_N_s_per_kg_air": "#ff71cf",
    }
    labels = {
        "momentum_specific_thrust_N_s_per_kg_air": "Momentum contribution",
        "pressure_specific_thrust_N_s_per_kg_air": "Pressure contribution",
        "total_specific_thrust_N_s_per_kg_air": "Total specific thrust",
    }
    lines = [
        f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}" role="img" aria-labelledby="title desc">',
        '<title id="title">Illustrative nozzle exit-pressure boundary sensitivity</title>',
        '<desc id="desc">For one conditional Mach 6, extraction-ratio 0.25 cycle calculation, the selected exit-pressure ratio varies from 0.7 to 1.4. The top graph separates momentum specific thrust and total specific thrust. The lower graph shows the pressure-thrust contribution. The exit area is derived from continuity separately for each point, so this is not a fixed-geometry nozzle prediction.</desc>',
        '<rect width="100%" height="100%" fill="#07152b"/>',
        '<text x="120" y="55" fill="#fff" font-size="25" font-weight="700">Exit-pressure boundary sensitivity · M = 6 · extraction = 0.25</text>',
        '<text x="120" y="92" fill="#cad8ed" font-size="17">Each pressure ratio is an analyst-selected boundary; each exit area follows continuity.</text>',
    ]
    for value in (660, 680, 700, 720, 740):
        y = y_thrust(value)
        lines += [f'<line x1="{x0}" y1="{y:.1f}" x2="{x1}" y2="{y:.1f}" stroke="#344968"/>',
                  f'<text x="{x0-14}" y="{y+5:.1f}" text-anchor="end" fill="#eaf1ff" font-size="15">{value}</text>']
    for value in (-40, -20, 0, 20, 40):
        y = y_pressure(value)
        stroke = "#94a6c4" if value == 0 else "#344968"
        lines += [f'<line x1="{x0}" y1="{y:.1f}" x2="{x1}" y2="{y:.1f}" stroke="{stroke}"/>',
                  f'<text x="{x0-14}" y="{y+5:.1f}" text-anchor="end" fill="#eaf1ff" font-size="15">{value}</text>']
    for q in Q_VALUES:
        x = px(q)
        lines += [f'<line x1="{x:.1f}" y1="{top0}" x2="{x:.1f}" y2="{bot1}" stroke="#263b5b"/>',
                  f'<text x="{x:.1f}" y="{bot1+28}" text-anchor="middle" fill="#eaf1ff" font-size="15">{q:g}</text>']
    lines += [f'<text x="28" y="{(top0+top1)/2}" transform="rotate(-90 28 {(top0+top1)/2})" text-anchor="middle" fill="#fff" font-size="16">Specific thrust contribution (N·s/kg-air)</text>',
              f'<text x="28" y="{(bot0+bot1)/2}" transform="rotate(-90 28 {(bot0+bot1)/2})" text-anchor="middle" fill="#fff" font-size="16">Pressure contribution (N·s/kg-air)</text>',
              f'<text x="{(x0+x1)/2}" y="{bot1+62}" text-anchor="middle" fill="#fff" font-size="17">Chosen exit static-pressure ratio q = pₑ / pₐ</text>',
              f'<text x="{x0}" y="{top0-15}" fill="#fff" font-size="18" font-weight="700">Momentum and total thrust</text>',
              f'<text x="{x0}" y="{bot0-16}" fill="#fff" font-size="18" font-weight="700">Pressure-thrust term</text>']
    series = [
        ("momentum_specific_thrust_N_s_per_kg_air", y_thrust),
        ("total_specific_thrust_N_s_per_kg_air", y_thrust),
        ("pressure_specific_thrust_N_s_per_kg_air", y_pressure),
    ]
    for key, transform in series:
        points = " ".join(f'{px(row["exit_pressure_over_ambient_q"]):.1f},{transform(row[key]):.1f}' for row in rows)
        lines.append(f'<polyline points="{points}" fill="none" stroke="{colors[key]}" stroke-width="4"/>')
        for row in rows:
            x, y = px(row["exit_pressure_over_ambient_q"]), transform(row[key])
            lines.append(f'<circle cx="{x:.1f}" cy="{y:.1f}" r="5" fill="{colors[key]}"/>')
    legend_x = 850
    for i, (key, _) in enumerate(series):
        y = 112 + i * 25
        lines.append(f'<line x1="{legend_x}" y1="{y}" x2="{legend_x+36}" y2="{y}" stroke="{colors[key]}" stroke-width="4"/><text x="{legend_x+46}" y="{y+5}" fill="#fff" font-size="15">{labels[key]}</text>')
    lines.append('</svg>')
    (HERE / "nasa-mhd-nozzle-boundary-sensitivity.svg").write_text("\n".join(lines) + "\n", encoding="utf-8")


def main() -> None:
    base, rows = build_rows()
    methods = {
        "title": "Illustrative exit-pressure boundary sensitivity for a conditional NASA/TP-2000-210387 reconstruction",
        "status": "Mathematical exit-state sensitivity only; not a fixed-geometry nozzle prediction, author-code reproduction, or source-validated performance result.",
        "source": {
            "record": "https://ntrs.nasa.gov/citations/20000095934",
            "pdf": "https://ntrs.nasa.gov/api/citations/20000095934/downloads/20000095934.pdf",
            "nozzle_efficiency_equations": "NASA Eqs. 54–56, PDF p. 22 (printed p. 16)",
            "exit_state_and_thrust_equations": "NASA Eqs. 57–64, PDF pp. 23–24 (printed pp. 17–18)",
        },
        "reference_case": {
            "flight_mach": 6.0,
            "generator_extraction_ratio": 0.25,
            "source_model_case": "Figure 2 burner-cap case, not NASA author code",
            "gamma": GAMMA,
            "R_J_per_kg_K": R_AIR_J_PER_KG_K,
            "source_model_p0_exit_over_ambient": base["p0e_over_pa"],
            "source_model_T0_exit_K": base["t06_K"],
            "fuel_air_ratio": base["fuel_air_ratio"],
            "freestream_speed_m_s": base["freestream_speed_m_s"],
            "source_model_nozzle_total_pressure_ratio": 0.98,
            "source_model_accelerator_exit_temperature_K": base["t06_K"],
            "reported_accelerator_temperature_limit_K": 3000.0,
            "accelerator_temperature_cap_exceeded": base["t06_K"] > 3000.0,
        },
        "sweep": {
            "exit_pressure_ratio_q_values": list(Q_VALUES),
            "selection": "Illustrative analyst-selected pressure boundaries, not transcribed from NASA Figure 2 and not fitted to its curve.",
            "held_fixed": "All upstream conditional cycle outputs, including total exit pressure, total exit temperature, fuel ratio and freestream state.",
            "varying_boundary": "Exit static pressure p_e / ambient static pressure p_a.",
            "geometry_treatment": "Exit area per inlet-air mass flow is derived separately at each q from continuity, assuming all air and fuel cross the exit plane. Therefore points do not share a fixed exit area or a fixed-geometry nozzle.",
        },
        "equations": {
            "exit_mach": "M_e = sqrt(2/(gamma-1) * ((p0_e/p_e)^((gamma-1)/gamma) - 1))",
            "exit_temperature": "T_e = T0_e / (1 + (gamma-1)*M_e^2/2)",
            "exit_speed": "u_e = M_e * sqrt(gamma*R*T_e)",
            "continuity_area_per_air_flow": "A_e/mdot_air = (1+f)*R*T_e/(p_e*u_e)",
            "momentum_thrust_per_air_flow": "(1+f)*u_e-u_a",
            "pressure_thrust_per_air_flow": "(A_e/mdot_air)*(p_e-p_a) = (1+f)*R*T_e/u_e*(1-1/q)",
            "total_specific_thrust": "momentum contribution + pressure contribution",
            "unit_check": "Each thrust-per-mass-flow term has units m/s = N*s/kg-air.",
        },
        "unavailable_source_inputs": [
            "The Figure 2 discussion gives nozzle total-pressure ratio pi_n=0.98 but not nozzle expansion efficiency eta_s(n), the pointwise station-6/exit static-pressure relation, or a fixed exit area/geometry.",
            "Eqs. 54–56 therefore do not select a unique physical nozzle state from the reported Figure 2 values alone.",
            "This sensitivity cannot determine which q applies to NASA's plotted curve and must not be used to tune the reconstruction to that curve.",
        ],
        "limits": [
            "Perfect-gas, quasi-one-dimensional reconstruction and single-stream continuity.",
            "All fuel is assumed to exit in the same plane; bleed, capture, spillage and installation drag are omitted.",
            "The reference model's accelerator-exit temperature is about 3418 K, above the report's separate 3000 K limit; this cap conflict remains.",
            "Near cancellation of momentum and pressure terms is a consequence of these conditional exit-state assumptions, not validation of a nozzle or vehicle.",
        ],
        "results": rows,
    }
    (HERE / "nasa-mhd-nozzle-boundary-sensitivity-methods.json").write_text(json.dumps(methods, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
    write_csv(rows)
    write_svg(rows)
    print(json.dumps({"rows": len(rows), "q_range": [Q_VALUES[0], Q_VALUES[-1]], "specific_thrust_range_N_s_per_kg_air": [min(x["total_specific_thrust_N_s_per_kg_air"] for x in rows), max(x["total_specific_thrust_N_s_per_kg_air"] for x in rows)], "fixed_geometry": False}, indent=2))


if __name__ == "__main__":
    main()
