## variant_paths.nim
## Variant path management for NIP
## Generates and validates content-addressed variant installation paths

import std/[strutils, os]
import variant_types

# #############################################################################
# Path Constants
# #############################################################################

const
  PROGRAMS_DIR* = "/Programs"
  # Supported hash algorithm prefixes for forward compatibility
  SUPPORTED_HASH_PREFIXES* = ["blake2b-", "blake3-"]

# #############################################################################
# Path Generation
# #############################################################################

proc generateVariantPath*(
  packageName: string,
  version: string,
  fingerprint: string
): string =
  ## Generate variant installation path
  ## Format: /Programs/<Name>/<Version>-<Fingerprint>/
  ##
  ## Example: /Programs/nginx/1.28.0-blake2b-abc123def456/

  if packageName.len == 0:
    raise newException(ValueError, "Package name cannot be empty")

  if version.len == 0:
    raise newException(ValueError, "Version cannot be empty")

  if fingerprint.len == 0:
    raise newException(ValueError, "Fingerprint cannot be empty")

  # Construct the path
  let versionWithFingerprint = version & "-" & fingerprint
  result = PROGRAMS_DIR / packageName / versionWithFingerprint

proc generateVariantPathFromFingerprint*(
  fp: VariantFingerprint
): string =
  ## Generate variant path from a VariantFingerprint object
  result = generateVariantPath(fp.packageName, fp.version, fp.hash)

# #############################################################################
# Path Validation
# #############################################################################

proc validateVariantPath*(path: string): bool =
  ## Validate variant path format
  ## Expected: /Programs/<Name>/<Version>-<Fingerprint>/
  ## Supports multiple hash algorithms: blake2b-, blake3-, etc.

  if path.len == 0:
    return false

  # Split path into components
  let parts = path.split('/')

  # Need at least: "", "Programs", "PackageName", "Version-Fingerprint"
  if parts.len < 4:
    return false

  # Check if it starts with /Programs
  if parts[1] != "Programs":
    return false

  # Check package name is not empty
  if parts[2].len == 0:
    return false

  # Check version-fingerprint format
  # Must contain at least one supported hash algorithm prefix
  let versionPart = parts[3]
  var hasValidHash = false
  for prefix in SUPPORTED_HASH_PREFIXES:
    if versionPart.contains("-" & prefix):
      hasValidHash = true
      break

  if not hasValidHash:
    return false

  return true

proc isValidVariantPath*(path: string): bool =
  ## Alias for validateVariantPath
  result = validateVariantPath(path)

proc detectHashAlgorithm*(path: string): string =
  ## Detect which hash algorithm is used in a variant path
  ## Returns: "blake2b", "blake3", or "" if unknown

  if not validateVariantPath(path):
    return ""

  let parts = path.split('/')
  let versionPart = parts[3]

  for prefix in SUPPORTED_HASH_PREFIXES:
    if versionPart.contains("-" & prefix):
      # Return algorithm name without the trailing dash
      return prefix[0..^2]

  return ""

# #############################################################################
# Path Parsing
# #############################################################################

proc extractPackageNameFromPath*(path: string): string =
  ## Extract package name from variant path
  ## Example: /Programs/nginx/1.28.0-blake2b-abc123/ → nginx

  if not validateVariantPath(path):
    raise newException(ValueError, "Invalid variant path: " & path)

  let parts = path.split('/')
  result = parts[2]

proc extractVersionFromPath*(path: string): string =
  ## Extract version from variant path
  ## Example: /Programs/nginx/1.28.0-blake2b-abc123/ → 1.28.0
  ## Works with any supported hash algorithm (blake2b, blake3, etc.)

  if not validateVariantPath(path):
    raise newException(ValueError, "Invalid variant path: " & path)

  let parts = path.split('/')
  let versionPart = parts[3]

  # Find the fingerprint separator (try all supported hash prefixes)
  var sepPos = -1
  for prefix in SUPPORTED_HASH_PREFIXES:
    let pos = versionPart.find("-" & prefix)
    if pos > 0:
      sepPos = pos
      break

  if sepPos > 0:
    result = versionPart[0..<sepPos]
  else:
    result = versionPart

proc extractFingerprintFromPath*(path: string): string =
  ## Extract fingerprint from variant path
  ## Example: /Programs/nginx/1.28.0-blake2b-abc123/ → blake2b-abc123
  ## Works with any supported hash algorithm (blake2b, blake3, etc.)

  if not validateVariantPath(path):
    raise newException(ValueError, "Invalid variant path: " & path)

  let parts = path.split('/')
  let versionPart = parts[3]

  # Find the fingerprint separator (try all supported hash prefixes)
  var sepPos = -1
  for prefix in SUPPORTED_HASH_PREFIXES:
    let pos = versionPart.find("-" & prefix)
    if pos >= 0:
      sepPos = pos
      break

  if sepPos >= 0:
    result = versionPart[sepPos+1..^1]
  else:
    raise newException(ValueError, "Could not extract fingerprint from: " & versionPart)


# #############################################################################
# Path Queries
# #############################################################################

proc getVariantBasePath*(packageName: string): string =
  ## Get the base path for all variants of a package
  ## Example: nginx → /Programs/nginx/
  result = PROGRAMS_DIR / packageName

