# Copyright (C) 2026 Wilhelmus Dingenus de Pooter
# SPDX-License-Identifier: GPL-3.0-or-later
"""
s101/grouping.py
----------------
S-101 layer-overview grouping helpers, pure-Python (no QGIS/GDAL):

  * scale band  — S-101's equivalent of the S-57 usage band, derived from the
    NW-Europe FNC (101+producer+band, e.g. 101BE00**4**_… → band 4). Verified
    against the cells' CATALOG.XML optimumDisplayScale (band 3≈1:180k, 4≈1:45k,
    5≈1:12k, 6≈1:4k).
  * thematic group — the SAME themes the S-57 object-layer tree uses
    (enc_layers.xml groups), so S-101 feature types fold into "Skin of the Earth",
    "Navigation", "Lights & Signals", … exactly like S-57. The mapping leans on
    the fact that S-101 was remodelled FROM S-57 (DepthArea↔DEPARE,
    Coastline↔COALNE, LightAllAround↔LIGHTS, …); S-100 meta/information types
    (DataCoverage, QualityOfBathymetricData, …) go to "Meta & Quality".

This drives the dock's Object-layers tree only; it is editable taxonomy, not
encoding — adjust S101_THEME freely.
"""
import os
import re

# Theme display order — mirrors enc_layers.xml group order, plus a Meta bucket
# for S-100 information/quality types and an Other catch-all.
THEME_ORDER = [
    "Skin of the Earth",
    "Coastline & Shoreline",
    "Navigation",
    "Lights & Signals",
    "Buoys & Beacons",
    "Ports & Harbours",
    "Obstructions & Hazards",
    "Regulated & Restricted Areas",
    "Topography & Land Features",
    "Tides & Currents",
    "Meta & Quality",
    "Other",
]

# Short aliases for the table below.
_SKIN = "Skin of the Earth"
_COAST = "Coastline & Shoreline"
_NAV = "Navigation"
_LIGHT = "Lights & Signals"
_BUOY = "Buoys & Beacons"
_PORT = "Ports & Harbours"
_OBSTR = "Obstructions & Hazards"
_AREA = "Regulated & Restricted Areas"
_TOPO = "Topography & Land Features"
_TIDAL = "Tides & Currents"
_META = "Metadata & Coverage"   # exact S-57 group name, for full UX parity

# S-101 feature type -> S-57-equivalent theme (the same themes the S-57 tree uses).
S101_THEME = {
    "AdministrationArea": _AREA, "AirportAirfield": _TOPO, "AnchorBerth": _PORT,
    "AnchorageArea": _PORT, "Berth": _PORT, "Bollard": _PORT, "Bridge": _TOPO,
    "Building": _TOPO, "BuiltUpArea": _TOPO, "CableOverhead": _OBSTR,
    "CableSubmarine": _OBSTR, "Canal": _COAST, "CardinalBeacon": _BUOY,
    "CardinalBuoy": _BUOY, "CautionArea": _AREA, "Checkpoint": _NAV,
    "CoastGuardStation": _LIGHT, "Coastline": _COAST, "ContiguousZone": _AREA,
    "ContinentalShelfArea": _AREA, "Crane": _PORT, "DataCoverage": _META,
    "Daymark": _LIGHT, "DeepWaterRoutePart": _NAV, "DepthArea": _SKIN,
    "DepthContour": _SKIN, "DistanceMark": _PORT, "DockArea": _PORT,
    "Dolphin": _PORT, "DredgedArea": _SKIN, "DryDock": _PORT,
    "DumpingGround": _OBSTR, "Dyke": _TOPO, "EmergencyWreckMarkingBuoy": _BUOY,
    "ExclusiveEconomicZone": _AREA, "Fairway": _NAV, "FerryRoute": _NAV,
    "FloatingDock": _PORT, "FogSignal": _LIGHT, "FoulGround": _OBSTR,
    "Gate": _PORT, "HarbourAreaAdministrative": _PORT, "HarbourFacility": _PORT,
    "InformationArea": _AREA, "Lake": _COAST, "LandArea": _SKIN,
    "LandRegion": _TOPO, "Landmark": _LIGHT, "LateralBuoy": _BUOY,
    "LightAllAround": _LIGHT, "LightSectored": _LIGHT, "LockBasin": _PORT,
    "MagneticVariation": _META, "MilitaryPracticeArea": _AREA,
    "MooringBuoy": _BUOY, "NavigationLine": _NAV,
    "LateralBeacon": _BUOY, "SafeWaterBeacon": _BUOY,
    "IsolatedDangerBeacon": _BUOY, "IsolatedDangerBuoy": _BUOY,
    "InstallationBuoy": _BUOY, "CableArea": _OBSTR,
    "NavigationalSystemOfMarks": _META, "Obstruction": _OBSTR,
    "OffshorePlatform": _OBSTR, "OffshoreProductionArea": _OBSTR,
    "OilBarrier": _OBSTR, "Pile": _PORT, "PilotBoardingPlace": _PORT,
    "PipelineOverhead": _OBSTR, "PipelineSubmarineOnLand": _OBSTR,
    "Pontoon": _TOPO, "PrecautionaryArea": _NAV, "ProductionStorageArea": _OBSTR,
    "PylonBridgeSupport": _TOPO, "QualityOfBathymetricData": _META,
    "RadarStation": _LIGHT, "RadarTransponderBeacon": _LIGHT,
    "RadioCallingInPoint": _NAV, "Railway": _TOPO, "RangeSystem": _NAV,
    "RecommendedTrack": _NAV, "RecommendedTrafficLanePart": _NAV,
    "RescueStation": _LIGHT, "RestrictedArea": _AREA, "Road": _TOPO,
    "SafeWaterBuoy": _BUOY, "SeaAreaNamedWaterArea": _COAST, "SeabedArea": _OBSTR,
    "SeparationZoneOrLine": _NAV, "ShorelineConstruction": _COAST,
    "SignalStationTraffic": _LIGHT, "SignalStationWarning": _LIGHT,
    "SiloTank": _TOPO, "SlopingGround": _TOPO, "Sounding": _SKIN,
    "SoundingDatum": _META, "SpanFixed": _TOPO, "SpanOpening": _TOPO,
    "SpecialPurposeGeneralBeacon": _BUOY, "SpecialPurposeGeneralBuoy": _BUOY,
    "StructureOverNavigableWater": _TOPO, "SubmarinePipelineArea": _OBSTR,
    "TerritorialSeaArea": _AREA, "TidalStreamPanelData": _TIDAL,
    "TrafficSeparationScheme": _NAV, "TrafficSeparationSchemeBoundary": _NAV,
    "TrafficSeparationSchemeLanePart": _NAV, "Tunnel": _TOPO,
    "TwoWayRoutePart": _NAV, "UnsurveyedArea": _SKIN, "Vegetation": _TOPO,
    "VerticalDatumOfData": _META, "VesselTrafficServiceArea": _NAV,
    "VirtualAISAidToNavigation": _BUOY, "WindTurbine": _OBSTR, "Wreck": _OBSTR,
}


