# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.

## variants.nim
## Typed variant system for deterministic, content-addressed packages
## Evolution of USE flags into semantic domains with type safety

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

type
  FlagType* = enum
    ftBool        ## Simple on/off: +lto, -debug
    ftChoice      ## Mutually exclusive: init=dinit
    ftSet         ## Multiple allowed: security=pie,relro,hardened
    ftEnum        ## Predefined options with validation

  VariantFlag* = object
    domain*: string        ## "init", "graphics", "security"
    name*: string          ## "dinit", "wayland", "pie"
    flagType*: FlagType
    enabled*: bool
    value*: string         ## For choice/enum types
    affects*: seq[string]  ## What this flag impacts
    conflicts*: seq[string]
    requires*: seq[string]

  FlagDomain* = object
    name*: string
    description*: string
    flagType*: FlagType
    exclusive*: bool       ## Only one flag can be enabled
    options*: seq[string]
    default*: string       ## Default value for choice types
    defaults*: seq[string] ## Default values for set types

  VariantFingerprint* = object
    hash*: string          ## BLAKE3 hash of variant configuration
    packageName*: string
    version*: string
    domains*: Table[string, seq[string]]
    compilerFlags*: CompilerFlags
    toolchain*: string
    target*: string

  PackageVariant* = object
    fingerprint*: VariantFingerprint
    installPath*: string
    isDefault*: bool
    installedAt*: string

# ============================================
# Domain Definitions (9 Semantic Domains)
# ============================================

proc getSemanticDomains*(): Table[string, FlagDomain] =
  ## Get the 9 semantic domains for typed variants
  result = initTable[string, FlagDomain]()

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

  # 2. Runtime Features (set)
  result["runtime"] = FlagDomain(
    name: "runtime",
    description: "Core runtime features",
    flagType: ftSet,
    exclusive: false,
    options: @[
      "ssl", "http3", "zstd", "lz4", "ipv6",
      "dbus", "doc", "examples",
      "python", "ruby", "perl", "lua", "go", "rust",
      "cuda", "rocm", "onnx", "tensorrt",
      "steam", "wine", "proton"
    ],
    defaults: @["ssl", "ipv6"]
  )

  # 3. Graphics (choice + sub-features)
  result["graphics"] = FlagDomain(
    name: "graphics",
    description: "Display server and GPU API",
    flagType: ftChoice,
    exclusive: false,  # Can have display + GPU APIs
    options: @[
      "none", "X", "wayland",
      "vulkan", "opengl", "mesa",
      "nvidia", "amd", "intel-gpu"
    ],
    default: "none"
  )

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

  # 5. Security Hardening (set)
  result["security"] = FlagDomain(
    name: "security",
    description: "Security hardening features",
    flagType: ftSet,
    exclusive: false,
    options: @["pie", "relro", "hardened", "fortify", "stack-protector"],
    defaults: @["pie", "relro"]
  )

  # 6. Optimization (set with conflicts)
  result["optimization"] = FlagDomain(
    name: "optimization",
    description: "Build optimizations",
    flagType: ftSet,
    exclusive: false,
    options: @["lto", "pgo", "march-native", "debug", "strip"],
    defaults: @["lto"]
  )

  # 7. Integration (set)
  result["integration"] = FlagDomain(
    name: "integration",
    description: "System interfaces and integration",
    flagType: ftSet,
    exclusive: false,
    options: @[
      "docker", "podman", "nipcells", "containerd", "runc", "crun",
      "kvm", "qemu", "libvirt", "xen", "bhyve",
      "nexus-api", "nexus-db", "nexus-sync", "nexus-monitor", "nexus-security"
    ],
    defaults: @[]
  )

  # 8. Network (set)
  result["network"] = FlagDomain(
    name: "network",
    description: "Networking stack and protocols",
    flagType: ftSet,
    exclusive: false,
    options: @[
      "ipv6", "wireguard", "zerotier", "tailscale",
      "mesh", "p2p", "ipfs", "libp2p"
    ],
    defaults: @["ipv6"]
  )

  # 9. Developer Tools (set)
  result["developer"] = FlagDomain(
    name: "developer",
    description: "Development tools and features",
    flagType: ftSet,
    exclusive: false,
    options: @["debugger", "profiler", "lsp", "repl", "hot-reload", "sanitizer", "coverage"],
    defaults: @[]
  )

# ============================================
# Variant Flag Parsing
# ============================================

