## Binary Serialization Layer for Dependency Resolution Caching
##
## This module provides MessagePack-based serialization for DependencyGraph
## and related structures, ensuring deterministic, compact binary representation
## for cache storage and retrieval.
##
## **Design Principles:**
## - Deterministic: Same graph always produces identical binary output
## - Compact: MessagePack provides efficient binary encoding
## - Fast: Minimal overhead for serialization/deserialization
## - Canonical: Sorted keys and stable ordering guarantee reproducibility
##
## **Cache Invalidation Strategy:**
## The cache key includes a global repository state hash, ensuring that any
## change to package metadata automatically invalidates stale cache entries.

import msgpack4nim
import tables
import algorithm
import sequtils
import sets
import strutils
import ./variant_types
import ./dependency_graph
import ../utils/hashing
import ../manifest_parser  # For SemanticVersion

# ============================================================================
# Canonical Serialization Helpers
# ============================================================================

proc canonicalizeVariantDemand*(demand: VariantDemand): string =
  ## Convert VariantDemand to canonical string representation.
  ## Ensures deterministic ordering of flags and settings.
  var parts: seq[string]
  
  # Add package name
  parts.add("pkg:" & demand.packageName)
  
  # Add variant profile (sorted domains and flags)
  var sortedDomains: seq[string] = @[]
  for k in demand.variantProfile.domains.keys:
    sortedDomains.add(k)
  sortedDomains.sort()
  
  for domainName in sortedDomains:
    let domain = demand.variantProfile.domains[domainName]
    var sortedFlags: seq[string] = @[]
    for flag in domain.flags:
      sortedFlags.add(flag)
    sortedFlags.sort()
    
    let exclusive = if domain.exclusivity == Exclusive: "!" else: ""
    parts.add(domainName & exclusive & ":" & sortedFlags.join(","))
  
  # Add optional flag
  if demand.optional:
    parts.add("optional:true")
  
  return parts.join("|")

proc canonicalizePackageTerm*(term: PackageTerm): string =
  ## Convert PackageTerm to canonical string representation.
  return term.packageName & "@" & $term.version & "#" & term.variantProfile.hash

proc canonicalizePackageTermId*(id: PackageTermId): string =
  ## Convert PackageTermId to canonical string representation.
  return $id

# ============================================================================
# DependencyGraph Serialization
# ============================================================================

type
  SerializedTerm = object
    ## Intermediate representation for MessagePack encoding
    id: string
    packageName: string
    version: string
    variantHash: string
    optional: bool
    source: string

  SerializedEdge = object
    ## Serialized dependency edge
    fromId: string
    toId: string
    depType: string

  SerializedGraph = object
    ## Complete serialized dependency graph
    terms: seq[SerializedTerm]
    edges: seq[SerializedEdge]

proc toSerializedTerm(term: PackageTerm): SerializedTerm =
  ## Convert PackageTerm to serializable form
  result.id = $term.id
  result.packageName = term.packageName
  result.version = $term.version
  result.variantHash = term.variantProfile.hash
  result.optional = term.optional
  result.source = term.source

proc toSerializedEdge(edge: DependencyEdge): SerializedEdge =
  ## Convert DependencyEdge to serializable form
  result.fromId = $edge.fromTerm
  result.toId = $edge.toTerm
  result.depType = $edge.dependencyType

proc toMessagePack*(graph: DependencyGraph): string =
  ## Serialize DependencyGraph to MessagePack binary format.
  ## 
  ## **Guarantees:**
  ## - Deterministic: Same graph always produces identical output
  ## - Canonical: Terms sorted by ID for stable ordering
  ## - Complete: All metadata and relationships preserved
  ##
  ## **Returns:** Binary MessagePack string
  
  var sgraph = SerializedGraph()
  
  # Convert all terms to serialized form
  sgraph.terms = newSeq[SerializedTerm]()
  for id, term in graph.terms:
    sgraph.terms.add(toSerializedTerm(term))
  
  # Sort terms by ID for determinism
  sgraph.terms.sort(proc(a, b: SerializedTerm): int =
    cmp(a.id, b.id)
  )
  
  # Convert all edges to serialized form
  sgraph.edges = newSeq[SerializedEdge]()
  for edge in graph.edges:
    sgraph.edges.add(toSerializedEdge(edge))
  
  # Sort edges for determinism
  sgraph.edges.sort(proc(a, b: SerializedEdge): int =
    let cmpFrom = cmp(a.fromId, b.fromId)
    if cmpFrom != 0: cmpFrom else: cmp(a.toId, b.toId)
  )
  
  # Pack to MessagePack binary
  return pack(sgraph)

