## variant_parser.nim
## CLI parser for domain-scoped variant flags
## Supports both new domain syntax and legacy USE flags

import std/[strutils, tables]
import variant_types
import variant_domains
import variant_validator

# #############################################################################
# Parser Error Types
# #############################################################################

type
  ParseError* = object of CatchableError
    ## Error during flag parsing
    flagStr*: string

# #############################################################################
# Forward Declarations
# #############################################################################

proc parseLegacyFlag*(flagStr: string): VariantFlag

# #############################################################################
# Domain-Scoped Flag Parsing
# #############################################################################

proc parseDomainFlag*(flagStr: string): VariantFlag =
  ## Parse domain-scoped flag: +domain=value1,value2
  ##
  ## Examples:
  ##   +init=dinit
  ##   +security=pie,relro,hardened
  ##   -optimization=debug
  ##   +runtime=ssl,http3

  if flagStr.len == 0:
    raise newException(ParseError, "Empty flag string")

  # Check if it's a domain-scoped flag (contains '=')
  if not flagStr.contains('='):
    # Simple flag - delegate to legacy parser
    return parseLegacyFlag(flagStr)

  # Determine if enabled or disabled
  var enabled = true  # Default to enabled
  var cleanFlag = flagStr

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

  # Split domain and values
  let parts = cleanFlag.split('=', 1)
  if parts.len != 2:
    var err = new(ParseError)
    err.msg = "Invalid flag format: '" & flagStr & "'. Expected: +domain=value"
    err.flagStr = flagStr
    raise err

  let domain = parts[0].strip()
  let valueStr = parts[1].strip()

  # Validate domain exists
  if not hasDomain(domain):
    let validDomains = getAllDomainNames()
    var err = new(ParseError)
    err.msg = "Unknown domain: '" & domain & "'. Valid domains: " & validDomains.join(", ")
    err.flagStr = flagStr
    raise err

  # Parse values (comma-separated)
  let values = valueStr.split(',')
  var cleanValues: seq[string] = @[]
  for val in values:
    let cleaned = val.strip()
    if cleaned.len > 0:
      cleanValues.add(cleaned)

  if cleanValues.len == 0:
    var err = new(ParseError)
    err.msg = "No values specified for domain '" & domain & "'"
    err.flagStr = flagStr
    raise err

  # Validate values
  let domainDef = getDomain(domain)
  for value in cleanValues:
    if not isValidDomainValue(domain, value):
      let validOptions = getDomainOptions(domain)
      var err = new(ParseError)
      err.msg = "Invalid value '" & value & "' for domain '" & domain & "'. Valid options: " & validOptions.join(", ")
      err.flagStr = flagStr
      raise err

  # Create variant flag
  result = VariantFlag(
    domain: domain,
    name: cleanValues[0],
    flagType: domainDef.flagType,
    enabled: enabled,
    value: cleanValues.join(","),
    affects: @[],
    conflicts: @[],
    requires: @[]
  )


# #############################################################################
# Legacy Flag Parsing
# #############################################################################

proc parseLegacyFlag*(flagStr: string): VariantFlag =
  ## Map legacy USE flag to domain-scoped flag
  ##
  ## Examples:
  ##   +lto → +optimization=lto
  ##   -systemd → -init=systemd
  ##   +ssl → +runtime=ssl

  if flagStr.len == 0:
    raise newException(ParseError, "Empty flag string")

  # Determine if enabled or disabled
  var enabled = true  # Default to enabled
  var name = flagStr

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

  if name.len == 0:
    raise newException(ParseError, "Invalid flag: '" & flagStr & "'")

  # Map to domain using legacy mapping
  let domain = mapLegacyFlagToDomain(name)

  # Validate the mapped domain and value
  if not hasDomain(domain):
    var err = new(ParseError)
    err.msg = "Could not map legacy flag '" & name & "' to a valid domain"
    err.flagStr = flagStr
    raise err

  # For legacy flags, we don't validate the value exists in the domain
  # because legacy flags might be more permissive
  # The validation will happen later in the validation phase

  result = VariantFlag(
    domain: domain,
    name: name,
    flagType: ftBool,
    enabled: enabled,
    value: "",
    affects: @[],
    conflicts: @[],
    requires: @[]
  )