proc listVariantPaths*(packageName: string): seq[string] =
  ## List all variant paths for a package
  ## Returns empty sequence if package directory doesn't exist
  result = @[]

  let basePath = getVariantBasePath(packageName)

  if not dirExists(basePath):
    return

  for kind, path in walkDir(basePath):
    if kind == pcDir:
      let fullPath = path & "/"
      if validateVariantPath(fullPath):
        result.add(fullPath)

proc variantPathExists*(path: string): bool =
  ## Check if a variant path exists on the filesystem
  result = dirExists(path)

proc findVariantByFingerprint*(packageName: string, fingerprint: string): string =
  ## Find a variant path by package name and fingerprint
  ## Returns empty string if not found

  let variants = listVariantPaths(packageName)

  for variantPath in variants:
    try:
      let pathFingerprint = extractFingerprintFromPath(variantPath)
      if pathFingerprint == fingerprint:
        return variantPath
    except ValueError:
      continue

  return ""

# #############################################################################
# Path Comparison
# #############################################################################

proc isSameVariant*(path1: string, path2: string): bool =
  ## Check if two paths refer to the same variant
  ## Compares by fingerprint

  if not validateVariantPath(path1) or not validateVariantPath(path2):
    return false

  try:
    let fp1 = extractFingerprintFromPath(path1)
    let fp2 = extractFingerprintFromPath(path2)
    result = fp1 == fp2
  except ValueError:
    result = false

proc compareVariantPaths*(path1: string, path2: string): int =
  ## Compare two variant paths
  ## Returns: -1 if path1 < path2, 0 if equal, 1 if path1 > path2
  ## Comparison is based on version then fingerprint

  if not validateVariantPath(path1) or not validateVariantPath(path2):
    return 0

  try:
    let pkg1 = extractPackageNameFromPath(path1)
    let pkg2 = extractPackageNameFromPath(path2)

    if pkg1 != pkg2:
      return cmp(pkg1, pkg2)

    let ver1 = extractVersionFromPath(path1)
    let ver2 = extractVersionFromPath(path2)

    if ver1 != ver2:
      return cmp(ver1, ver2)

    let fp1 = extractFingerprintFromPath(path1)
    let fp2 = extractFingerprintFromPath(path2)

    return cmp(fp1, fp2)
  except ValueError:
    return 0

# #############################################################################
# Path Utilities
# #############################################################################

proc normalizeVariantPath*(path: string): string =
  ## Normalize a variant path (ensure trailing slash, absolute path)
  result = path.strip()

  if not result.endsWith("/"):
    result.add("/")

  if not result.startsWith("/"):
    result = "/" & result

proc getVariantBinPath*(variantPath: string): string =
  ## Get the bin directory path for a variant
  ## Example: /Programs/nginx/1.28.0-blake2b-abc123/ → /Programs/nginx/1.28.0-blake2b-abc123/bin
  result = variantPath / "bin"

proc getVariantLibPath*(variantPath: string): string =
  ## Get the lib directory path for a variant
  result = variantPath / "lib"

proc getVariantIncludePath*(variantPath: string): string =
  ## Get the include directory path for a variant
  result = variantPath / "include"

proc getVariantSharePath*(variantPath: string): string =
  ## Get the share directory path for a variant
  result = variantPath / "share"

# #############################################################################
# Path Information
# #############################################################################

type
  VariantPathInfo* = object
    ## Parsed information from a variant path
    fullPath*: string
    packageName*: string
    version*: string
    fingerprint*: string
    isValid*: bool

proc parseVariantPath*(path: string): VariantPathInfo =
  ## Parse a variant path into its components
  result.fullPath = path
  result.isValid = validateVariantPath(path)

  if result.isValid:
    try:
      result.packageName = extractPackageNameFromPath(path)
      result.version = extractVersionFromPath(path)
      result.fingerprint = extractFingerprintFromPath(path)
    except ValueError:
      result.isValid = false

proc `$`*(info: VariantPathInfo): string =
  ## String representation of VariantPathInfo
  if info.isValid:
    result = "VariantPath(" & info.packageName & " " & info.version & " " & info.fingerprint & ")"
  else:
    result = "VariantPath(invalid: " & info.fullPath & ")"

# #############################################################################
# Path Conflicts
# #############################################################################

proc detectPathConflicts*(paths: seq[string]): seq[tuple[path1: string, path2: string]] =
  ## Detect conflicting variant paths (same package/version, different fingerprints)
  ## This should never happen in a properly functioning system
  result = @[]

  for i in 0..<paths.len:
    for j in (i+1)..<paths.len:
      if not validateVariantPath(paths[i]) or not validateVariantPath(paths[j]):
        continue

      try:
        let pkg1 = extractPackageNameFromPath(paths[i])
        let pkg2 = extractPackageNameFromPath(paths[j])
        let ver1 = extractVersionFromPath(paths[i])
        let ver2 = extractVersionFromPath(paths[j])
        let fp1 = extractFingerprintFromPath(paths[i])
        let fp2 = extractFingerprintFromPath(paths[j])

        # Same package and version but different fingerprints = conflict
        if pkg1 == pkg2 and ver1 == ver2 and fp1 != fp2:
          result.add((paths[i], paths[j]))
      except ValueError:
        continue
