## Cell Management for Dependency Resolver
##
## This module provides cell management integration for the dependency resolver,
## bridging the resolver's conflict detection with the NipCell system.
##
## **Purpose:**
## - Provide normal cell management operations (not just fallback)
## - Integrate resolver with existing NipCell infrastructure
## - Support cell activation, switching, and removal
## - Clean up cell-specific packages during resolution
##
## **Requirements:**
## - 10.3: Maintain separate dependency graphs per cell
## - 10.4: Support cell switching
## - 10.5: Clean up cell-specific packages
##
## **Architecture:**
## ```
## ┌─────────────────────────────────────────────────────────────┐
## │  Resolver Cell Manager                                      │
## │  ─────────────────────────────────────────────────────────  │
## │  Coordinates resolver with NipCell system                   │
## └────────────────────┬────────────────────────────────────────┘
##                      │
##                      v
## ┌─────────────────────────────────────────────────────────────┐
## │  Cell Operations                                            │
## │  ─────────────────────────────────────────────────────────  │
## │  - Activate cell for resolution                             │
## │  - Switch between cells                                     │
## │  - Remove cells and clean up packages                       │
## │  - Resolve dependencies within cell context                 │
## └─────────────────────────────────────────────────────────────┘
## ```

import std/[tables, sets, options, strformat, times]
import ./nipcell_fallback
import ./dependency_graph
import ./variant_types

type
  ## Cell activation result
  CellActivationResult* = object
    success*: bool
    cellName*: string
    previousCell*: Option[string]
    packagesAvailable*: int
    error*: string

  ## Cell removal result
  CellRemovalResult* = object
    success*: bool
    cellName*: string
    packagesRemoved*: int
    error*: string

  ## Resolver cell manager
  ResolverCellManager* = ref object
    graphManager*: NipCellGraphManager
    activeResolutions*: Table[string, DependencyGraph]  ## Active resolutions per cell
    cellPackageCache*: Table[string, HashSet[string]]   ## Package cache per cell

# =============================================================================
# Cell Manager Construction
# =============================================================================

proc newResolverCellManager*(cellRoot: string = ""): ResolverCellManager =
  ## Create a new resolver cell manager.
  ##
  ## **Requirements:** 10.3, 10.4 - Maintain graphs and support switching
  
  result = ResolverCellManager(
    graphManager: newNipCellGraphManager(cellRoot),
    activeResolutions: initTable[string, DependencyGraph](),
    cellPackageCache: initTable[string, HashSet[string]]()
  )

# =============================================================================
# Cell Activation
# =============================================================================

proc activateCell*(
  manager: ResolverCellManager,
  cellName: string
): CellActivationResult =
  ## Activate a cell for dependency resolution.
  ##
  ## **Requirements:** 10.4 - Support cell switching
  ##
  ## **Effect:**
  ## - Switches the active cell
  ## - Loads the cell's dependency graph
  ## - Makes cell packages available for resolution
  ##
  ## **Returns:** Activation result with status and details
  
  # Check if cell exists
  if cellName notin manager.graphManager.cells:
    return CellActivationResult(
      success: false,
      cellName: cellName,
      previousCell: manager.graphManager.activeCell,
      packagesAvailable: 0,
      error: fmt"Cell '{cellName}' not found"
    )
  
  # Get previous cell
  let previousCell = manager.graphManager.activeCell
  
  # Switch to new cell
  let switchResult = manager.graphManager.switchCell(cellName)
  
  if not switchResult.success:
    return CellActivationResult(
      success: false,
      cellName: cellName,
      previousCell: previousCell,
      packagesAvailable: 0,
      error: switchResult.error
    )
  
  # Load cell packages
  let packages = manager.graphManager.getCellPackages(cellName)
  
  # Update package cache
  if cellName notin manager.cellPackageCache:
    manager.cellPackageCache[cellName] = initHashSet[string]()
  
  for pkg in packages:
    manager.cellPackageCache[cellName].incl(pkg)
  
  return CellActivationResult(
    success: true,
    cellName: cellName,
    previousCell: previousCell,
    packagesAvailable: packages.len,
    error: ""
  )

proc deactivateCell*(manager: ResolverCellManager): bool =
  ## Deactivate the current cell.
  ##
  ## **Requirements:** 10.4 - Support cell switching
  
  if manager.graphManager.activeCell.isNone:
    return false
  
  manager.graphManager.activeCell = none(string)
  return true

proc getActiveCellName*(manager: ResolverCellManager): Option[string] =
  ## Get the name of the currently active cell.
  ##
  ## **Requirements:** 10.4 - Support cell switching
  
  return manager.graphManager.activeCell