# #############################################################################
# Multi-Flag Parsing
# #############################################################################

proc parseFlags*(flagStrs: seq[string]): seq[VariantFlag] =
  ## Parse multiple flag strings
  result = @[]

  for flagStr in flagStrs:
    let trimmed = flagStr.strip()
    if trimmed.len == 0:
      continue

    try:
      result.add(parseDomainFlag(trimmed))
    except ParseError as e:
      # Re-raise with context
      raise e
    except Exception as e:
      var err = new(ParseError)
      err.msg = "Failed to parse flag '" & trimmed & "': " & e.msg
      err.flagStr = trimmed
      raise err

proc parseFlagsString*(flagsStr: string): seq[VariantFlag] =
  ## Parse space-separated flags from a single string
  ## Example: "+init=dinit +security=pie,relro -optimization=debug"

  let parts = flagsStr.split()
  result = parseFlags(parts)

# #############################################################################
# Flag Grouping and Conversion
# #############################################################################

proc groupFlagsByDomain*(flags: seq[VariantFlag]): Table[string, seq[string]] =
  ## Group variant flags by domain
  ## Returns a table mapping domain names to lists of enabled values
  result = initTable[string, seq[string]]()

  for flag in flags:
    if not flag.enabled:
      continue  # Skip disabled flags

    if not result.hasKey(flag.domain):
      result[flag.domain] = @[]

    if flag.value.len > 0:
      # Domain-scoped flag with explicit values
      let values = flag.value.split(',')
      for val in values:
        let cleaned = val.strip()
        if cleaned.len > 0 and cleaned notin result[flag.domain]:
          result[flag.domain].add(cleaned)
    else:
      # Legacy flag
      if flag.name notin result[flag.domain]:
        result[flag.domain].add(flag.name)

proc flagsToTable*(flags: seq[VariantFlag]): Table[string, seq[string]] =
  ## Convert variant flags to domain table (alias for groupFlagsByDomain)
  result = groupFlagsByDomain(flags)

# #############################################################################
# Flag Validation
# #############################################################################

proc parseAndValidate*(flagStrs: seq[string]): Table[string, seq[string]] =
  ## Parse flags and validate them, returning grouped domain table
  let flags = parseFlags(flagStrs)

  # Validate the flags
  let validation = validateVariantFlags(flags)
  if not validation.isOk:
    raise validation.error

  # Group by domain
  result = groupFlagsByDomain(flags)

proc parseAndValidateString*(flagsStr: string): Table[string, seq[string]] =
  ## Parse space-separated flags string and validate
  let flags = parseFlagsString(flagsStr)

  # Validate the flags
  let validation = validateVariantFlags(flags)
  if not validation.isOk:
    raise validation.error

  # Group by domain
  result = groupFlagsByDomain(flags)

# #############################################################################
# Helper Functions
# #############################################################################

proc isDomainScopedFlag*(flagStr: string): bool =
  ## Check if a flag string uses domain-scoped syntax
  result = flagStr.contains('=')

proc isLegacyFlag*(flagStr: string): bool =
  ## Check if a flag string uses legacy syntax
  result = not flagStr.contains('=')

proc extractDomainFromFlag*(flagStr: string): string =
  ## Extract domain name from a flag string
  if flagStr.contains('='):
    let cleanFlag = if flagStr[0] in ['+', '-']: flagStr[1..^1] else: flagStr
    let parts = cleanFlag.split('=', 1)
    result = parts[0].strip()
  else:
    # Legacy flag - need to map it
    let name = if flagStr[0] in ['+', '-']: flagStr[1..^1] else: flagStr
    result = mapLegacyFlagToDomain(name)

proc extractValuesFromFlag*(flagStr: string): seq[string] =
  ## Extract values from a flag string
  result = @[]

  if flagStr.contains('='):
    let cleanFlag = if flagStr[0] in ['+', '-']: flagStr[1..^1] else: flagStr
    let parts = cleanFlag.split('=', 1)
    if parts.len == 2:
      let values = parts[1].split(',')
      for val in values:
        let cleaned = val.strip()
        if cleaned.len > 0:
          result.add(cleaned)
  else:
    # Legacy flag - the flag name is the value
    let name = if flagStr[0] in ['+', '-']: flagStr[1..^1] else: flagStr
    result.add(name)
