## Source Adapter Interface
##
## This module defines the abstraction for package sources in NIP's dependency
## resolution system. Different package ecosystems (Nix, AUR, Gentoo, etc.) are
## unified behind this interface.
##
## Philosophy:
## - Source adapters abstract away ecosystem differences
## - Frozen sources provide pre-built binaries (Nix, Arch)
## - Flexible sources build on demand (Gentoo, NPK)
## - Strategy pattern enables intelligent source selection
##
## The adapter system enables NIP to access 100,000+ packages from all ecosystems
## while maintaining a unified interface for the dependency resolver.

import std/[options, tables, algorithm]
import ./variant_types

# Result type for operations that can fail
type
  Result*[T, E] = object
    case isOk*: bool
    of true:
      value*: T
    of false:
      error*: E

proc ok*[T, E](value: T): Result[T, E] =
  Result[T, E](isOk: true, value: value)

proc err*[T, E](error: E): Result[T, E] =
  Result[T, E](isOk: false, error: error)

type
  # Source classification determines adapter behavior
  SourceClass* = enum
    Frozen,         ## Pre-built binaries only (Nix, Arch)
    Flexible,       ## Build on demand (Gentoo, NPK)
    FullyFlexible   ## Source-only, always build

  # Result of package lookup
  PackageAvailability* = enum
    Available,      ## Package exists and can be provided
    Unavailable,    ## Package doesn't exist in this source
    WrongVariant    ## Package exists but variant doesn't match

  # CAS identifier for built packages
  CasId* = distinct string

  # Build error information
  BuildError* = object
    message*: string
    exitCode*: int
    buildLog*: string

  # Base source adapter interface
  SourceAdapter* = ref object of RootObj
    name*: string           ## Source name (e.g., "nix", "aur", "gentoo")
    class*: SourceClass     ## Source classification
    priority*: int          ## Selection priority (higher = preferred)

  # Package metadata from source
  PackageMetadata* = object
    name*: string
    version*: string
    availableVariants*: seq[VariantProfile]
    dependencies*: seq[VariantDemand]
    sourceHash*: string
    buildTime*: int         ## Estimated build time in seconds (0 for frozen)

# String conversion for CasId
proc `$`*(id: CasId): string =
  string(id)

proc `==`*(a, b: CasId): bool =
  string(a) == string(b)

# Base methods for source adapters
method canSatisfy*(adapter: SourceAdapter, demand: VariantDemand): PackageAvailability {.base.} =
  ## Check if this source can satisfy a variant demand
  ## Returns Available, Unavailable, or WrongVariant
  ##
  ## This is the first step in source selection - quickly determine
  ## if this source has the package with the right variant.
  
  raise newException(CatchableError, "canSatisfy not implemented for " & adapter.name)

method getVariant*(adapter: SourceAdapter, demand: VariantDemand): Option[PackageMetadata] {.base.} =
  ## Get package metadata for a specific variant demand
  ## Returns Some(metadata) if available, None if not
  ##
  ## For frozen sources: returns metadata for exact variant match
  ## For flexible sources: returns metadata showing build is possible
  
  raise newException(CatchableError, "getVariant not implemented for " & adapter.name)

method synthesize*(adapter: SourceAdapter, demand: VariantDemand): Result[CasId, BuildError] {.base.} =
  ## Build a package with the requested variant profile
  ## Returns CasId on success, BuildError on failure
  ##
  ## Only applicable for Flexible and FullyFlexible sources
  ## Frozen sources should raise an error if called
  
  raise newException(CatchableError, "synthesize not implemented for " & adapter.name)

# Resolution strategy for source selection
type
  ResolutionStrategy* = enum
    PreferBinary,   ## Prefer frozen sources, fall back to flexible
    PreferSource,   ## Always build from source (flexible)
    Balanced        ## Consider recency, trust, and availability

  SourceSelection* = object
    adapter*: SourceAdapter
    reason*: string
    estimatedTime*: int

# Source selection function
proc selectSource*(
  adapters: seq[SourceAdapter],
  demand: VariantDemand,
  strategy: ResolutionStrategy
): Option[SourceSelection] =
  ## Select the best source adapter for a given demand
  ## Returns Some(selection) if a source can satisfy, None otherwise
  ##
  ## Strategy determines selection logic:
  ## - PreferBinary: Choose frozen first, fall back to flexible
  ## - PreferSource: Always choose flexible if available
  ## - Balanced: Consider multiple factors (recency, trust, build time)
  
  var candidates: seq[tuple[adapter: SourceAdapter, availability: PackageAvailability]] = @[]
  
  # Check all adapters for availability
  for adapter in adapters:
    let availability = adapter.canSatisfy(demand)
    if availability == Available:
      candidates.add((adapter, availability))
  
  if candidates.len == 0:
    return none(SourceSelection)
  
  # Apply strategy to select best candidate
  case strategy:
  of PreferBinary:
    # Prefer frozen sources (pre-built binaries)
    for (adapter, _) in candidates:
      if adapter.class == Frozen:
        return some(SourceSelection(
          adapter: adapter,
          reason: "Pre-built binary available",
          estimatedTime: 0
        ))
    
    # Fall back to flexible sources
    for (adapter, _) in candidates:
      if adapter.class in [Flexible, FullyFlexible]:
        let metadata = adapter.getVariant(demand)
        if metadata.isSome:
          return some(SourceSelection(
            adapter: adapter,
            reason: "Build from source (no binary available)",
            estimatedTime: metadata.get.buildTime
          ))
  
  of PreferSource:
    # Always prefer building from source
    for (adapter, _) in candidates:
      if adapter.class in [Flexible, FullyFlexible]:
        let metadata = adapter.getVariant(demand)
        if metadata.isSome:
          return some(SourceSelection(
            adapter: adapter,
            reason: "Build from source (user preference)",
            estimatedTime: metadata.get.buildTime
          ))
    
    # Fall back to frozen if no flexible source available
    for (adapter, _) in candidates:
      if adapter.class == Frozen:
        return some(SourceSelection(
          adapter: adapter,
          reason: "Pre-built binary (no source available)",
          estimatedTime: 0
        ))
  
  of Balanced:
    # Consider multiple factors: priority, build time, recency
    # Sort by priority (higher first)
    var sortedCandidates = candidates
    sortedCandidates.sort(proc(a, b: auto): int =
      b[0].priority - a[0].priority
    )
    
    # Return highest priority candidate
    if sortedCandidates.len > 0:
      let adapter = sortedCandidates[0][0]
      let metadata = adapter.getVariant(demand)
      let estimatedTime = if metadata.isSome: metadata.get.buildTime else: 0
      
      return some(SourceSelection(
        adapter: adapter,
        reason: "Best balance of priority and availability",
        estimatedTime: estimatedTime
      ))
  
  return none(SourceSelection)

# Helper to create CasId from string
proc newCasId*(id: string): CasId =
  CasId(id)

# String representation for debugging
proc `$`*(selection: SourceSelection): string =
  result = "SourceSelection("
  result.add("adapter=" & selection.adapter.name)
  result.add(", reason=\"" & selection.reason & "\"")
  result.add(", estimatedTime=" & $selection.estimatedTime & "s")
  result.add(")")

proc `$`*(availability: PackageAvailability): string =
  case availability:
  of Available: "Available"
  of Unavailable: "Unavailable"
  of WrongVariant: "WrongVariant"

proc `$`*(class: SourceClass): string =
  case class:
  of Frozen: "Frozen"
  of Flexible: "Flexible"
  of FullyFlexible: "FullyFlexible"

