# 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.

## variant_compiler.nim
## Compiler flag resolution system for NIP variant management
## Resolves domain flags to actual compiler flags with priority ordering

import std/[tables, strutils]
import variant_types
import config  # For CompilerFlags

# Forward declarations
proc cleanCompilerFlags*(flags: CompilerFlags): CompilerFlags

# #############################################################################
# Compiler Flag Rules
# #############################################################################

proc initCompilerFlagRules*(): Table[string, Table[string, CompilerFlagRule]] =
  ## Initialize compiler flag rules for each domain
  result = initTable[string, Table[string, CompilerFlagRule]]()

  # Optimization domain rules
  result["optimization"] = initTable[string, CompilerFlagRule]()
  result["optimization"]["lto"] = CompilerFlagRule(
    condition: "lto",
    cflags: "-flto=full",
    ldflags: "-flto -fuse-ld=mold"
  )
  result["optimization"]["march-native"] = CompilerFlagRule(
    condition: "march-native",
    cflags: "-march=native",
    ldflags: ""
  )
  result["optimization"]["pgo"] = CompilerFlagRule(
    condition: "pgo",
    cflags: "-fprofile-use",
    ldflags: ""
  )
  result["optimization"]["debug"] = CompilerFlagRule(
    condition: "debug",
    cflags: "-g -O0",
    ldflags: ""
  )
  result["optimization"]["strip"] = CompilerFlagRule(
    condition: "strip",
    cflags: "",
    ldflags: "-Wl,--strip-all"
  )

  # Security domain rules
  result["security"] = initTable[string, CompilerFlagRule]()
  result["security"]["pie"] = CompilerFlagRule(
    condition: "pie",
    cflags: "-fPIE",
    ldflags: "-pie"
  )
  result["security"]["relro"] = CompilerFlagRule(
    condition: "relro",
    cflags: "",
    ldflags: "-Wl,-z,relro,-z,now"
  )
  # NOTE: hardened and fortify are mutually exclusive (different FORTIFY_SOURCE levels)
  # If both are specified, fortify takes precedence (higher security level)
  result["security"]["hardened"] = CompilerFlagRule(
    condition: "hardened",
    cflags: "-D_FORTIFY_SOURCE=2 -fstack-protector-strong",
    ldflags: ""
  )
  result["security"]["fortify"] = CompilerFlagRule(
    condition: "fortify",
    cflags: "-D_FORTIFY_SOURCE=3",
    ldflags: ""
  )
  result["security"]["stack-protector"] = CompilerFlagRule(
    condition: "stack-protector",
    cflags: "-fstack-protector-all",
    ldflags: ""
  )

# Global constant for compiler flag rules
const COMPILER_FLAG_RULES* = initCompilerFlagRules()

# #############################################################################
# Priority Ordering
# #############################################################################

const DOMAIN_PRIORITY_ORDER* = [
  "security",      # Highest priority - security flags applied first
  "optimization",  # Second - optimization flags
  "runtime",       # Third - runtime feature flags
  "integration"    # Fourth - integration flags
]

# #############################################################################
# Compiler Flag Resolution
# #############################################################################

proc resolveCompilerFlags*(
  domains: Table[string, seq[string]],
  baseFlags: CompilerFlags
): CompilerFlags =
  ## Resolve compiler flags from domain configuration
  ## Applies flags in priority order: security > optimization > runtime > integration
  ## Handles conflicts: fortify takes precedence over hardened

  result = baseFlags

  # Check for hardened/fortify conflict and skip hardened if both present
  var skipHardened = false
  if domains.hasKey("security"):
    let secFlags = domains["security"]
    if "hardened" in secFlags and "fortify" in secFlags:
      skipHardened = true

  # Process domains in priority order
  for domain in DOMAIN_PRIORITY_ORDER:
    if not domains.hasKey(domain):
      continue

    # Check if this domain has compiler flag rules
    if not COMPILER_FLAG_RULES.hasKey(domain):
      continue

    let domainRules = COMPILER_FLAG_RULES[domain]

    # Apply rules for each flag in this domain
    for flag in domains[domain]:
      # Skip hardened if fortify is also present (conflict resolution)
      if flag == "hardened" and skipHardened:
        continue

      if domainRules.hasKey(flag):
        let rule = domainRules[flag]

        # Append cflags
        if rule.cflags.len > 0:
          if result.cflags.len > 0:
            result.cflags.add(" " & rule.cflags)
          else:
            result.cflags = rule.cflags

        # Append ldflags
        if rule.ldflags.len > 0:
          if result.ldflags.len > 0:
            result.ldflags.add(" " & rule.ldflags)
          else:
            result.ldflags = rule.ldflags

  # Clean and deduplicate final flags
  result = cleanCompilerFlags(result)


proc resolveCompilerFlagsFromFlags*(
  flags: seq[VariantFlag],
  baseFlags: CompilerFlags
): CompilerFlags =
  ## Resolve compiler flags from a sequence of variant flags
  ## Converts flags to domain table first, then resolves

  var domains = initTable[string, seq[string]]()

  for flag in flags:
    if not flag.enabled:
      continue

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

    if flag.value.len > 0:
      let values = flag.value.split(',')
      for val in values:
        let cleaned = val.strip()
        if cleaned.len > 0 and cleaned notin domains[flag.domain]:
          domains[flag.domain].add(cleaned)
    else:
      if flag.name notin domains[flag.domain]:
        domains[flag.domain].add(flag.name)

  result = resolveCompilerFlags(domains, baseFlags)

# #############################################################################
# Flag Rule Queries
# #############################################################################

