"""Shared core detection across scenes.

Finds features that are active across ALL scenes in a group (e.g. all Karman Re,
all Illusion diameters) and identifies the cross-scene shared core.

Usage:
    python compare/shared_core.py --sindy-results sindy/karman/sindy_results.json \\
        --scenes karman_re50 karman_re100 karman_re200 karman_re400
    python compare/shared_core.py \\
        --sindy-results sindy/results.json \\
        --scenes karman_re100 illusion_1L vortex_lamb steady
"""
from __future__ import annotations

import argparse
import json
import os
import sys
from typing import Dict, List, Tuple

import numpy as np

_REPO = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
if _REPO not in sys.path:
    sys.path.insert(0, _REPO)
_SRC = os.path.join(_REPO, "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

from SR_analysis.utils.sindy_fitter import get_active_support

RELATIVE_THRESHOLD = 0.02


def feat_group(name: str) -> str:
    if name == "bias":
        return "bias"
    if name in ("u_m", "u_a", "u_c", "v_a", "sin_ua", "cos_ua"):
        return "sensor"
    if name.startswith("Cd") or name.startswith("Cl"):
        return "force"
    if "lag1" in name:
        return "memory_lag"
    if name.startswith("da"):
        return "memory_delta"
    if name == "mu" or name.startswith("mu_"):
        return "mu_mod"
    return "other"


def detect_core(scene_data: Dict[str, dict], channels: List[str],
                threshold: float) -> dict:
    """Find features active in ALL scenes for each channel."""
    scene_names = list(scene_data.keys())
    results = {}

    for ch_name in channels:
        fn_key = f"feature_names_{'front' if ch_name == 'front' else 'rear'}"

        # Collect active sets per scene
        active_per_scene = {}
        for sn in scene_names:
            fn = scene_data[sn][fn_key]
            coef = scene_data[sn][ch_name]["best_coef"]
            active = get_active_support(np.array(coef, dtype=np.float64)[:len(fn)],
                                        fn, threshold)
            active_per_scene[sn] = set(active.keys())

        # Intersection = shared core
        core_keys = set.intersection(*active_per_scene.values()) if active_per_scene else set()

        # Union for scene-specific detection
        all_keys = set.union(*active_per_scene.values()) if active_per_scene else set()
        scene_specific = {}
        for sn in scene_names:
            others = set.union(*[v for k, v in active_per_scene.items() if k != sn])
            diff = active_per_scene[sn] - others
            if diff:
                scene_specific[sn] = sorted(diff)

        # Coef means for core features
        core_coefs = {}
        for k in sorted(core_keys):
            vals = []
            for sn in scene_names:
                fn = scene_data[sn][fn_key]
                coef = scene_data[sn][ch_name]["best_coef"]
                if k in fn:
                    idx = fn.index(k)
                    vals.append(float(coef[idx]))
            core_coefs[k] = {
                "mean": float(np.mean(vals)),
                "std": float(np.std(vals)),
                "per_scene": {sn: vals[i] for i, sn in enumerate(scene_names)},
            }

        results[ch_name] = {
            "n_scenes": len(scene_names),
            "n_core": len(core_keys),
            "core_features": {k: {"group": feat_group(k), "coef": v}
                              for k, v in core_coefs.items()},
            "scene_specific": {sn: sorted(v) for sn, v in scene_specific.items()},
        }

    return results


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--sindy-results", type=str, required=True)
    ap.add_argument("--scenes", type=str, nargs="+", required=True)
    ap.add_argument("--threshold", type=float, default=RELATIVE_THRESHOLD)
    ap.add_argument("--out", type=str, default=None)
    args = ap.parse_args()

    with open(args.sindy_results) as f:
        all_data = json.load(f)

    per = all_data.get("per_scene", {})
    scene_data = {sn: per[sn] for sn in args.scenes if sn in per}
    if len(scene_data) < 2:
        print(f"Need >=2 scenes. Found: {list(scene_data.keys())}")
        return 1

    print(f"Shared Core Detection: {len(scene_data)} scenes")
    for sn in scene_data:
        print(f"  {sn}")
    print(f"  threshold={args.threshold}")

    results = detect_core(scene_data, ["front", "top", "bottom"], args.threshold)

    for ch_name, ch_data in results.items():
        print(f"\n--- {ch_name} ---")
        print(f"  Core features: {ch_data['n_core']}")
        for k, v in ch_data["core_features"].items():
            c = v["coef"]
            print(f"    {k:20s}  mean={c['mean']:+.6f}  std={c['std']:.6f}  [{v['group']}]")
        for sn, keys in ch_data["scene_specific"].items():
            if keys:
                print(f"  {sn} specific: {', '.join(keys)}")

    if args.out:
        output = {"scenes": args.scenes, "threshold": args.threshold,
                  "channels": results}
        os.makedirs(os.path.dirname(args.out), exist_ok=True)
        with open(args.out, "w") as f:
            json.dump(output, f, indent=2)
        print(f"\nSaved: {args.out}")


if __name__ == "__main__":
    main()
