## use_flags.nim
## USE flag parsing and management for NIP
## Supports both simple key-value format and structured formats

import std/[strutils, tables, sequtils, algorithm]
import config

type
  UseFlagParseError* = object of CatchableError

  UseFlagCategory* = object
    name*: string
    description*: string
    exclusive*: bool  # Only one flag in category can be enabled
    options*: seq[string]

  UseFlagSet* = object
    flags*: seq[UseFlag]
    categories*: Table[string, UseFlagCategory]

# ============================================
# USE Flag Parsing
# ============================================

proc parseUseFlag*(flagStr: string): UseFlag =
  ## Parse a single USE flag string like "+lto" or "-systemd"
  ## Returns a UseFlag object
  let trimmed = flagStr.strip()

  if trimmed.len == 0:
    raise newException(UseFlagParseError, "Empty USE flag")

  if trimmed[0] == '+':
    result = UseFlag(
      name: trimmed[1..^1],
      enabled: true,
      category: ""
    )
  elif trimmed[0] == '-':
    result = UseFlag(
      name: trimmed[1..^1],
      enabled: false,
      category: ""
    )
  else:
    # No prefix means enabled by default
    result = UseFlag(
      name: trimmed,
      enabled: true,
      category: ""
    )

proc parseUseFlagLine*(line: string): seq[UseFlag] =
  ## Parse a line containing multiple USE flags
  ## Example: "+lto -debug +wayland -X"
  result = @[]

  let trimmed = line.strip()
  if trimmed.len == 0 or trimmed.startsWith("#"):
    return

  # Split on whitespace
  let parts = trimmed.split()
  for part in parts:
    if part.len > 0:
      try:
        result.add(parseUseFlag(part))
      except UseFlagParseError:
        # Skip invalid flags
        continue

proc parseUseFlagsFromConfig*(line: string): seq[UseFlag] =
  ## Parse USE flags from config file format
  ## Supports: use-flags = "+lto -debug +wayland"
  let trimmed = line.strip()

  # Check if it's a use-flags line
  if not trimmed.startsWith("use-flags"):
    return @[]

  # Split on = and get the value part
  let parts = trimmed.split('=', 1)
  if parts.len != 2:
    return @[]

  let value = parts[1].strip()

  # Remove quotes if present
  var cleanValue = value
  if cleanValue.startsWith('"') and cleanValue.endsWith('"'):
    cleanValue = cleanValue[1..^2]

  return parseUseFlagLine(cleanValue)

# ============================================
# USE Flag Merging and Resolution
# ============================================

proc mergeUseFlags*(base: seq[UseFlag], override: seq[UseFlag]): seq[UseFlag] =
  ## Merge two USE flag sets, with override taking precedence
  ## Later flags override earlier flags for the same name
  var flagMap = initTable[string, UseFlag]()

  # Add base flags
  for flag in base:
    flagMap[flag.name] = flag

  # Override with new flags
  for flag in override:
    flagMap[flag.name] = flag

  # Convert back to sequence
  result = @[]
  for name, flag in flagMap:
    result.add(flag)

proc getEffectiveUseFlags*(config: NipConfig, packageName: string): seq[UseFlag] =
  ## Get effective USE flags for a package
  ## Merges: profile flags -> global flags -> package-specific flags
  result = @[]

  # Start with active profile flags
  if config.profiles.hasKey(config.activeProfile):
    result = config.profiles[config.activeProfile].useFlags

  # Merge global USE flags
  result = mergeUseFlags(result, config.globalUseFlags)

  # Merge package-specific flags
  if config.packageConfigs.hasKey(packageName):
    result = mergeUseFlags(result, config.packageConfigs[packageName].useFlags)

proc getEffectiveCompilerFlags*(config: NipConfig, packageName: string): CompilerFlags =
  ## Get effective compiler flags for a package
  ## Merges: profile flags -> global flags -> package-specific flags
  result = config.compilerFlags

  # Override with active profile flags
  if config.profiles.hasKey(config.activeProfile):
    let profile = config.profiles[config.activeProfile]
    if profile.compilerFlags.cflags.len > 0:
      result.cflags = profile.compilerFlags.cflags
    if profile.compilerFlags.cxxflags.len > 0:
      result.cxxflags = profile.compilerFlags.cxxflags
    if profile.compilerFlags.ldflags.len > 0:
      result.ldflags = profile.compilerFlags.ldflags
    if profile.compilerFlags.makeflags.len > 0:
      result.makeflags = profile.compilerFlags.makeflags

  # Override with package-specific flags
  if config.packageConfigs.hasKey(packageName):
    let pkgCfg = config.packageConfigs[packageName]
    if pkgCfg.compilerFlags.cflags.len > 0:
      result.cflags = pkgCfg.compilerFlags.cflags
    if pkgCfg.compilerFlags.cxxflags.len > 0:
      result.cxxflags = pkgCfg.compilerFlags.cxxflags
    if pkgCfg.compilerFlags.ldflags.len > 0:
      result.ldflags = pkgCfg.compilerFlags.ldflags
    if pkgCfg.compilerFlags.makeflags.len > 0:
      result.makeflags = pkgCfg.compilerFlags.makeflags