proc hasCompilerFlagRule*(domain: string, flag: string): bool =
  ## Check if a compiler flag rule exists for a domain/flag combination
  if not COMPILER_FLAG_RULES.hasKey(domain):
    return false

  result = COMPILER_FLAG_RULES[domain].hasKey(flag)

proc getCompilerFlagRule*(domain: string, flag: string): CompilerFlagRule =
  ## Get the compiler flag rule for a domain/flag combination
  ## Raises KeyError if not found
  result = COMPILER_FLAG_RULES[domain][flag]

proc getDomainRules*(domain: string): Table[string, CompilerFlagRule] =
  ## Get all compiler flag rules for a domain
  if COMPILER_FLAG_RULES.hasKey(domain):
    result = COMPILER_FLAG_RULES[domain]
  else:
    result = initTable[string, CompilerFlagRule]()

proc getAllRules*(): Table[string, Table[string, CompilerFlagRule]] =
  ## Get all compiler flag rules
  result = COMPILER_FLAG_RULES

# #############################################################################
# Flag Analysis
# #############################################################################

proc analyzeCompilerFlags*(domains: Table[string, seq[string]]): seq[string] =
  ## Analyze which compiler flags will be applied for a domain configuration
  ## Returns a list of descriptions
  result = @[]

  for domain in DOMAIN_PRIORITY_ORDER:
    if not domains.hasKey(domain):
      continue

    if not COMPILER_FLAG_RULES.hasKey(domain):
      continue

    let domainRules = COMPILER_FLAG_RULES[domain]

    for flag in domains[domain]:
      if domainRules.hasKey(flag):
        let rule = domainRules[flag]
        var desc = domain & "." & flag & ":"

        if rule.cflags.len > 0:
          desc.add(" cflags=" & rule.cflags)
        if rule.ldflags.len > 0:
          desc.add(" ldflags=" & rule.ldflags)

        result.add(desc)

proc explainFlag*(domain: string, flag: string): string =
  ## Explain what compiler flags a domain flag will produce
  if not hasCompilerFlagRule(domain, flag):
    return "No compiler flag rule defined for " & domain & "." & flag

  let rule = getCompilerFlagRule(domain, flag)
  result = "Flag " & domain & "." & flag & ":\n"

  if rule.cflags.len > 0:
    result.add("  CFLAGS: " & rule.cflags & "\n")
  else:
    result.add("  CFLAGS: (none)\n")

  if rule.ldflags.len > 0:
    result.add("  LDFLAGS: " & rule.ldflags & "\n")
  else:
    result.add("  LDFLAGS: (none)\n")

# #############################################################################
# Conflict Detection
# #############################################################################

proc detectFlagConflicts*(domains: Table[string, seq[string]]): seq[string] =
  ## Detect potential conflicts in compiler flags
  ## Returns list of conflict descriptions
  result = @[]

  # Check for debug + strip conflict
  if domains.hasKey("optimization"):
    let optFlags = domains["optimization"]
    if "debug" in optFlags and "strip" in optFlags:
      result.add("Conflict: 'debug' and 'strip' are mutually exclusive")

    if "debug" in optFlags and "lto" in optFlags:
      result.add("Warning: 'debug' with 'lto' may cause issues with debugging")

  # Check for hardened + fortify conflict (different FORTIFY_SOURCE levels)
  if domains.hasKey("security"):
    let secFlags = domains["security"]
    if "hardened" in secFlags and "fortify" in secFlags:
      result.add("Conflict: 'hardened' (-D_FORTIFY_SOURCE=2) and 'fortify' (-D_FORTIFY_SOURCE=3) are mutually exclusive. Using 'fortify' (higher security level).")

proc hasConflicts*(domains: Table[string, seq[string]]): bool =
  ## Check if there are any compiler flag conflicts
  let conflicts = detectFlagConflicts(domains)
  result = conflicts.len > 0

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

proc mergeCompilerFlags*(base: CompilerFlags, additional: CompilerFlags): CompilerFlags =
  ## Merge two CompilerFlags objects with deduplication
  ## Preserves first-seen order and removes duplicates
  result = base

  # Merge cflags
  if additional.cflags.len > 0:
    if result.cflags.len > 0:
      result.cflags.add(" " & additional.cflags)
    else:
      result.cflags = additional.cflags

  # Merge cxxflags
  if additional.cxxflags.len > 0:
    if result.cxxflags.len > 0:
      result.cxxflags.add(" " & additional.cxxflags)
    else:
      result.cxxflags = additional.cxxflags

  # Merge ldflags
  if additional.ldflags.len > 0:
    if result.ldflags.len > 0:
      result.ldflags.add(" " & additional.ldflags)
    else:
      result.ldflags = additional.ldflags

  # Merge makeflags
  if additional.makeflags.len > 0:
    if result.makeflags.len > 0:
      result.makeflags.add(" " & additional.makeflags)
    else:
      result.makeflags = additional.makeflags

  # Clean and deduplicate all merged flags
  result = cleanCompilerFlags(result)

proc deduplicateFlags(flagString: string): string =
  ## Deduplicate flags while preserving order and trimming whitespace
  ## First occurrence of each flag is kept
  if flagString.len == 0:
    return ""

  let tokens = flagString.strip().splitWhitespace()
  var seen: seq[string] = @[]

  for token in tokens:
    if token notin seen:
      seen.add(token)

  result = seen.join(" ")

proc cleanCompilerFlags*(flags: CompilerFlags): CompilerFlags =
  ## Clean up compiler flags by removing duplicate flags and extra spaces
  result = flags

  # Deduplicate flags and normalize spacing
  result.cflags = deduplicateFlags(result.cflags)
  result.cxxflags = deduplicateFlags(result.cxxflags)
  result.ldflags = deduplicateFlags(result.ldflags)
  result.makeflags = deduplicateFlags(result.makeflags)
