#!/usr/bin/env python3
"""Align BoundaryFlag with sbc/ef/mag/scd/pbc index blocks in OpticsFEM JSON files."""
from __future__ import annotations

import json
import sys
from pathlib import Path

PMC, PEC, SBC, ELE, PBC, MAG, SCD = 0, 1, 2, 3, 4, 8, 9

ROOT = Path(__file__).resolve().parents[2]

OPTICS_JSON_GLOBS = [
    ROOT / "3D opticsfem-master" / "*.json",
    ROOT / "3D opticsfem-master" / "scat3D" / "Release" / "*.json",
    ROOT / "3D opticsfem-master" / "scat3D_SBC" / "Release" / "*.json",
    ROOT / "三维matlab代码" / "matlab 3D一阶散射问题" / "*.json",
    ROOT / "三维matlab代码" / "matlab 3D一阶基+散射边界条件" / "*.json",
    ROOT / "三维matlab代码" / "matlab 3D一阶基 + bele" / "*.json",
    ROOT / "三维matlab代码" / "matlab 3D一阶基+散射边界条件+单周期边界" / "PBCmesh.json",
]


def collect_pbc_domains(doc: dict) -> set[int]:
    domains: set[int] = set()
    for key in ("pbc", "pbc2"):
        if key not in doc:
            continue
        block = doc[key]
        for field in ("srcIndex", "dstIndex"):
            if field in block:
                domains.update(int(x) for x in block[field])
    return domains


def build_boundary_flag(doc: dict) -> list[int] | None:
    if "NbrBoundary" not in doc or "BoundaryFlag" not in doc:
        return None
    n = int(doc["NbrBoundary"])
    flags = [PMC] * n

    for d in collect_pbc_domains(doc):
        if 1 <= d <= n:
            flags[d - 1] = PBC

    if "sbc" in doc:
        for d in doc["sbc"]["Index"]:
            d = int(d)
            if 1 <= d <= n:
                flags[d - 1] = SBC

    if "ef" in doc:
        for d in doc["ef"]["index"]:
            d = int(d)
            if 1 <= d <= n:
                flags[d - 1] = ELE

    if "mag" in doc:
        for d in doc["mag"]["index"]:
            d = int(d)
            if 1 <= d <= n:
                flags[d - 1] = MAG

    if "scd" in doc:
        for d in doc["scd"]["index"]:
            d = int(d)
            if 1 <= d <= n:
                flags[d - 1] = SCD

    # Legacy 14-face cube: domain 6 is material interface, not an outer SBC face.
    if n == 14 and "sbc" in doc:
        sbc_set = {int(x) for x in doc["sbc"]["Index"]}
        if 6 not in sbc_set and flags[5] == SBC:
            flags[5] = PMC

    return flags


def validate(doc: dict, path: Path) -> list[str]:
    issues: list[str] = []
    if "BoundaryFlag" not in doc:
        return issues
    flags = doc["BoundaryFlag"]
    n = len(flags)

    if "sbc" in doc:
        for d in doc["sbc"]["Index"]:
            d = int(d)
            if not (1 <= d <= n):
                issues.append(f"sbc.Index {d} out of range")
            elif flags[d - 1] != SBC:
                issues.append(f"sbc.Index {d} but BoundaryFlag[{d}]={flags[d - 1]} (expected {SBC})")

    if "ef" in doc:
        for d in doc["ef"]["index"]:
            d = int(d)
            if flags[d - 1] != ELE:
                issues.append(f"ef.index {d} but BoundaryFlag[{d}]={flags[d - 1]} (expected {ELE})")

    if "mag" in doc:
        for d in doc["mag"]["index"]:
            d = int(d)
            if flags[d - 1] != MAG:
                issues.append(f"mag.index {d} flag={flags[d - 1]} (expected {MAG})")

    for d in collect_pbc_domains(doc):
        if 1 <= d <= n and flags[d - 1] != PBC:
            issues.append(f"pbc domain {d} but BoundaryFlag[{d}]={flags[d - 1]} (expected {PBC})")

    return issues


def main() -> int:
    write = "--write" in sys.argv
    paths: list[Path] = []
    for pattern in OPTICS_JSON_GLOBS:
        if pattern.parent.exists():
            paths.extend(sorted(pattern.parent.glob(pattern.name)))

    changed = 0
    for path in paths:
        if path.name in ("CMakeSettings.json",):
            continue
        try:
            doc = json.loads(path.read_text(encoding="utf-8"))
        except (json.JSONDecodeError, OSError):
            continue
        if "FemType" not in doc:
            continue

        new_flags = build_boundary_flag(doc)
        if new_flags is None:
            continue

        old_flags = doc.get("BoundaryFlag")
        issues_before = validate(doc, path) if old_flags else ["missing BoundaryFlag"]

        if old_flags == new_flags and not issues_before:
            print(f"OK  {path.relative_to(ROOT)}")
            continue

        print(f"FIX {path.relative_to(ROOT)}")
        if old_flags != new_flags:
            print(f"    old: {old_flags}")
            print(f"    new: {new_flags}")
        for msg in issues_before:
            print(f"    was: {msg}")

        if write:
            doc["BoundaryFlag"] = new_flags
            path.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
            changed += 1

    if write:
        print(f"\nUpdated {changed} file(s).")
    else:
        print("\nDry run. Re-run with --write to apply.")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