proc parseVariantFlag*(flagStr: string): VariantFlag =
  ## Parse domain-scoped variant flag
  ## Examples:
  ##   "+init=dinit"
  ##   "+runtime=ssl,http3,zstd"
  ##   "+optimization=lto"
  ##   "-debug"

  let trimmed = flagStr.strip()

  if trimmed.len == 0:
    raise newException(ValueError, "Empty variant flag")

  # Determine enabled/disabled
  var enabled = true
  var flagPart = trimmed

  if trimmed[0] == '+':
    enabled = true
    flagPart = trimmed[1..^1]
  elif trimmed[0] == '-':
    enabled = false
    flagPart = trimmed[1..^1]

  # Check for domain syntax: domain=value
  if '=' in flagPart:
    let parts = flagPart.split('=', 1)
    result = VariantFlag(
      domain: parts[0],
      name: parts[1],
      flagType: ftChoice,  # Will be determined by domain
      enabled: enabled,
      value: parts[1]
    )
  else:
    # Simple flag (backward compatible)
    result = VariantFlag(
      domain: "",  # Will be inferred
      name: flagPart,
      flagType: ftBool,
      enabled: enabled,
      value: ""
    )

proc inferDomain*(flagName: string, domains: Table[string, FlagDomain]): string =
  ## Infer domain from flag name for backward compatibility
  for domainName, domain in domains:
    if flagName in domain.options:
      return domainName
  return "runtime"  # Default domain

# ============================================
# Variant Fingerprint Generation
# ============================================

proc calculateVariantFingerprint*(
  packageName: string,
  version: string,
  domains: Table[string, seq[string]],
  compilerFlags: CompilerFlags,
  toolchain: string = "default",
  target: string = "native"
): string =
  ## Calculate deterministic BLAKE3 hash for variant
  ## Hash of: source + version + flags + toolchain + target

  var hashInput = ""

  # 1. Package identity
  hashInput.add(packageName & "\n")
  hashInput.add(version & "\n")

  # 2. Sorted domain flags (deterministic)
  var sortedDomainNames = domains.keys.toSeq
  sortedDomainNames.sort()

  for domainName in sortedDomainNames:
    hashInput.add(domainName & ":")
    var sortedFlags = domains[domainName]
    sortedFlags.sort()
    hashInput.add(sortedFlags.join(",") & "\n")

  # 3. Compiler flags
  hashInput.add("cflags:" & compilerFlags.cflags & "\n")
  hashInput.add("cxxflags:" & compilerFlags.cxxflags & "\n")
  hashInput.add("ldflags:" & compilerFlags.ldflags & "\n")

  # 4. Toolchain
  hashInput.add("toolchain:" & toolchain & "\n")

  # 5. Target
  hashInput.add("target:" & target & "\n")

  # Calculate hash (simplified for now - will use BLAKE3)
  # For now, use a simple hash
  var simpleHash = 0
  for c in hashInput:
    simpleHash = (simpleHash * 31 + ord(c)) and 0x7FFFFFFF

  result = "blake3-" & simpleHash.toHex()[0..11].toLower()

proc generateVariantPath*(
  packageName: string,
  version: string,
  fingerprint: string,
  baseDir: string = "/Programs"
): string =
  ## Generate variant installation path
  ## Format: /Programs/<Name>/<Version>-<Fingerprint>/
  result = baseDir / packageName / (version & "-" & fingerprint)

# ============================================
# Variant Management
# ============================================

proc listVariants*(packageName: string, baseDir: string = "/Programs"): seq[PackageVariant] =
  ## List all installed variants of a package
  result = @[]

  let packageDir = baseDir / packageName
  if not dirExists(packageDir):
    return

  for kind, path in walkDir(packageDir):
    if kind == pcDir:
      let dirName = path.splitPath().tail
      if dirName.contains("-blake3-"):
        let parts = dirName.split("-blake3-", 1)
        if parts.len == 2:
          result.add(PackageVariant(
            fingerprint: VariantFingerprint(
              hash: "blake3-" & parts[1],
              packageName: packageName,
              version: parts[0]
            ),
            installPath: path,
            isDefault: false,  # TODO: Check symlinks
            installedAt: ""
          ))

proc getDefaultVariant*(packageName: string): string =
  ## Get the default variant fingerprint for a package
  ## Checks which variant is currently symlinked
  # TODO: Implement by checking /System/Links/ symlinks
  result = ""

proc setDefaultVariant*(packageName: string, fingerprint: string): bool =
  ## Set which variant is the default (symlinked)
  ## Updates symlinks in /System/Links/
  # TODO: Implement symlink switching
  result = false

# ============================================
# Display Functions
# ============================================

proc displayVariant*(variant: PackageVariant) =
  ## Display variant information
  echo variant.fingerprint.packageName & " " & variant.fingerprint.version & "-" & variant.fingerprint.hash
  if variant.isDefault:
    echo "  [DEFAULT]"
  echo "  Path: " & variant.installPath

proc displayVariants*(variants: seq[PackageVariant]) =
  ## Display list of variants
  if variants.len == 0:
    echo "No variants found"
    return

  echo "Installed Variants:"
  echo ""
  for variant in variants:
    displayVariant(variant)
    echo ""