# =============================================================================
# Cell Switching
# =============================================================================

proc switchToCell*(
  manager: ResolverCellManager,
  cellName: string,
  preserveResolution: bool = false
): CellActivationResult =
  ## Switch to a different cell.
  ##
  ## **Requirements:** 10.4 - Support cell switching
  ##
  ## **Parameters:**
  ## - cellName: Name of cell to switch to
  ## - preserveResolution: If true, preserve current resolution state
  ##
  ## **Returns:** Activation result
  
  # Save current resolution if requested
  if preserveResolution and manager.graphManager.activeCell.isSome:
    let currentCell = manager.graphManager.activeCell.get()
    if currentCell in manager.activeResolutions:
      # Resolution is already saved
      discard
  
  # Activate the new cell
  return manager.activateCell(cellName)

proc listAvailableCells*(manager: ResolverCellManager): seq[string] =
  ## List all available cells.
  ##
  ## **Requirements:** 10.4 - Support cell management
  
  return manager.graphManager.listCells()

# =============================================================================
# Cell Removal
# =============================================================================

proc removeCell*(
  manager: ResolverCellManager,
  cellName: string,
  cleanupPackages: bool = true
): CellRemovalResult =
  ## Remove a cell and optionally clean up its packages.
  ##
  ## **Requirements:** 10.5 - Clean up cell-specific packages
  ##
  ## **Parameters:**
  ## - cellName: Name of cell to remove
  ## - cleanupPackages: If true, remove all cell-specific packages
  ##
  ## **Returns:** Removal result with status and details
  
  # Check if cell exists
  if cellName notin manager.graphManager.cells:
    return CellRemovalResult(
      success: false,
      cellName: cellName,
      packagesRemoved: 0,
      error: fmt"Cell '{cellName}' not found"
    )
  
  # Get packages before removal
  let packages = manager.graphManager.getCellPackages(cellName)
  let packageCount = packages.len
  
  # Clean up packages if requested
  if cleanupPackages:
    for pkg in packages:
      discard manager.graphManager.removePackageFromCell(cellName, pkg)
  
  # Remove from active resolutions
  if cellName in manager.activeResolutions:
    manager.activeResolutions.del(cellName)
  
  # Remove from package cache
  if cellName in manager.cellPackageCache:
    manager.cellPackageCache.del(cellName)
  
  # Delete the cell
  let success = manager.graphManager.deleteCell(cellName)
  
  if not success:
    return CellRemovalResult(
      success: false,
      cellName: cellName,
      packagesRemoved: 0,
      error: fmt"Failed to delete cell '{cellName}'"
    )
  
  return CellRemovalResult(
    success: true,
    cellName: cellName,
    packagesRemoved: packageCount,
    error: ""
  )

# =============================================================================
# Package Management in Cells
# =============================================================================

proc addPackageToActiveCell*(
  manager: ResolverCellManager,
  packageName: string
): bool =
  ## Add a package to the currently active cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  if manager.graphManager.activeCell.isNone:
    return false
  
  let cellName = manager.graphManager.activeCell.get()
  
  # Add to graph manager
  let success = manager.graphManager.addPackageToCell(cellName, packageName)
  
  if success:
    # Update cache
    if cellName notin manager.cellPackageCache:
      manager.cellPackageCache[cellName] = initHashSet[string]()
    manager.cellPackageCache[cellName].incl(packageName)
  
  return success

proc removePackageFromActiveCell*(
  manager: ResolverCellManager,
  packageName: string
): bool =
  ## Remove a package from the currently active cell.
  ##
  ## **Requirements:** 10.5 - Clean up cell-specific packages
  
  if manager.graphManager.activeCell.isNone:
    return false
  
  let cellName = manager.graphManager.activeCell.get()
  
  # Remove from graph manager
  let success = manager.graphManager.removePackageFromCell(cellName, packageName)
  
  if success:
    # Update cache
    if cellName in manager.cellPackageCache:
      manager.cellPackageCache[cellName].excl(packageName)
  
  return success

proc getActiveCellPackages*(manager: ResolverCellManager): seq[string] =
  ## Get all packages in the currently active cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  if manager.graphManager.activeCell.isNone:
    return @[]
  
  let cellName = manager.graphManager.activeCell.get()
  return manager.graphManager.getCellPackages(cellName)

proc isPackageInActiveCell*(
  manager: ResolverCellManager,
  packageName: string
): bool =
  ## Check if a package is in the currently active cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  if manager.graphManager.activeCell.isNone:
    return false
  
  let cellName = manager.graphManager.activeCell.get()
  
  # Check cache first for performance
  if cellName in manager.cellPackageCache:
    return packageName in manager.cellPackageCache[cellName]
  
  # Fall back to graph manager
  return manager.graphManager.isPackageInCell(cellName, packageName)