# ============================================
# USE Flag Validation
# ============================================

proc validateUseFlags*(flags: seq[UseFlag], categories: Table[string, UseFlagCategory]): tuple[valid: bool, errors: seq[string]] =
  ## Validate USE flags against category definitions
  ## Checks for exclusive categories and unknown flags
  result.valid = true
  result.errors = @[]

  # Check exclusive categories
  for catName, category in categories:
    if not category.exclusive:
      continue

    # Find all enabled flags in this category
    var enabledInCategory: seq[string] = @[]
    for flag in flags:
      if flag.enabled and flag.category == catName and flag.name in category.options:
        enabledInCategory.add(flag.name)

    # Check if more than one is enabled
    if enabledInCategory.len > 1:
      result.valid = false
      result.errors.add("Exclusive category '" & catName & "' has multiple enabled flags: " & enabledInCategory.join(", "))

proc hasUseFlag*(flags: seq[UseFlag], name: string): bool =
  ## Check if a USE flag is enabled
  for flag in flags:
    if flag.name == name:
      return flag.enabled
  return false

proc getEnabledFlags*(flags: seq[UseFlag]): seq[string] =
  ## Get list of enabled flag names
  result = @[]
  for flag in flags:
    if flag.enabled:
      result.add(flag.name)

proc getDisabledFlags*(flags: seq[UseFlag]): seq[string] =
  ## Get list of disabled flag names
  result = @[]
  for flag in flags:
    if not flag.enabled:
      result.add(flag.name)

# ============================================
# USE Flag Display
# ============================================

proc formatUseFlag*(flag: UseFlag): string =
  ## Format a USE flag for display
  if flag.enabled:
    result = "+" & flag.name
  else:
    result = "-" & flag.name

proc formatUseFlags*(flags: seq[UseFlag]): string =
  ## Format USE flags for display
  result = flags.map(formatUseFlag).join(" ")

proc useFlagsToHash*(flags: seq[UseFlag]): string =
  ## Convert USE flags to a hash string for variant identification
  ## Sorts flags by name for consistent hashing
  var sortedFlags = flags
  sortedFlags.sort do (a, b: UseFlag) -> int:
    cmp(a.name, b.name)

  var parts: seq[string] = @[]
  for flag in sortedFlags:
    if flag.enabled:
      parts.add(flag.name)

  result = parts.join("-")
  if result.len == 0:
    result = "default"

# ============================================
# Standard USE Flag Categories
# ============================================

proc getStandardCategories*(): Table[string, UseFlagCategory] =
  ## Get standard USE flag categories
  result = initTable[string, UseFlagCategory]()

  result["init"] = UseFlagCategory(
    name: "init",
    description: "Init system (mutually exclusive)",
    exclusive: true,
    options: @["systemd", "dinit", "openrc", "runit", "s6"]
  )

  result["gui"] = UseFlagCategory(
    name: "gui",
    description: "GUI toolkit support",
    exclusive: false,
    options: @["X", "wayland", "gtk", "qt", "gtk2", "gtk3", "gtk4", "qt5", "qt6"]
  )

  result["audio"] = UseFlagCategory(
    name: "audio",
    description: "Audio system",
    exclusive: true,
    options: @["pulseaudio", "pipewire", "alsa", "jack", "oss"]
  )

  result["optimization"] = UseFlagCategory(
    name: "optimization",
    description: "Compiler optimizations",
    exclusive: false,
    options: @["lto", "pgo", "debug", "strip", "march-native"]
  )

  result["security"] = UseFlagCategory(
    name: "security",
    description: "Security hardening",
    exclusive: false,
    options: @["hardened", "pie", "relro", "stack-protector", "fortify"]
  )

  result["features"] = UseFlagCategory(
    name: "features",
    description: "Common features",
    exclusive: false,
    options: @["ipv6", "ssl", "zstd", "lz4", "doc", "examples", "dbus", "systemd-units"]
  )

  result["bindings"] = UseFlagCategory(
    name: "bindings",
    description: "Language bindings",
    exclusive: false,
    options: @["python", "ruby", "perl", "lua", "go", "rust"]
  )

  # ============================================
  # Nexus-Specific Categories
  # ============================================

  result["nexus-fleet"] = UseFlagCategory(
    name: "nexus-fleet",
    description: "Nexus fleet command and orchestration",
    exclusive: false,
    options: @["fleet-agent", "fleet-controller", "fleet-discovery", "fleet-mesh", "fleet-sync"]
  )

  result["nexus-bootstrap"] = UseFlagCategory(
    name: "nexus-bootstrap",
    description: "System bootstrapping and building",
    exclusive: false,
    options: @["bootstrap", "cross-compile", "stage1", "stage2", "stage3", "toolchain"]
  )

  result["container"] = UseFlagCategory(
    name: "container",
    description: "Container and isolation technologies",
    exclusive: false,
    options: @["docker", "podman", "containerd", "runc", "crun", "nipcells", "systemd-nspawn"]
  )

  result["virtualization"] = UseFlagCategory(
    name: "virtualization",
    description: "Hypervisor and virtualization support",
    exclusive: false,
    options: @["kvm", "qemu", "xen", "bhyve", "virtualbox", "vmware", "libvirt"]
  )

  result["mesh"] = UseFlagCategory(
    name: "mesh",
    description: "Distributed mesh networking",
    exclusive: false,
    options: @["mesh-network", "p2p", "ipfs", "libp2p", "wireguard", "zerotier", "tailscale"]
  )

  result["gaming"] = UseFlagCategory(
    name: "gaming",
    description: "Gaming and graphics acceleration",
    exclusive: false,
    options: @["vulkan", "opengl", "mesa", "nvidia", "amd", "intel-gpu", "steam", "wine", "proton"]
  )

  result["ai-ml"] = UseFlagCategory(
    name: "ai-ml",
    description: "AI/ML and NPU acceleration",
    exclusive: false,
    options: @["cuda", "rocm", "opencl", "npu", "tpu", "tensorrt", "onnx", "openvino"]
  )

  result["developer"] = UseFlagCategory(
    name: "developer",
    description: "Development tools and features",
    exclusive: false,
    options: @["debugger", "profiler", "sanitizer", "coverage", "lsp", "ide", "repl", "hot-reload"]
  )

  result["nexus-integration"] = UseFlagCategory(
    name: "nexus-integration",
    description: "NexusOS system integration",
    exclusive: false,
    options: @["nexus-api", "nexus-db", "nexus-sync", "nexus-monitor", "nexus-security"]
  )

