"""Build a small, explicit extension of the frozen dependency-free solar solver.

The original Rust source is unchanged. The generated solver adds an optional
physical mu(0), linearly joined to the first table row. Legacy behavior and
tests remain available when the optional argument is absent.
"""
from pathlib import Path
import hashlib
import json
import subprocess

BASE=Path(__file__).resolve().parent
OUT=BASE/"outputs/dark_cmc/solver"
ORIGINAL_SHA256="2dab4dfbb87656a33e64a69855a811e69ed344ad9c053c47387b81e911057a3b"


def digest(path):
    return hashlib.sha256(Path(path).read_bytes()).hexdigest()


def build(test=False):
    original=BASE/"rust/src/main.rs"
    assert digest(original)==ORIGINAL_SHA256
    source=original.read_text()
    replacements=[
        ("    low_slope: f64,", "    low_slope: f64,\n    low_value: Option<f64>,"),
        ("            low_slope: if", "            low_value: None,\n            low_slope: if"),
        ("            low_slope: rows[0].1 / rows[0].0,", "            low_slope: rows[0].1 / rows[0].0,\n            low_value: None,"),
        ("        if x <= self.low.exp() {\n", "        if x <= self.low.exp() {\n            if let Some(value) = self.low_value {\n                return (value + (self.values[0] - value) * x / self.low.exp()).max(self.floor);\n            }\n"),
        ("    let (response, scale) = if matches!(model, Model::Table) {", "    let (mut response, scale) = if matches!(model, Model::Table) {"),
        ("        if args.len() != 11 {", "        if args.len() != 11 && args.len() != 12 {"),
        ("    let eta = ext / scale;", "    if let Some(value) = args.get(11) {\n        let mu_zero: f64 = value.parse().expect(\"Invalid physical mu(0)\");\n        assert!(mu_zero.is_finite() && mu_zero > 0.);\n        response.low_value = Some(mu_zero);\n    }\n    let eta = ext / scale;")]
    for before,after in replacements:
        assert source.count(before)==1,before
        source=source.replace(before,after)
    source+='''
#[cfg(test)]
mod physical_zero_tests {
    use super::*;
    #[test]
    fn physical_zero_core_is_continuous_and_has_positive_stiffness() {
        let mut law = Response::from_table("x,mu\\n1e-12,0.99999\\n1e-6,0.5\\n1,1\\n", 0.).unwrap();
        law.low_value = Some(1.);
        assert_eq!(law.get(0.), 1.);
        assert!((law.get(5e-13)-0.999995).abs()<1e-14);
        assert!((law.get(1e-12)-0.99999).abs()<1e-14);
        for i in 1..100 {
            let x = (i as f64)*1e-14;
            let d = 1e-17;
            let stiffness = ((x+d)*law.get(x+d)-(x-d)*law.get(x-d))/(2.*d);
            assert!(stiffness > 0.9999);
        }
    }
}
'''
    OUT.mkdir(parents=True,exist_ok=True)
    generated=OUT/"cmc_solar.rs"
    binary=OUT/"cmc-solar"
    source_hash=hashlib.sha256(source.encode()).hexdigest()
    changed=not generated.exists() or digest(generated)!=source_hash
    if changed:
        generated.write_text(source)
    if changed or not binary.exists():
        subprocess.run(["rustc","--edition=2024","-O",str(generated),"-o",str(binary)],check=True)
    checks=None
    if test:
        tests=OUT/"cmc-solar-tests"
        subprocess.run(["rustc","--edition=2024","--test",str(generated),"-o",str(tests)],check=True)
        done=subprocess.run([str(tests)],check=True,capture_output=True,text=True)
        (OUT/"tests.log").write_text(done.stdout+done.stderr)
        checks=done.stdout
        print(checks,flush=True)
    record={"original_source_sha256":ORIGINAL_SHA256,"adapter_source_sha256":digest(__file__),
            "generated_source_sha256":source_hash,"binary_sha256":digest(binary),
            "rustc":subprocess.run(["rustc","--version"],capture_output=True,text=True,check=True).stdout.strip(),
            "physical_change":"Optional mu(0) and a continuous linear core below the first table row",
            "original_source_unchanged":True}
    if checks is not None:
        record["tests_log_sha256"]=digest(OUT/"tests.log")
    (OUT/"build.json").write_text(json.dumps(record,indent=2)+"\n")
    return binary,record


if __name__=="__main__":
    build(test=True)
