import os
from pathlib import Path
from typing import Optional, Tuple

import torch
import yaml


def load_config(path: str) -> dict:
    with open(path, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)


def save_checkpoint(model, optimizer: torch.optim.Optimizer, iteration: int, path: str):
    os.makedirs(os.path.dirname(path) or ".", exist_ok=True)
    torch.save(
        {
            "iteration": iteration,
            "model_state_dict": model.state_dict(),
            "optimizer_state_dict": optimizer.state_dict(),
        },
        path,
    )
    print(f"[Checkpoint] saved → {path}")


def load_checkpoint(model, path: str, device=None) -> int:
    ckpt = torch.load(path, map_location=device or "cpu")
    model.load_state_dict(ckpt["model_state_dict"], strict=False)
    if "optimizer_state_dict" in ckpt and hasattr(model, "optimizer"):
        try:
            model.optimizer.load_state_dict(ckpt["optimizer_state_dict"])
        except Exception:
            pass
    it = ckpt.get("iteration", 0)
    print(f"[Checkpoint] loaded ← {path} (iter {it})")
    return it


def setup_helmholtz_config(config: dict, k_test=None, center=None, radius=None, eps_test=None) -> float:
    """Lock scatterer/helmholtz config for test/viz. Returns wave number k."""
    hc = config.setdefault("environment", {}).setdefault("mesh_refinement", {}).setdefault("fem", {}).setdefault("helmholtz", {})
    sc = hc.setdefault("scatterer", {})
    sc["mode"] = "fixed"
    if center is not None:
        sc["cx"], sc["cy"] = center[0], center[1]
    if radius is not None:
        sc["radius"] = radius
    if eps_test is not None:
        sc["eps_r"] = eps_test
    if k_test is not None:
        hc["wave_number_mode"] = "fixed"
        hc["wave_number"] = k_test
    return hc.get("wave_number", 6.0)


def parse_center(center_str: Optional[str]) -> Optional[Tuple[float, float]]:
    if center_str is None:
        return None
    parts = center_str.split(",")
    if len(parts) != 2:
        raise ValueError(f"Invalid --center format (expected 'cx,cy'): {center_str}")
    return (float(parts[0].strip()), float(parts[1].strip()))