# =============================================================================
# Resolution Integration
# =============================================================================

proc resolveInCell*(
  manager: ResolverCellManager,
  cellName: string,
  rootPackage: string,
  variantDemand: VariantDemand
): Option[DependencyGraph] =
  ## Resolve dependencies within a specific cell context.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  ##
  ## **Parameters:**
  ## - cellName: Name of cell to resolve in
  ## - rootPackage: Root package to resolve
  ## - variantDemand: Variant requirements
  ##
  ## **Returns:** Resolved dependency graph or None if resolution fails
  
  # Check if cell exists
  if cellName notin manager.graphManager.cells:
    return none(DependencyGraph)
  
  # Get cell graph
  let cellGraphOpt = manager.graphManager.getCellGraph(cellName)
  if cellGraphOpt.isNone:
    return none(DependencyGraph)
  
  # TODO: Integrate with actual resolver
  # For now, return the cell's existing graph
  let cellGraph = cellGraphOpt.get()
  return some(cellGraph.graph)

proc saveResolution*(
  manager: ResolverCellManager,
  cellName: string,
  graph: DependencyGraph
) =
  ## Save a resolved dependency graph for a cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  manager.activeResolutions[cellName] = graph

proc getResolution*(
  manager: ResolverCellManager,
  cellName: string
): Option[DependencyGraph] =
  ## Get the saved resolution for a cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  if cellName in manager.activeResolutions:
    return some(manager.activeResolutions[cellName])
  return none(DependencyGraph)

# =============================================================================
# Cell Information
# =============================================================================

proc getCellInfo*(
  manager: ResolverCellManager,
  cellName: string
): Option[NipCellGraph] =
  ## Get detailed information about a cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  return manager.graphManager.getCellGraph(cellName)

proc getCellStatistics*(
  manager: ResolverCellManager,
  cellName: string
): tuple[packageCount: int, lastModified: DateTime, created: DateTime] =
  ## Get statistics for a cell.
  ##
  ## **Requirements:** 10.3 - Maintain separate dependency graphs per cell
  
  let cellOpt = manager.graphManager.getCellGraph(cellName)
  
  if cellOpt.isNone:
    return (packageCount: 0, lastModified: now(), created: now())
  
  let cell = cellOpt.get()
  return (
    packageCount: cell.packages.len,
    lastModified: cell.lastModified,
    created: cell.created
  )

# =============================================================================
# Cleanup Operations
# =============================================================================

proc cleanupUnusedPackages*(
  manager: ResolverCellManager,
  cellName: string
): int =
  ## Clean up packages that are no longer referenced in the cell's graph.
  ##
  ## **Requirements:** 10.5 - Clean up cell-specific packages
  ##
  ## **Returns:** Number of packages removed
  
  let cellOpt = manager.graphManager.getCellGraph(cellName)
  if cellOpt.isNone:
    return 0
  
  let cell = cellOpt.get()
  var removedCount = 0
  
  # Get packages from graph (packages that are actually used)
  var usedPackages = initHashSet[string]()
  for term in cell.graph.terms.values:
    usedPackages.incl(term.packageName)
  
  # Find packages in cell that aren't in the graph
  for pkg in cell.packages:
    if pkg notin usedPackages:
      if manager.graphManager.removePackageFromCell(cellName, pkg):
        removedCount += 1
  
  return removedCount

proc cleanupAllCells*(manager: ResolverCellManager): Table[string, int] =
  ## Clean up unused packages in all cells.
  ##
  ## **Requirements:** 10.5 - Clean up cell-specific packages
  ##
  ## **Returns:** Map of cell name to number of packages removed
  
  var results = initTable[string, int]()
  
  for cellName in manager.graphManager.listCells():
    let removed = manager.cleanupUnusedPackages(cellName)
    if removed > 0:
      results[cellName] = removed
  
  return results

# =============================================================================
# String Representation
# =============================================================================

proc `$`*(manager: ResolverCellManager): string =
  ## String representation for debugging.
  
  let cellCount = manager.graphManager.listCells().len
  let activeCell = if manager.graphManager.activeCell.isSome:
    manager.graphManager.activeCell.get()
  else:
    "none"
  
  result = "ResolverCellManager(\n"
  result &= fmt"  cells: {cellCount}\n"
  result &= fmt"  active: {activeCell}\n"
  result &= fmt"  resolutions: {manager.activeResolutions.len}\n"
  result &= ")"
