## Resolver Integration
##
## This module integrates the dependency graph, CNF translation, and CDCL solver
## to provide end-to-end dependency resolution.
##
## Philosophy:
## - Graph construction identifies package relationships
## - CNF translation converts constraints to boolean logic
## - PubGrub-style CDCL solver finds satisfying assignments
## - Solution is converted back to installation order
##
## The integration flow:
## 1. Build dependency graph from package requirements
## 2. Translate graph to CNF formula
## 3. Solve CNF using CDCL solver
## 4. Convert SAT model to package selections
## 5. Perform topological sort for installation order

import std/[tables, sets, options, sequtils, strutils]
import ./dependency_graph
import ./cnf_translator
import ./cdcl_solver
import ./variant_types
import ../manifest_parser

type
  ## Resolution request from user
  ResolutionRequest* = object
    rootPackages*: seq[PackageSpec]
    constraints*: seq[VariantConstraint]
  
  ## A package specification for resolution
  PackageSpec* = object
    packageName*: string
    versionConstraint*: VersionConstraint
    variantProfile*: VariantProfile
  
  ## A variant constraint
  VariantConstraint* = object
    packageName*: string
    requiredFlags*: VariantProfile
  
  ## Resolution result
  ResolutionResult* = object
    case success*: bool
    of true:
      packages*: seq[ResolvedPackage]
      installOrder*: seq[string]  ## Topologically sorted
    of false:
      conflict*: ConflictReport
  
  ## A resolved package ready for installation
  ResolvedPackage* = object
    packageName*: string
    version*: SemanticVersion
    variant*: VariantProfile
    source*: string
  
  ## Conflict report for user
  ConflictReport* = object
    conflictType*: ConflictType
    packages*: seq[string]
    details*: string
    suggestions*: seq[string]
  
  ConflictType* = enum
    VersionConflict,
    VariantConflict,
    CircularDependency,
    Unsatisfiable

# --- Graph to CNF Translation ---

proc graphToCNF*(graph: DependencyGraph): CNFFormula =
  ## Convert a dependency graph to a CNF formula
  ##
  ## Requirements: 5.1 - Use PubGrub algorithm with CDCL
  
  var formula = newCNFFormula()
  
  # For each term in the graph, create a boolean variable
  for termId, term in graph.terms.pairs:
    let variable = BoolVar(
      package: term.packageName,
      version: term.version,  # Use actual version from term
      variant: term.variantProfile
    )
    discard formula.getOrCreateVarId(variable)
  
  # For each edge, create an implication clause
  for edge in graph.edges:
    let fromTerm = graph.terms[edge.fromTerm]
    let toTerm = graph.terms[edge.toTerm]
    
    discard formula.translateDependency(
      dependent = fromTerm.packageName,
      dependentVersion = fromTerm.version,  # Use actual version
      dependentVariant = fromTerm.variantProfile,
      dependency = toTerm.packageName,
      dependencyVersion = toTerm.version,  # Use actual version
      dependencyVariant = toTerm.variantProfile
    )
  
  return formula

# --- Solution to Package Selection ---

proc modelToPackages*(model: Table[BoolVar, bool], graph: DependencyGraph): seq[ResolvedPackage] =
  ## Convert a SAT model to a list of resolved packages
  ##
  ## Requirements: 5.4 - Produce deterministic installation order
  
  var packages: seq[ResolvedPackage] = @[]
  var seen = initHashSet[string]()  # Track package names to avoid duplicates
  
  for variable, value in model.pairs:
    if value:  # Only include selected packages
      # Create a unique key for this package (name + version + variant)
      let key = variable.package & "-" & $variable.version & "-" & variable.variant.hash
      
      if key notin seen:
        seen.incl(key)
        
        # Find corresponding term in graph to get source
        var foundSource = "unknown"
        for termId, term in graph.terms.pairs:
          if term.packageName == variable.package and 
             term.version == variable.version and
             term.variantProfile.hash == variable.variant.hash:
            foundSource = term.source
            break
        
        packages.add(ResolvedPackage(
          packageName: variable.package,
          version: variable.version,
          variant: variable.variant,
          source: foundSource  # Use actual source from graph
        ))
  
  return packages

# --- Main Resolution Function ---