def s101_theme(feature_type):
    """Thematic group for an S-101 feature type (one of THEME_ORDER); 'Other' if
    the type is not in the table."""
    return S101_THEME.get(feature_type, "Other")


# ── Aids-to-Navigation taxonomy (for the S-101 "Hybrid layers" AtoN merge) ──
# Mirror of the S-57 buoy/beacon family. The STRUCTURES are the mark bodies that
# get one merged "Aids to Navigation" symbol; their EQUIPMENT (lights, fog
# signals, topmarks, daymarks, racons, retro-reflectors) is folded onto the body
# via the S-101 StructureEquipment association (FASC) rather than the S-57 path's
# position/LNAM co-location heuristic. Names are S-101 feature-catalogue classes;
# any not present in a given cell simply don't match.
S101_ATON_STRUCTURES = {
    "LateralBuoy", "CardinalBuoy", "IsolatedDangerBuoy", "SafeWaterBuoy",
    "SpecialPurposeGeneralBuoy", "InstallationBuoy", "EmergencyWreckMarkingBuoy",
    "MooringBuoy",
    "LateralBeacon", "CardinalBeacon", "IsolatedDangerBeacon", "SafeWaterBeacon",
    "SpecialPurposeGeneralBeacon",
}

S101_ATON_EQUIPMENT = {
    "LightAllAround", "LightSectored", "LightFloodlit", "LightDirectional",
    "FogSignal", "Daymark", "Topmark", "RadarTransponderBeacon",
    "RetroReflector",
}


def is_s101_aton_structure(feature_type):
    """True if the S-101 feature type is an AtoN mark body (buoy/beacon) that the
    AtoN merge represents as one consolidated symbol."""
    return feature_type in S101_ATON_STRUCTURES


# NW-Europe S-101 FNC: 101 + 2-letter producer + zero-padded band, e.g.
# 101BE004_514N028E -> band 4. (Optional ".000" extension tolerated.)
_BAND_RE = re.compile(r"^101[A-Za-z]{2}(?P<band>\d+)")


def s101_scale_band(name):
    """Scale band (int) from an S-101 dataset name/FNC, or None if it doesn't
    match. The band is the digit group after the 2-letter producer code."""
    m = _BAND_RE.match(os.path.splitext(os.path.basename(str(name)))[0])
    return int(m.group("band")) if m else None


def s101_scale_band_label(band):
    """Human label for a scale band node, e.g. 'Scale band 4'. Unknown -> a
    generic '(scale band ?)'."""
    return f"Scale band {band}" if band is not None else "(scale band ?)"
