#!/usr/bin/env python3
"""
Regenerate features.geojson for Bahia de Cartagena directly from the
source CSVs, bypassing the GDAL S-57 round-trip that loses LITCHR/SIGPER/
VALNMR for BOYLAT and CATCAM for BOYCAR.
"""
import csv
import json
from pathlib import Path

CSV_DIR  = Path(r"D:\Proyectos Software\QGISS57Converter\capas_ctg")
OUT_FILE = Path(r"C:\AidsMonitoring\charts\BAHÍA_DE_CARTAGENA\features.geojson")
META_FILE= Path(r"C:\AidsMonitoring\charts\BAHÍA_DE_CARTAGENA\meta.json")

# ── S-57 attribute lookup tables ──────────────────────────────────────────────
LITCHR_MAP = {
    1:"F", 2:"Fl", 3:"LFl", 4:"Q", 5:"VQ", 6:"UQ", 7:"Iso",
    8:"Oc", 9:"IQ", 10:"IVQ", 11:"IUQ", 12:"Mo", 13:"FFl",
    14:"FlLFl", 15:"OcFl", 16:"FLFl", 25:"Al.Oc", 26:"Al.LFl",
    27:"Al.Fl", 28:"Al.Grp",
}
COLOUR_MAP = {
    1:"W", 2:"K", 3:"R", 4:"G", 5:"B", 6:"Y",
    7:"Gy", 8:"Br", 9:"Amb", 10:"Vi", 11:"Or", 12:"Mg",
}

# CSV feat_type → canonical S-57 layer name
FEAT_TYPE_TO_LAYER = {
    "BOYSPEC": "BOYSPP",  # DIMAR CSV uses BOYSPEC; IHO S-57 is BOYSPP
}

# Layer → feature category
CATEGORY_MAP = {
    "BOYLAT": "buoy", "BOYCAR": "buoy", "BOYISD": "buoy",
    "BOYSPP": "buoy", "BOYSAW": "buoy",
    "BCNLAT": "beacon", "BCNCAR": "beacon",
    "LIGHTS": "light",
    "LNDMRK": "landmark",
}


def _num(v):
    if v is None or str(v).strip() == "":
        return None
    try:
        return float(v)
    except Exception:
        return None


def _int(v):
    n = _num(v)
    return int(n) if n is not None else None


def _fmt(v):
    """Format a number: drop trailing .0 for whole numbers."""
    try:
        f = float(v)
        return str(int(f)) if f == int(f) else str(f)
    except Exception:
        return str(v)


def make_light_desc(litchr, siggrp, colour, sigper, valnmr):
    """Build the compact light description string (e.g. 'Fl G 3s 3M')."""
    parts = []
    import re as _re2
    lc_int = _int(litchr)
    lc = LITCHR_MAP.get(lc_int, str(lc_int)) if lc_int is not None else ""
    sg_raw = str(siggrp or "").strip()
    sg_m = _re2.search(r'\d+', sg_raw)
    sg = int(sg_m.group()) if sg_m else None
    suffix = "+" if "+" in sg_raw else ""
    if sg is not None:
        lc = f"{lc}({sg}){suffix}"
    col_int = _int(colour)
    col_str = COLOUR_MAP.get(col_int, "") if col_int is not None else ""
    if lc:
        parts.append(f"{lc} {col_str}".strip())
    sp = _num(sigper)
    if sp is not None:
        parts.append(f"{_fmt(sp)}s")
    rng = _num(valnmr)
    if rng is not None:
        parts.append(f"{_fmt(rng)}M")
    return " ".join(parts)


def infer_catcam(siggrp, name):
    """
    Cardinal buoy direction from SIGGRP (most reliable).
      Q(9)+LFl = West, Q(6)+LFl = South, Q(3) = East, Q = North
    DIMAR name convention (fallback):
      SS/VS → S,  SN/VN → N,  SE → E,  SO → W
    """
    # Extract first integer from SIGGRP even if it looks like "(6)+" or "(9)+LFl"
    import re as _re
    sg_str = str(siggrp or "")
    m = _re.search(r'\d+', sg_str)
    sg = int(m.group()) if m else None
    if sg is not None:
        if sg == 9: return "W"
        if sg == 6: return "S"
        if sg == 3: return "E"
        return "N"          # Q without group or Q(1) = North cardinal
    n = (name or "").upper()
    if any(x in n for x in ("SUR", " SS", "(SS)", " VS", "VS ")): return "S"
    if any(x in n for x in ("ESTE", " SE", "(SE)")): return "E"
    if any(x in n for x in ("OESTE", " SO", "(SO)", " SW", "(SW)")): return "W"
    if any(x in n for x in ("NORTE", " SN", "(SN)", " VN", "VN ")): return "N"
    return "N"


def infer_catlam(colour):
    """IALA B (Americas): green=port=1, red=stbd=2."""
    c = _int(colour)
    if c == 4: return 1   # green → port
    if c == 3: return 2   # red  → starboard
    return None


