"""Task 01 - Laboratory configuration.

Writes ./out/config/lab_config.yaml, the authoritative machine-readable
laboratory model (volumes, deposition matrix, physical-model coefficients,
random seed) that all downstream tasks read, plus the ./out/.done/01.ok
completion marker. Spec: docs/specs/01_lab_configuration.md.
"""

from __future__ import annotations

import logging
import sys
from pathlib import Path

import yaml

from common.pipeline import parse_task_args, remove_stale_marker, write_marker

logger = logging.getLogger(__name__)

TASK_ID = "01"

# Transcription of docs/specs/01_lab_configuration.md. This literal is the
# ONLY sanctioned home of these values in code; every other task reads the
# YAML this script writes (docs/rules/code-config-yaml.md).
LAB_CONFIG: dict = {
	"project": "LabDataStorageEvaluation",
	"description": "Simulated tribology laboratory (Sandia Pt-Au LDRD context)",
	"spec": "docs/specs/01_lab_configuration.md",
	"seed": 20260711,
	"material_system": {
		"substrate": {"material": "Ti-6Al-4V", "dimensions_mm": [10, 10, 3]},
		"adhesion_layer": {"material": "Cr", "process": "sputtered"},
		"coating": {
			"material": "Pt-Au",
			"deposition": "composition gradient across each wafer",
			"thickness_um_range": [0.3, 1.1],
		},
	},
	"instruments": [
		{
			"instrument_id": "rapid",
			"role": "friction",
			"name": "RAPID custom high-throughput parallelized 6-probe tribometer",
			"data_produced": "COF vs cycle per track; test conditions",
		},
		{
			"instrument_id": "ti980",
			"role": "nanoindentation",
			"name": "Bruker TI980 TriboIndenter",
			"data_produced": "hardness, reduced modulus (25 indents per coupon)",
		},
		{
			"instrument_id": "m4_tornado",
			"role": "composition",
			"name": "Bruker M4 Tornado micro-XRF",
			"data_produced": "Pt/Au wt% map per coupon (20x20 grid)",
		},
		{
			"instrument_id": "pvd200",
			"role": "deposition",
			"name": "Kurt J. Lesker PVD 200 sputter-down",
			"data_produced": "batch deposition parameters",
		},
		{
			"instrument_id": "afm",
			"role": "surface_roughness",
			"name": "AFM",
			"data_produced": "topography image metadata + Ra/Rq per coupon",
		},
		{
			"instrument_id": "profilometer",
			"role": "film_thickness",
			"name": "Optical profilometry",
			"data_produced": "thickness map per coupon (10x10 grid)",
		},
		{
			"instrument_id": "simtra",
			"role": "simulation",
			"name": "SIMTRA sputter transport Monte-Carlo",
			"data_produced": "deposition atom-energy / composition profiles per deposition run",
		},
	],
	"deposition_matrix": [
		{
			"batch_code": "B721",
			"pt_gun_tilt_deg": 20,
			"au_gun_tilt_deg": 0,
			"pt_power_W": 150,
			"au_power_W": 50,
			"pt_discharge_V": 432,
			"au_discharge_V": 311,
		},
		{
			"batch_code": "B722",
			"pt_gun_tilt_deg": 20,
			"au_gun_tilt_deg": 20,
			"pt_power_W": 100,
			"au_power_W": 100,
			"pt_discharge_V": 399,
			"au_discharge_V": 352,
		},
		{
			"batch_code": "B723",
			"pt_gun_tilt_deg": 20,
			"au_gun_tilt_deg": 20,
			"pt_power_W": 150,
			"au_power_W": 50,
			"pt_discharge_V": 430,
			"au_discharge_V": 311,
		},
		{
			"batch_code": "B724",
			"pt_gun_tilt_deg": 0,
			"au_gun_tilt_deg": 20,
			"pt_power_W": 50,
			"au_power_W": 150,
			"pt_discharge_V": 376,
			"au_discharge_V": 340,
		},
	],
	"hierarchy": {
		"batches": 4,
		"wafers_per_batch": 3,
		"coupon_grid_per_wafer": [7, 7],
		"coupons_per_wafer": 49,
	},
	"friction_assignment": {
		"friction_coupons_total": 480,
		"reserve_coupons_total": 108,
		"runs": 4,
		"coupons_per_run": 120,
		"plates_per_run": 5,
		"probes_per_plate": 6,
		"coupons_per_probe_square": 4,
		"tracks_per_friction_coupon": 3,
		"counterfaces_per_holder": 3,
		"fresh_counterface_per_track": True,
		# QA witness pattern: rows 1/4/7 x cols 1/4/7 of the 7x7 grid stay
		# unworn (reserve), sampling low/mid/high Au columns on every wafer.
		"reserve_grid_positions": [1, 4, 7, 22, 25, 28, 43, 46, 49],
	},
	"volumes": {
		"coupons_total": 588,
		"tracks_total": 1440,
		"cycles_per_track": 1000,
		"cycle_rows_total": 1440000,
		"loop_every_n_cycles": 100,
		"loops_per_track": 10,
		"loop_points_per_loop": 200,
		"loop_points_total": 2880000,
		"xrf_grid": [20, 20],
		"xrf_points_per_coupon": 400,
		"xrf_points_total": 235200,
		"profilometry_grid": [10, 10],
		"profilometry_points_per_coupon": 100,
		"nanoindentation_indents_per_coupon": 25,
		"simtra_rows_per_batch": 1000,
	},
	"runs": [
		{"run_code": "R1", "batch_code": "B721", "environment": "lab_air", "date": "2026-04-06", "operator": "A. Reyes"},
		{"run_code": "R2", "batch_code": "B722", "environment": "lab_air", "date": "2026-04-13", "operator": "K. Patel"},
		{"run_code": "R3", "batch_code": "B723", "environment": "dry_n2", "date": "2026-04-20", "operator": "A. Reyes"},
		{"run_code": "R4", "batch_code": "B724", "environment": "dry_n2", "date": "2026-04-27", "operator": "M. Novak"},
	],
	"schedule": {
		"deposition_start_date": "2026-03-02",
		"batch_interval_days": 7,
		"characterization_lag_days": 3,
		"run_start_time": "09:00:00",
		"track_interval_s": 2100,
	},
	"test_conditions": {
		"normal_load_mN": 100,
		"stroke_mm": 1.0,
		"speed_mm_s": 1.0,
		"counterface": {"material": "ruby (Al2O3)", "diameter_mm": 3.175},
		"rh_pct": {"lab_air": 45, "dry_n2": 2},
		"temperature_C": 23,
		"temperature_tolerance_C": 1,
	},
	"physical_models": {
		"au_gradient": {
			"description": "linear Au wt% gradient along wafer x, batch-dependent center",
			"batch_center_wtpct": {"B721": 8, "B722": 25, "B723": 10, "B724": 60},
			"wafer_span_wtpct": 5,
			"xrf_noise_sd_wtpct": 0.3,
		},
		"cof_run_in": {
			"formula": "cof(c) = cof_ss + (cof_0 - cof_ss) * exp(-c / tau) + N(0, sigma)",
			"cof_0_range": [0.35, 0.5],
			"tau_cycles_range": [100, 400],
			"noise_sd": 0.01,
		},
		"cof_steady_state": {
			"formula": "cof_ss = intercept + slope_per_au_wtpct * au_wtpct_mean, clamped to >= floor",
			"lab_air": {"intercept": 0.32, "slope_per_au_wtpct": -0.0015},
			"dry_n2": {"intercept": 0.24, "slope_per_au_wtpct": -0.0012},
			"floor": 0.12,
		},
		"hardness_GPa": {
			"formula": "H = intercept + slope_per_au_wtpct * au_wtpct_mean + N(0, noise_sd)",
			"intercept": 8.5,
			"slope_per_au_wtpct": -0.05,
			"noise_sd": 0.25,
		},
		"reduced_modulus_GPa": {
			"formula": "Er = intercept + slope_per_au_wtpct * au_wtpct_mean + N(0, noise_sd)",
			"intercept": 190,
			"slope_per_au_wtpct": -0.6,
			"noise_sd": 4,
		},
		"roughness_ra_nm": {
			"distribution": "lognormal",
			"median_nm": 5,
			"sigma_log": 0.3,
			"rq_over_ra": 1.25,
		},
		"thickness_um": {
			"profile": "radial parabolic across wafer plus noise",
			"center_um": 1.05,
			"edge_um": 0.35,
			"noise_sd_um": 0.02,
			"range_um": [0.3, 1.1],
		},
		"wear_archard": {
			# Spec 01 fixes only the model shape (V = k*F*s, k depends on Au%
			# and environment); these coefficients are project defaults chosen
			# to yield Sandia-plausible low-wear volumes at 100 mN / 2 m.
			"formula": "V = k * F * s; k = k0 * (1 + slope_per_au_wtpct * au_wtpct_mean), lognormal noise, clamped to >= k_floor",
			"k0_mm3_per_N_m": {"lab_air": 1.5e-07, "dry_n2": 6.0e-08},
			"slope_per_au_wtpct": {"lab_air": -0.008, "dry_n2": -0.010},
			"k_floor_mm3_per_N_m": 1.0e-09,
			"noise_sigma_log": 0.15,
		},
		"friction_loop": {
			"force_noise_sd_mN": 0.3,
		},
		"nanoindentation": {
			"max_load_mN": 10,
			"indent_grid": [5, 5],
			"grid_pitch_um": 20,
		},
		"simtra": {
			"angle_deg_range": [0, 90],
			"energy_eV_max": 50,
			"surface_binding_energy_eV": {"pt": 5.84, "au": 3.81},
			"flux_per_W": 0.02,
		},
	},
}