# ============================================
# Configuration File Parsing Extensions
# ============================================

proc parseUseFlagsSection*(content: string): seq[UseFlag] =
  ## Parse a USE flags section from config file
  ## Handles multi-line USE flag definitions
  result = @[]

  for line in content.splitLines():
    let flags = parseUseFlagLine(line)
    result = mergeUseFlags(result, flags)

proc parsePackageUseFlagsFromConfig*(config: var NipConfig, packageName: string, useFlagsStr: string) =
  ## Parse and add package-specific USE flags to config
  let flags = parseUseFlagLine(useFlagsStr)

  if not config.packageConfigs.hasKey(packageName):
    config.packageConfigs[packageName] = PackageConfig(
      name: packageName,
      useFlags: @[],
      compilerFlags: CompilerFlags()
    )

  config.packageConfigs[packageName].useFlags = mergeUseFlags(
    config.packageConfigs[packageName].useFlags,
    flags
  )

proc parseCompilerFlagsFromConfig*(config: var NipConfig, packageName: string, flagType: string, value: string) =
  ## Parse and add compiler flags to config
  if not config.packageConfigs.hasKey(packageName):
    config.packageConfigs[packageName] = PackageConfig(
      name: packageName,
      useFlags: @[],
      compilerFlags: CompilerFlags()
    )

  case flagType.toLower()
  of "cflags":
    config.packageConfigs[packageName].compilerFlags.cflags = value
  of "cxxflags":
    config.packageConfigs[packageName].compilerFlags.cxxflags = value
  of "ldflags":
    config.packageConfigs[packageName].compilerFlags.ldflags = value
  of "makeflags":
    config.packageConfigs[packageName].compilerFlags.makeflags = value
  else:
    discard

# ============================================
# CLI Helpers
# ============================================

proc displayUseFlags*(flags: seq[UseFlag], title: string = "USE Flags") =
  ## Display USE flags in a formatted way
  echo ""
  echo title & ":"
  echo "=" .repeat(title.len + 1)

  if flags.len == 0:
    echo "  (none)"
    return

  let enabled = getEnabledFlags(flags)
  let disabled = getDisabledFlags(flags)

  if enabled.len > 0:
    echo "  Enabled:  " & enabled.map(proc(s: string): string = "+" & s).join(" ")

  if disabled.len > 0:
    echo "  Disabled: " & disabled.map(proc(s: string): string = "-" & s).join(" ")

  echo ""

proc displayCompilerFlags*(flags: CompilerFlags, title: string = "Compiler Flags") =
  ## Display compiler flags in a formatted way
  echo ""
  echo title & ":"
  echo "=" .repeat(title.len + 1)

  if flags.cflags.len > 0:
    echo "  CFLAGS:    " & flags.cflags
  if flags.cxxflags.len > 0:
    echo "  CXXFLAGS:  " & flags.cxxflags
  if flags.ldflags.len > 0:
    echo "  LDFLAGS:   " & flags.ldflags
  if flags.makeflags.len > 0:
    echo "  MAKEFLAGS: " & flags.makeflags

  echo ""
