## Flexible Source Adapter
##
## This module implements the flexible adapter for source-based package systems
## like Gentoo and NPK. Flexible sources can build packages on demand with
## custom variant profiles.
##
## Philosophy:
## - Flexible = build on demand with any variant
## - Maximum customization (USE flags, compiler options)
## - Slower deployment (build time required)
## - Perfect for custom configurations
##
## Examples:
## - Gentoo: Build with custom USE flags
## - NPK: Build with custom variant profiles
## - Source-only packages: Always build from source

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

type
  # Build function signature for flexible sources
  BuildFunction* = proc(demand: VariantDemand): Result[CasId, BuildError] {.closure.}

  # Flexible adapter for source-based builds
  FlexibleAdapter* = ref object of SourceAdapter
    availablePackages*: Table[string, PackageMetadata]  ## Packages that can be built
    buildFunc*: BuildFunction                            ## Function to build packages

# Constructor
proc newFlexibleAdapter*(
  name: string,
  priority: int = 30,
  buildFunc: BuildFunction = nil
): FlexibleAdapter =
  ## Create a new flexible adapter
  ## 
  ## Args:
  ##   name: Source name (e.g., "gentoo", "npk", "source")
  ##   priority: Selection priority (default: 30, lower than frozen)
  ##   buildFunc: Function to build packages (optional, for testing)
  
  result = FlexibleAdapter(
    name: name,
    class: Flexible,
    priority: priority,
    availablePackages: initTable[string, PackageMetadata](),
    buildFunc: buildFunc
  )

# Add a package that can be built
proc addPackage*(adapter: FlexibleAdapter, metadata: PackageMetadata) =
  ## Add a package that can be built from source
  ## 
  ## For flexible sources, the variant profile in metadata indicates
  ## what variants are possible, not what's pre-built.
  
  adapter.availablePackages[metadata.name] = metadata

# Check if adapter can satisfy a demand
method canSatisfy*(adapter: FlexibleAdapter, demand: VariantDemand): PackageAvailability =
  ## Check if this flexible source can satisfy a variant demand
  ## 
  ## For flexible sources, we can build any variant as long as the package exists.
  ## Returns Available if package exists, Unavailable otherwise.
  
  if adapter.availablePackages.hasKey(demand.packageName):
    return Available
  else:
    return Unavailable

# Get package metadata for a demand
method getVariant*(adapter: FlexibleAdapter, demand: VariantDemand): Option[PackageMetadata] =
  ## Get package metadata for a specific variant demand
  ## 
  ## For flexible sources, we return metadata indicating the package can be built
  ## with the requested variant profile.
  
  if not adapter.availablePackages.hasKey(demand.packageName):
    return none(PackageMetadata)
  
  # Return metadata with the requested variant
  var metadata = adapter.availablePackages[demand.packageName]
  
  # Update available variants to include the requested one
  # (flexible sources can build any variant)
  metadata.availableVariants = @[demand.variantProfile]
  
  return some(metadata)

# Synthesize a package with requested variant
method synthesize*(adapter: FlexibleAdapter, demand: VariantDemand): Result[CasId, BuildError] =
  ## Build a package with the requested variant profile
  ## 
  ## This is the core capability of flexible sources - building packages
  ## on demand with custom configurations.
  ## 
  ## Returns CasId on success, BuildError on failure.
  
  # Check if package exists
  if not adapter.availablePackages.hasKey(demand.packageName):
    return err[CasId, BuildError](BuildError(
      message: "Package not found: " & demand.packageName,
      exitCode: 1,
      buildLog: "Package " & demand.packageName & " is not available in source " & adapter.name
    ))
  
  # Use custom build function if provided (for testing)
  if adapter.buildFunc != nil:
    return adapter.buildFunc(demand)
  
  # Default implementation: simulate successful build
  # In production, this would invoke the actual build system
  let metadata = adapter.availablePackages[demand.packageName]
  let casId = newCasId(adapter.name & "-" & demand.packageName & "-" & demand.variantProfile.hash)
  
  return ok[CasId, BuildError](casId)

# Helper to create a mock build function for testing
proc mockBuildSuccess*(packageName: string, casId: string): BuildFunction =
  ## Create a mock build function that always succeeds
  ## 
  ## Useful for testing without actual build infrastructure
  
  result = proc(demand: VariantDemand): Result[CasId, BuildError] =
    if demand.packageName == packageName:
      return ok[CasId, BuildError](newCasId(casId))
    else:
      return err[CasId, BuildError](BuildError(
        message: "Package not found: " & demand.packageName,
        exitCode: 1,
        buildLog: "Mock build function only handles " & packageName
      ))

# Helper to create a mock build function that fails
proc mockBuildFailure*(errorMessage: string, exitCode: int = 1): BuildFunction =
  ## Create a mock build function that always fails
  ## 
  ## Useful for testing build failure scenarios
  
  result = proc(demand: VariantDemand): Result[CasId, BuildError] =
    return err[CasId, BuildError](BuildError(
      message: errorMessage,
      exitCode: exitCode,
      buildLog: "Mock build failure: " & errorMessage
    ))