proc fromMessagePack*(data: string): DependencyGraph =
  ## Deserialize DependencyGraph from MessagePack binary format.
  ##
  ## **Parameters:**
  ## - data: Binary MessagePack string
  ##
  ## **Returns:** Reconstructed DependencyGraph
  ##
  ## **Raises:** UnpackError if data is corrupted or invalid
  ##
  ## **Note:** This is a simplified reconstruction that may not preserve
  ## all graph invariants. Use with caution.
  
  let sgraph = unpack(data, SerializedGraph)
  
  result = DependencyGraph(
    terms: initTable[PackageTermId, PackageTerm](),
    edges: @[],
    incomingEdges: initTable[PackageTermId, seq[DependencyEdge]](),
    outgoingEdges: initTable[PackageTermId, seq[DependencyEdge]]()
  )
  
  # Reconstruct terms (simplified - doesn't fully reconstruct VariantProfile)
  for sterm in sgraph.terms:
    let id = PackageTermId(sterm.id)
    let term = PackageTerm(
      id: id,
      packageName: sterm.packageName,
      version: parseSemanticVersion(sterm.version),
      variantProfile: VariantProfile(
        domains: initTable[string, VariantDomain](),
        hash: sterm.variantHash
      ),
      optional: sterm.optional,
      source: sterm.source
    )
    result.terms[id] = term
  
  # Reconstruct edges
  for sedge in sgraph.edges:
    let edge = DependencyEdge(
      fromTerm: PackageTermId(sedge.fromId),
      toTerm: PackageTermId(sedge.toId),
      dependencyType: Required  # Simplified
    )
    result.edges.add(edge)

# ============================================================================
# Cache Key Calculation
# ============================================================================

proc calculateGlobalRepoStateHash*(metadataStrings: seq[string]): string =
  ## Calculate deterministic hash of all repository metadata.
  ##
  ## **Purpose:** This hash serves as the cache invalidation key. Any change
  ## to package metadata will produce a different hash, automatically
  ## invalidating stale cache entries.
  ##
  ## **Algorithm:**
  ## 1. Sort all metadata strings lexicographically
  ## 2. Serialize sorted list to MessagePack
  ## 3. Hash the final binary with xxh3_128
  ##
  ## **Guarantees:**
  ## - Deterministic: Same repo state always produces same hash
  ## - Sensitive: Any metadata change produces different hash
  ## - Fast: xxh3_128 provides high-speed hashing
  ##
  ## **Returns:** 128-bit hash as hex string
  
  var sortedMetadata = metadataStrings
  sortedMetadata.sort()
  
  # Pack sorted metadata and compute final hash
  let sortedBinary = pack(sortedMetadata)
  return xxh3_128(sortedBinary)

proc calculateCacheKey*(rootPackage: string, rootConstraint: string, 
                       repoStateHash: string, demand: VariantDemand): string =
  ## Calculate deterministic cache key using xxh3_128.
  ##
  ## **Purpose:** Generate a unique, deterministic identifier for a specific
  ## dependency resolution request. The key captures all inputs that affect
  ## the resolution result.
  ##
  ## **Components:**
  ## - Root package name and constraint
  ## - Global repository state hash (for invalidation)
  ## - Variant demand (canonicalized)
  ##
  ## **Algorithm:**
  ## 1. Canonicalize variant demand (sorted flags, stable ordering)
  ## 2. Assemble all components in fixed order
  ## 3. Serialize to MessagePack binary
  ## 4. Hash with xxh3_128
  ##
  ## **Guarantees:**
  ## - Deterministic: Same inputs always produce same key
  ## - Unique: Different inputs produce different keys (with high probability)
  ## - Fast: xxh3_128 provides high-speed hashing
  ##
  ## **Returns:** 128-bit hash as hex string
  
  # Canonicalize the most complex structure
  let canonicalDemand = canonicalizeVariantDemand(demand)
  
  # Assemble all components in fixed order
  let components = @[
    rootPackage,
    rootConstraint,
    repoStateHash,
    canonicalDemand
  ]
  
  # Serialize to canonical binary
  let encoded = pack(components)
  
  # Hash the binary
  return xxh3_128(encoded)

# ============================================================================
# Serialization Tests (Determinism Verification)
# ============================================================================
# Note: Tests moved to tests/test_serialization.nim to use proper test fixtures