proc resolve*(request: ResolutionRequest, graph: DependencyGraph): ResolutionResult =
  ## Main resolution function - integrates all components
  ##
  ## This is the complete end-to-end resolution pipeline:
  ## 1. Build dependency graph (already done, passed as parameter)
  ## 2. Translate graph to CNF formula
  ## 3. Solve CNF using CDCL solver
  ## 4. Convert SAT model to package selections
  ## 5. Perform topological sort for installation order
  ##
  ## Requirements: 5.1, 5.4, 5.5
  
  # Step 1: Check for circular dependencies in graph
  if graph.hasCycle():
    let cycle = graph.findCycle()
    var cyclePackages: seq[string] = @[]
    if cycle.len > 0:
      for term in cycle:
        cyclePackages.add(term.packageName)
    
    return ResolutionResult(
      success: false,
      conflict: ConflictReport(
        conflictType: CircularDependency,
        packages: cyclePackages,
        details: "Circular dependency detected: " & cyclePackages.join(" -> "),
        suggestions: @[
          "Break the circular dependency by making one dependency optional",
          "Check if this is a bug in package metadata"
        ]
      )
    )
  
  # Step 2: Translate graph to CNF
  var formula = graphToCNF(graph)
  
  # Step 3: Add root requirements as unit clauses
  # Find the root package terms in the graph and add them as unit clauses
  for spec in request.rootPackages:
    # Find matching term in graph
    var foundTerm = false
    for termId, term in graph.terms.pairs:
      if term.packageName == spec.packageName:
        # Add this term as a unit clause (must be selected)
        discard formula.translateRootRequirement(
          package = term.packageName,
          version = term.version,
          variant = term.variantProfile
        )
        foundTerm = true
        break
    
    if not foundTerm:
      # Root package not in graph - this shouldn't happen
      return ResolutionResult(
        success: false,
        conflict: ConflictReport(
          conflictType: Unsatisfiable,
          packages: @[spec.packageName],
          details: "Root package " & spec.packageName & " not found in dependency graph",
          suggestions: @["Check package name", "Ensure package exists in repository"]
        )
      )
  
  # Step 4: Validate CNF is well-formed
  if not formula.isValidCNF():
    return ResolutionResult(
      success: false,
      conflict: ConflictReport(
        conflictType: Unsatisfiable,
        packages: @[],
        details: "Invalid CNF formula generated",
        suggestions: @["Check package specifications", "Report this as a bug"]
      )
    )
  
  # Step 5: Solve using CDCL
  var solver = newCDCLSolver(formula)
  let solverResult = solver.solve()
  
  # Step 6: Handle result
  if solverResult.isSat:
    # Success! Convert model to packages
    let packages = modelToPackages(solverResult.model, graph)
    
    # Step 7: Compute installation order using topological sort
    # Build a subgraph containing only selected packages
    var selectedGraph = newDependencyGraph()
    var selectedTermIds = initHashSet[PackageTermId]()
    
    # Add selected terms to subgraph
    for pkg in packages:
      for termId, term in graph.terms.pairs:
        if term.packageName == pkg.packageName and 
           term.variantProfile.hash == pkg.variant.hash:
          selectedGraph.addTerm(term)
          selectedTermIds.incl(termId)
          break
    
    # Add edges between selected terms
    for edge in graph.edges:
      if edge.fromTerm in selectedTermIds and edge.toTerm in selectedTermIds:
        selectedGraph.addEdge(edge)
    
    # Perform topological sort on selected subgraph
    try:
      let sortedTermIds = selectedGraph.topologicalSort()
      var installOrder: seq[string] = @[]
      
      for termId in sortedTermIds:
        let term = selectedGraph.getTerm(termId)
        if term.isSome:
          installOrder.add(term.get().packageName)
      
      return ResolutionResult(
        success: true,
        packages: packages,
        installOrder: installOrder
      )
    except ValueError as e:
      # This shouldn't happen since we already checked for cycles
      return ResolutionResult(
        success: false,
        conflict: ConflictReport(
          conflictType: CircularDependency,
          packages: @[],
          details: "Unexpected cycle in selected packages: " & e.msg,
          suggestions: @["Report this as a bug"]
        )
      )
  else:
    # Conflict detected - analyze and report
    let conflict = solverResult.conflict
    
    # Extract package names from conflict
    var conflictPackages: seq[string] = @[]
    for assignment in conflict.assignments:
      if assignment.decisionLevel > 0:  # Skip root assignments
        conflictPackages.add(assignment.variable.package)
    
    return ResolutionResult(
      success: false,
      conflict: ConflictReport(
        conflictType: Unsatisfiable,
        packages: conflictPackages,
        details: "No satisfying assignment found: " & $conflict.clause,
        suggestions: @[
          "Check for conflicting version requirements",
          "Check for incompatible variant flags",
          "Try relaxing version constraints",
          "Consider using different package sources"
        ]
      )
    )

# --- Simplified Resolution (for testing) ---

proc resolveSimple*(rootPackage: string, rootVariant: VariantProfile): ResolutionResult =
  ## Simplified resolution for a single root package
  ## Useful for testing and simple use cases
  
  # Create a simple graph with just the root
  var graph = newDependencyGraph()
  let termId = createTermId(rootPackage, rootVariant.hash)
  let term = PackageTerm(
    id: termId,
    packageName: rootPackage,
    variantProfile: rootVariant,
    optional: false,
    source: "test"
  )
  graph.addTerm(term)
  
  # Create resolution request
  let request = ResolutionRequest(
    rootPackages: @[PackageSpec(
      packageName: rootPackage,
      versionConstraint: VersionConstraint(
        operator: OpAny,
        version: SemanticVersion(major: 1, minor: 0, patch: 0)
      ),
      variantProfile: rootVariant
    )],
    constraints: @[]
  )
  
  return resolve(request, graph)