YAML_HEADER = (
	"# Generated by make_lab_config.py (task 01) from docs/specs/01_lab_configuration.md.\n"
	"# Do not hand-edit: regenerate via `python make_lab_config.py`.\n"
)


def validate_config(cfg: dict) -> None:
	"""Assert the arithmetic identities between declared volumes (spec 01)."""
	h = cfg["hierarchy"]
	f = cfg["friction_assignment"]
	v = cfg["volumes"]
	checks = [
		("coupons_total", v["coupons_total"], h["batches"] * h["wafers_per_batch"] * h["coupons_per_wafer"]),
		("coupons_per_wafer", h["coupons_per_wafer"], h["coupon_grid_per_wafer"][0] * h["coupon_grid_per_wafer"][1]),
		("friction+reserve", v["coupons_total"], f["friction_coupons_total"] + f["reserve_coupons_total"]),
		("friction_coupons_total", f["friction_coupons_total"], f["runs"] * f["coupons_per_run"]),
		("coupons_per_run", f["coupons_per_run"], f["plates_per_run"] * f["probes_per_plate"] * f["coupons_per_probe_square"]),
		("tracks_total", v["tracks_total"], f["friction_coupons_total"] * f["tracks_per_friction_coupon"]),
		("cycle_rows_total", v["cycle_rows_total"], v["tracks_total"] * v["cycles_per_track"]),
		("loops_per_track", v["loops_per_track"], v["cycles_per_track"] // v["loop_every_n_cycles"]),
		("loop_points_total", v["loop_points_total"], v["tracks_total"] * v["loops_per_track"] * v["loop_points_per_loop"]),
		("xrf_points_per_coupon", v["xrf_points_per_coupon"], v["xrf_grid"][0] * v["xrf_grid"][1]),
		("xrf_points_total", v["xrf_points_total"], v["coupons_total"] * v["xrf_points_per_coupon"]),
		("profilometry_points_per_coupon", v["profilometry_points_per_coupon"], v["profilometry_grid"][0] * v["profilometry_grid"][1]),
		("runs_count", f["runs"], len(cfg["runs"])),
		("batches_count", h["batches"], len(cfg["deposition_matrix"])),
		("reserve_total", f["reserve_coupons_total"], len(f["reserve_grid_positions"]) * h["wafers_per_batch"] * h["batches"]),
		("friction_per_batch", f["coupons_per_run"], (h["coupons_per_wafer"] - len(f["reserve_grid_positions"])) * h["wafers_per_batch"]),
	]
	for name, declared, derived in checks:
		if declared != derived:
			raise ValueError(f"volume identity failed: {name}: declared {declared} != derived {derived}")
	run_batches = [r["batch_code"] for r in cfg["runs"]]
	matrix_batches = [b["batch_code"] for b in cfg["deposition_matrix"]]
	if sorted(run_batches) != sorted(matrix_batches):
		raise ValueError(f"run-batch mapping mismatch: runs cover {run_batches}, matrix has {matrix_batches}")
	logger.info("all %d volume identities hold", len(checks))


def write_yaml(cfg: dict, config_path: Path) -> int:
	config_path.parent.mkdir(parents=True, exist_ok=True)
	body = yaml.safe_dump(cfg, sort_keys=False, allow_unicode=False, width=100)
	text = YAML_HEADER + body
	with open(config_path, "w", encoding="utf-8", newline="\n") as fh:
		fh.write(text)
	reloaded = yaml.safe_load(text)
	if reloaded != cfg:
		raise RuntimeError(f"YAML round-trip mismatch for {config_path}")
	return len(text.encode("utf-8"))


def main() -> int:
	args = parse_task_args("Task 01: write out/config/lab_config.yaml")
	out_root: Path = args.out_root
	config_path = out_root / "config" / "lab_config.yaml"
	try:
		remove_stale_marker(out_root, TASK_ID)
		validate_config(LAB_CONFIG)
		config_bytes = write_yaml(LAB_CONFIG, config_path)
		v = LAB_CONFIG["volumes"]
		f = LAB_CONFIG["friction_assignment"]
		h = LAB_CONFIG["hierarchy"]
		marker_path = write_marker(out_root, TASK_ID, {
			"config_file": config_path.as_posix(),
			"config_bytes": config_bytes,
			"seed": LAB_CONFIG["seed"],
			"batches": h["batches"],
			"wafers": h["batches"] * h["wafers_per_batch"],
			"coupons": v["coupons_total"],
			"friction_coupons": f["friction_coupons_total"],
			"tracks": v["tracks_total"],
			"cycle_rows": v["cycle_rows_total"],
			"loop_points": v["loop_points_total"],
			"xrf_points": v["xrf_points_total"],
		})
	except Exception:
		logger.critical("task %s failed", TASK_ID, exc_info=True)
		return 1

	v = LAB_CONFIG["volumes"]
	print(f"task 01 ok: {config_path} ({config_bytes} bytes)")
	print(
		f"volumes: batches=4 wafers=12 coupons={v['coupons_total']} tracks={v['tracks_total']} "
		f"cycle_rows={v['cycle_rows_total']} loop_points={v['loop_points_total']} xrf_points={v['xrf_points_total']}"
	)
	print(f"marker: {marker_path}")
	return 0


if __name__ == "__main__":
	sys.exit(main())
