"""
The system table — single source of truth for ROM identification & organization.

Each `System` ties together three namespaces that must agree:

  • the **catalog** system name (exactly as it appears in mock_dataset.json and
    in game ids, e.g. "Game Boy Color" / id prefix "GameBoyColor"),
  • the **/roms folder** the device/emulators expect (organize target +
    accepted aliases a ROM might already live under), and
  • the **ROM extensions** for that system.

From this table we derive the lookups ingest needs:

  ALL_EXTS            every scannable extension (the scan filter)
  FOLDER_TO_SYSTEM    folder basename  → canonical system name
  EXT_TO_SYSTEMS      extension        → systems that use it
  unique_system_for_ext(ext)   → the system iff the ext is unambiguous

Identification is deliberately conservative: a ROM is only auto-placed when we
are sure of its system (its folder names it, OR its extension belongs to exactly
one system, OR the catalog match resolves it). Ambiguous disc images stay put.

Folder names follow the ArkOS/dArkOS convention used on this device.
"""

from __future__ import annotations

from dataclasses import dataclass


@dataclass(frozen=True)
class System:
    name: str            # canonical — matches catalog `system` exactly
    id_prefix: str       # prefix in catalog game ids (system without spaces)
    folder: str          # primary /roms folder = organize TARGET
    folders: tuple[str, ...]   # accepted folder basenames (lowercase), incl. primary
    exts: tuple[str, ...]      # ROM extensions (lowercase, no leading dot)


# ── the 13 catalog systems ───────────────────────────────────────────
# Extensions include archive forms (zip/7z) for cartridge systems and the
# disc-image family for CD systems. Shared exts (bin/iso/cue/chd/zip…) are
# intentionally listed on every system that uses them — uniqueness is derived,
# not hand-declared, so the table stays the one place to edit.
SYSTEMS: tuple[System, ...] = (
    System("GBA", "GBA", "gba",
           ("gba",),
           ("gba", "zip", "7z")),
    System("Game Boy", "GameBoy", "gb",
           ("gb",),
           ("gb", "zip", "7z")),
    System("Game Boy Color", "GameBoyColor", "gbc",
           ("gbc",),
           ("gbc", "zip", "7z")),
    System("Game Gear", "GameGear", "gamegear",
           ("gamegear", "gg"),
           ("gg", "zip", "7z")),
    System("Genesis", "Genesis", "genesis",
           ("genesis", "megadrive", "md"),
           ("md", "gen", "smd", "bin", "zip", "7z")),
    System("Master System", "MasterSystem", "mastersystem",
           ("mastersystem", "sms"),
           ("sms", "zip", "7z")),
    System("N64", "N64", "n64",
           ("n64",),
           ("n64", "z64", "v64", "zip", "7z")),
    System("NES", "NES", "nes",
           ("nes", "famicom"),
           ("nes", "fds", "unif", "unf", "zip", "7z")),
    System("Neo Geo", "NeoGeo", "neogeo",
           ("neogeo",),
           ("neo", "zip", "7z")),
    System("PC Engine", "PCEngine", "pcengine",
           ("pcengine", "pce", "tg16", "turbografx16"),
           ("pce", "cue", "ccd", "chd", "bin", "zip", "7z")),
    System("PS1", "PS1", "psx",
           ("psx", "ps1", "playstation"),
           ("cue", "bin", "img", "mdf", "mds", "pbp", "toc", "cbn",
            "m3u", "ccd", "chd", "iso", "zip", "7z")),
    # Sega CD / Mega-CD (1992-1996). Disc-based add-on for Genesis;
    # shares the cue/bin/ccd/chd extension family with PS1 — but the
    # `segacd` FOLDER routes correctly via FOLDER_TO_SYSTEM (folder wins
    # when extensions are ambiguous between systems).
    System("Sega CD", "SegaCD", "segacd",
           ("segacd", "mega-cd", "megacd"),
           ("cue", "bin", "img", "iso", "ccd", "chd", "toc",
            "m3u", "mds", "mdf", "zip", "7z")),
    # Sega 32X (1994-1996). Genesis add-on; cartridge-based; `.32x` is
    # the canonical extension (matches No-Intro DATs and PicoDrive's
    # accepted set). Common archive containers also accepted.
    System("Sega 32X", "Sega32X", "sega32x",
           ("sega32x", "32x"),
           ("32x", "bin", "smd", "md", "zip", "7z")),
    System("PSP", "PSP", "psp",
           ("psp",),
           ("iso", "cso", "pbp", "chd")),
    System("SNES", "SNES", "snes",
           ("snes", "sfc", "superfamicom"),
           ("sfc", "smc", "swc", "fig", "bs", "zip", "7z")),
    # Java ME (mobile 2005-2012) via the freej2me core. Cards built from
    # dArkOS-RE templates carry the canonical "00 Dream Pack" + freej2me
    # runtime under /roms/j2me/. We surface JAR (the executable) and JAD
    # (the manifest descriptor) — both are valid entry points.
    System("J2ME", "J2ME", "j2me",
           ("j2me",),
           ("jar", "jad")),
)

# Disc control files that reference sibling tracks/files. Moving one of these on
# its own would split the set, so ingest never auto-moves a LOOSE one — it flags
# them for the desktop companion / manual placement instead. (Single-file disc
# images like .chd/.pbp/.cso/.iso move safely and are not in this set.)
MULTIFILE_DISC: frozenset[str] = frozenset({"cue", "ccd", "m3u", "gdi", "toc"})


# ── derived lookups (built once at import) ───────────────────────────
_BY_NAME: dict[str, System] = {s.name: s for s in SYSTEMS}
_BY_ID_PREFIX: dict[str, System] = {s.id_prefix: s for s in SYSTEMS}

FOLDER_TO_SYSTEM: dict[str, str] = {}
for _s in SYSTEMS:
    for _f in _s.folders:
        # a folder alias must be unambiguous; the table is authored so it is
        FOLDER_TO_SYSTEM.setdefault(_f, _s.name)

EXT_TO_SYSTEMS: dict[str, tuple[str, ...]] = {}
for _s in SYSTEMS:
    for _e in _s.exts:
        EXT_TO_SYSTEMS.setdefault(_e, ())
        EXT_TO_SYSTEMS[_e] = EXT_TO_SYSTEMS[_e] + (_s.name,)

ALL_EXTS: frozenset[str] = frozenset(EXT_TO_SYSTEMS)


# ── accessors ────────────────────────────────────────────────────────
def by_name(name: str) -> System | None:
    return _BY_NAME.get(name)


def folder_for_system(name: str) -> str | None:
    s = _BY_NAME.get(name)
    return s.folder if s else None


def system_for_folder(folder_basename: str) -> str | None:
    """Canonical system for a directory's basename (case-insensitive)."""
    return FOLDER_TO_SYSTEM.get((folder_basename or "").lower())


def systems_for_ext(ext: str) -> tuple[str, ...]:
    return EXT_TO_SYSTEMS.get((ext or "").lower().lstrip("."), ())


def unique_system_for_ext(ext: str) -> str | None:
    """The system iff this extension belongs to exactly one — else None."""
    hits = systems_for_ext(ext)
    return hits[0] if len(hits) == 1 else None


def system_for_game_id(game_id: str) -> str | None:
    """Map a catalog id (e.g. 'GameBoyColor_Foo') back to its system name."""
    prefix = (game_id or "").split("_", 1)[0]
    s = _BY_ID_PREFIX.get(prefix)
    return s.name if s else None


def is_multifile_disc(ext: str) -> bool:
    return (ext or "").lower().lstrip(".") in MULTIFILE_DISC