def classify(layer, props):
    catlam = props.get("catlam")
    catcam = props.get("catcam")
    if layer in ("BOYLAT", "BCNLAT"):
        if catlam == 1: return "LATERAL_PORT"
        if catlam == 2: return "LATERAL_STBD"
        if catlam == 3: return "LATERAL_PREF_STBD"
        if catlam == 4: return "LATERAL_PREF_PORT"
        return "LATERAL_UNKNOWN"
    if layer in ("BOYCAR", "BCNCAR"):
        if catcam == "N": return "CARDINAL_N"
        if catcam == "E": return "CARDINAL_E"
        if catcam == "S": return "CARDINAL_S"
        if catcam == "W": return "CARDINAL_W"
        return "CARDINAL_UNKNOWN"
    if layer in ("BOYISD", "BCNISD"): return "ISOLATED_DANGER"
    if layer == "BOYSAW": return "SAFE_WATER"
    if layer in ("BOYSPP", "BOYSPEC"): return "SPECIAL"
    if layer == "LIGHTS": return "LIGHT_POINT"
    if layer == "LNDMRK": return "LANDMARK"
    if layer.startswith("BCN"): return "BEACON_GENERIC"
    if layer.startswith("BOY"): return "BUOY_GENERIC"
    return "UNKNOWN"


# ── Main loop ─────────────────────────────────────────────────────────────────
features = []
csv_files = sorted(CSV_DIR.glob("*.csv"))
print(f"Processing {len(csv_files)} CSV files from {CSV_DIR}")

for csv_file in csv_files:
    row_count = 0
    with open(csv_file, newline="", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            feat_type = (row.get("feat_type") or csv_file.stem).strip()
            layer = FEAT_TYPE_TO_LAYER.get(feat_type, feat_type)
            if not layer:
                continue

            lon = _num(row.get("lon"))
            lat = _num(row.get("lat"))
            if lon is None or lat is None:
                continue

            category = CATEGORY_MAP.get(layer, "buoy")

            litchr = row.get("LITCHR", "").strip()
            siggrp = row.get("SIGGRP", "").strip()
            colour = row.get("COLOUR", "").strip()
            sigper = row.get("SIGPER", "").strip()
            valnmr = row.get("VALNMR", "").strip()
            height = row.get("HEIGHT", "").strip()
            orient_raw = row.get("ORIENT", "").strip()
            name   = row.get("OBJNAM", "").strip()
            inform = row.get("INFORM", "").strip()

            colour_int = _int(colour)
            colours    = [colour_int] if colour_int is not None else []
            light_desc = make_light_desc(litchr, siggrp, colour, sigper, valnmr)

            props = {
                "layer":       layer,
                "category":    category,
                "name":        name   or None,
                "info":        inform or None,
                "light_desc":  light_desc or None,
                "range_nm":    _num(valnmr),
                "height_m":    _num(height),
                "colours":     colours,
                "colour_code": colour_int,
            }

            # Layer-specific S-57 attributes
            if layer in ("BOYCAR", "BCNCAR"):
                # Prefer explicit CATCAM column (1=N,2=E,3=S,4=W); fall back to inference
                catcam_raw = row.get("CATCAM", "").strip()
                catcam_int = _int(catcam_raw)
                _CATCAM_INT = {1: "N", 2: "E", 3: "S", 4: "W"}
                if catcam_int in _CATCAM_INT:
                    props["catcam"] = _CATCAM_INT[catcam_int]
                else:
                    props["catcam"] = infer_catcam(siggrp, name)
            if layer in ("BOYLAT", "BCNLAT"):
                props["catlam"] = infer_catlam(colour)

            orient_num = _num(orient_raw)
            if orient_num is not None:
                props["orient"] = orient_num

            props["aid_type"] = classify(layer, props)

            # Strip None values (but keep empty lists like colours=[])
            props = {k: v for k, v in props.items() if v is not None}

            features.append({
                "type": "Feature",
                "geometry": {"type": "Point", "coordinates": [lon, lat]},
                "properties": props,
            })
            row_count += 1

    print(f"  {csv_file.name}: {row_count} features")

# ── Write features.geojson ────────────────────────────────────────────────────
fc = {"type": "FeatureCollection", "features": features}
OUT_FILE.parent.mkdir(parents=True, exist_ok=True)
with open(OUT_FILE, "w", encoding="utf-8") as f:
    json.dump(fc, f, ensure_ascii=False, indent=2)
print(f"\nWrote {len(features)} features to {OUT_FILE}")

# ── Update meta.json ──────────────────────────────────────────────────────────
if features:
    lons = [ft["geometry"]["coordinates"][0] for ft in features]
    lats = [ft["geometry"]["coordinates"][1] for ft in features]
    bbox = [min(lons), min(lats), max(lons), max(lats)]
    try:
        meta = json.loads(META_FILE.read_text(encoding="utf-8"))
    except Exception:
        meta = {}
    meta["feature_count"] = len(features)
    meta["bbox"] = bbox
    META_FILE.write_text(json.dumps(meta), encoding="utf-8")
    print(f"Updated meta.json: {len(features)} features, bbox={bbox}")

# ── Quick QA: show light_desc for first 5 BOYLAT and all BOYCAR ──────────────
print("\n── BOYLAT sample ──")
for ft in [f for f in features if f["properties"].get("layer") == "BOYLAT"][:5]:
    p = ft["properties"]
    print(f"  {p['name']:30s}  {p.get('light_desc','(none)'):20s}  "
          f"aid_type={p['aid_type']}")

print("\n── BOYCAR (all) ──")
for ft in [f for f in features if f["properties"].get("layer") == "BOYCAR"]:
    p = ft["properties"]
    print(f"  {p['name']:35s}  {p.get('light_desc','(none)'):20s}  "
          f"catcam={p.get('catcam','?')}  aid_type={p['aid_type']}")

print("\n── LNDMRK (all) ──")
for ft in [f for f in features if f["properties"].get("layer") == "LNDMRK"]:
    p = ft["properties"]
    print(f"  {p['name']:35s}  {p.get('light_desc','(none)'):22s}  "
          f"height={p.get('height_m','?')}m  range={p.get('range_nm','?')}NM")
