## variant_domains.nim
## Semantic domain definitions for NIP variant system
## Defines 9 orthogonal domains with typed constraints

import std/tables
import variant_types

# #############################################################################
# Semantic Domain Definitions
# #############################################################################

proc initSemanticDomains*(): Table[string, FlagDomain] =
  ## Initialize the 9 semantic domains with their constraints
  result = initTable[string, FlagDomain]()

  # 1. Init System Domain
  result["init"] = FlagDomain(
    name: "init",
    description: "Init system selection",
    flagType: ftChoice,
    exclusive: true,
    options: @["systemd", "dinit", "openrc", "runit", "s6"],
    default: "dinit",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 2. Runtime Features Domain
  result["runtime"] = FlagDomain(
    name: "runtime",
    description: "Core runtime features",
    flagType: ftSet,
    exclusive: false,
    options: @["ssl", "http3", "zstd", "ipv6", "doc", "dbus", "cuda", "rocm",
               "onnx", "tensorrt", "steam", "wine", "proton"],
    default: "",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 3. Graphics Domain
  result["graphics"] = FlagDomain(
    name: "graphics",
    description: "Display server and GPU API",
    flagType: ftChoice,
    exclusive: true,
    options: @["none", "X", "wayland", "vulkan", "opengl", "mesa"],
    default: "none",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 4. Audio Domain
  result["audio"] = FlagDomain(
    name: "audio",
    description: "Sound server selection",
    flagType: ftChoice,
    exclusive: true,
    options: @["none", "pipewire", "pulseaudio", "alsa", "jack"],
    default: "pipewire",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )


  # 5. Security Domain
  result["security"] = FlagDomain(
    name: "security",
    description: "Security hardening",
    flagType: ftSet,
    exclusive: false,
    options: @["pie", "relro", "hardened", "fortify", "stack-protector"],
    default: "",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 6. Optimization Domain
  result["optimization"] = FlagDomain(
    name: "optimization",
    description: "Build optimizations",
    flagType: ftSet,
    exclusive: false,
    options: @["lto", "pgo", "march-native", "debug", "strip"],
    default: "",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 7. Integration Domain
  result["integration"] = FlagDomain(
    name: "integration",
    description: "System interfaces",
    flagType: ftSet,
    exclusive: false,
    options: @["docker", "podman", "nipcells", "libvirt", "nexus-api",
               "nexus-security", "nexus-monitor"],
    default: "",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 8. Network Domain
  result["network"] = FlagDomain(
    name: "network",
    description: "Networking stack",
    flagType: ftSet,
    exclusive: false,
    options: @["ipv6", "wireguard", "zerotier", "mesh", "p2p", "ipfs"],
    default: "",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

  # 9. Developer Domain
  result["developer"] = FlagDomain(
    name: "developer",
    description: "Development tooling",
    flagType: ftSet,
    exclusive: false,
    options: @["debugger", "profiler", "lsp", "repl", "hot-reload", "sanitizer"],
    default: "",
    compilerFlagRules: initTable[string, CompilerFlagRule]()
  )

# Global constant for semantic domains
const SEMANTIC_DOMAINS* = initSemanticDomains()

# #############################################################################
# Domain Query Functions
# #############################################################################

proc getDomain*(name: string): FlagDomain =
  ## Get a domain by name, raises KeyError if not found
  result = SEMANTIC_DOMAINS[name]

proc hasDomain*(name: string): bool =
  ## Check if a domain exists
  result = SEMANTIC_DOMAINS.hasKey(name)

proc getAllDomainNames*(): seq[string] =
  ## Get all domain names
  result = @[]
  for name in SEMANTIC_DOMAINS.keys:
    result.add(name)

proc isValidDomainValue*(domain: string, value: string): bool =
  ## Check if a value is valid for a domain
  if not hasDomain(domain):
    return false

  let domainDef = SEMANTIC_DOMAINS[domain]
  result = value in domainDef.options

proc getDomainOptions*(domain: string): seq[string] =
  ## Get all valid options for a domain
  if not hasDomain(domain):
    return @[]

  result = SEMANTIC_DOMAINS[domain].options

proc getDomainType*(domain: string): FlagType =
  ## Get the flag type for a domain
  if not hasDomain(domain):
    raise newException(KeyError, "Unknown domain: " & domain)

  result = SEMANTIC_DOMAINS[domain].flagType

proc isExclusiveDomain*(domain: string): bool =
  ## Check if a domain is exclusive (only one value allowed)
  if not hasDomain(domain):
    return false

  result = SEMANTIC_DOMAINS[domain].exclusive


# #############################################################################
# Legacy Category Migration
# #############################################################################

proc initLegacyCategoryMap*(): Table[string, string] =
  ## Initialize mapping from legacy 16 categories to new 9 domains
  result = initTable[string, string]()

  # Eliminated categories → New domains
  result["gui"] = "graphics"
  result["gaming"] = "graphics"  # vulkan, opengl → graphics; steam → runtime
  result["container"] = "integration"
  result["virtualization"] = "integration"
  result["mesh"] = "network"
  result["ai-ml"] = "runtime"  # cuda, rocm → runtime
  result["nexus-fleet"] = "profile"  # Becomes a profile, not a domain
  result["nexus-bootstrap"] = "build-mode"  # Build-time, not runtime
  result["nexus-integration"] = "integration"
  result["bindings"] = "runtime"
  result["features"] = "runtime"

  # Preserved categories (direct mapping)
  result["init"] = "init"
  result["audio"] = "audio"
  result["optimization"] = "optimization"
  result["security"] = "security"

# Global constant for legacy category mapping
const LEGACY_CATEGORY_MAP* = initLegacyCategoryMap()

# #############################################################################
# Legacy Flag Migration Functions
# #############################################################################

proc mapLegacyFlagToDomain*(flagName: string, category: string = ""): string =
  ## Map a legacy USE flag to its corresponding domain
  ## If category is provided, use it for mapping
  ## Otherwise, try to infer from common flag names

  # If category is provided and exists in legacy map, use it
  if category.len > 0 and LEGACY_CATEGORY_MAP.hasKey(category):
    let mappedDomain = LEGACY_CATEGORY_MAP[category]
    # Skip special cases that aren't runtime domains
    if mappedDomain notin ["profile", "build-mode"]:
      return mappedDomain

  # Infer domain from common flag names
  case flagName
  # Init system flags
  of "systemd", "dinit", "openrc", "runit", "s6":
    return "init"

  # Graphics flags
  of "X", "wayland", "vulkan", "opengl", "mesa", "gtk", "qt":
    return "graphics"

  # Audio flags
  of "pipewire", "pulseaudio", "alsa", "jack":
    return "audio"

  # Security flags
  of "pie", "relro", "hardened", "fortify", "stack-protector":
    return "security"

  # Optimization flags
  of "lto", "pgo", "march-native", "debug", "strip":
    return "optimization"

  # Integration flags
  of "docker", "podman", "nipcells", "libvirt", "nexus-api", "nexus-security", "nexus-monitor":
    return "integration"

  # Network flags
  of "ipv6", "wireguard", "zerotier", "mesh", "p2p", "ipfs":
    return "network"

  # Developer flags
  of "debugger", "profiler", "lsp", "repl", "hot-reload", "sanitizer":
    return "developer"

  # Runtime flags (default for unknown)
  else:
    return "runtime"

proc mapLegacyCategoryToDomain*(category: string): string =
  ## Map a legacy category name to a domain name
  ## Returns empty string if category should not be mapped to a domain

  if LEGACY_CATEGORY_MAP.hasKey(category):
    let mapped = LEGACY_CATEGORY_MAP[category]
    # Filter out special cases
    if mapped in ["profile", "build-mode"]:
      return ""
    return mapped

  # Unknown category defaults to runtime
  return "runtime"

proc isLegacyCategory*(category: string): bool =
  ## Check if a category name is a legacy category
  result = LEGACY_CATEGORY_MAP.hasKey(category)

proc getLegacyCategories*(): seq[string] =
  ## Get all legacy category names
  result = @[]
  for category in LEGACY_CATEGORY_MAP.keys:
    result.add(category)
