# SPDX-License-Identifier: LSL-1.0
# Copyright (c) 2026 Markus Maiwald
# Stewardship: Self Sovereign Society Foundation
#
# This file is part of the Nexus Sovereign Core.
# See legal/LICENSE_SOVEREIGN.md for license terms.

## nimpak/build_system.nim
## Nimplate Build System Integration
##
## Type-safe build templates (Nimplates) for reproducible source compilation.
## Supports CMake, Meson, Autotools, Cargo, and custom build systems with
## environment isolation, caching, and incremental compilation.

import std/[os, strutils, strformat, tables, sequtils, osproc, times]
import types_fixed

type
  BuildError* = object of CatchableError
    buildSystem*: BuildSystemType
    phase*: string
    exitCode*: int

  BuildPhase* = enum
    PhaseSetup, PhaseConfigure, PhaseBuild, PhaseInstall, PhaseTest

  BuildResult* = object
    success*: bool
    buildTime*: float
    outputSize*: int64
    buildLog*: string
    artifacts*: seq[string]
    phase*: BuildPhase
    exitCode*: int

  BuildEnvironment* = object
    workDir*: string
    sourceDir*: string
    buildDir*: string
    installDir*: string
    environment*: Table[string, string]
    isolated*: bool

  NimplateExecutor* = ref object
    environment*: BuildEnvironment
    buildTemplate*: BuildTemplate
    cacheEnabled*: bool
    sandboxed*: bool

# =============================================================================
# Core Nimplate System
# =============================================================================

proc newBuildEnvironment*(sourceDir: string, isolated: bool = true): BuildEnvironment =
  ## Create a new isolated build environment
  let workDir = if isolated: getTempDir() / "nimpak_build_" & $epochTime().int
               else: sourceDir / "build"

  result = BuildEnvironment(
    workDir: workDir,
    sourceDir: sourceDir,
    buildDir: workDir / "build",
    installDir: workDir / "install",
    environment: initTable[string, string](),
    isolated: isolated
  )

  # Set up standard environment variables
  result.environment["PREFIX"] = result.installDir
  result.environment["DESTDIR"] = result.installDir
  result.environment["MAKEFLAGS"] = "-j" & $countProcessors()

proc newNimplateExecutor*(buildTmpl: BuildTemplate, sourceDir: string): NimplateExecutor =
  ## Create a new Nimplate executor for the given build template
  result = NimplateExecutor(
    environment: newBuildEnvironment(sourceDir),
    buildTemplate: buildTmpl,
    cacheEnabled: true,
    sandboxed: true
  )

# =============================================================================
# Build System Implementations
# =============================================================================

proc executeCMakeBuild*(executor: NimplateExecutor): BuildResult =
  ## Execute CMake build using Nimplate
  var buildResult = BuildResult(phase: PhaseConfigure)
  let startTime = cpuTime()

  try:
    # Create build directory
    createDir(executor.environment.buildDir)

    # Configure phase
    let configureCmd = "cmake " & executor.environment.sourceDir & " " &
                      executor.buildTemplate.configureArgs.join(" ")

    let (configOutput, configCode) = execCmdEx(configureCmd, workingDir = executor.environment.buildDir)
    buildResult.buildLog.add("=== Configure Phase ===\n" & configOutput & "\n")

    if configCode != 0:
      buildResult.exitCode = configCode
      buildResult.success = false
      return buildResult

    # Build phase
    buildResult.phase = PhaseBuild
    let buildCmd = "cmake --build . " & executor.buildTemplate.buildArgs.join(" ")

    let (buildOutput, buildCode) = execCmdEx(buildCmd, workingDir = executor.environment.buildDir)
    buildResult.buildLog.add("=== Build Phase ===\n" & buildOutput & "\n")

    if buildCode != 0:
      buildResult.exitCode = buildCode
      buildResult.success = false
      return buildResult

    # Install phase
    buildResult.phase = PhaseInstall
    let installCmd = "cmake --install . --prefix " & executor.environment.installDir & " " &
                    executor.buildTemplate.installArgs.join(" ")

    let (installOutput, installCode) = execCmdEx(installCmd, workingDir = executor.environment.buildDir)
    buildResult.buildLog.add("=== Install Phase ===\n" & installOutput & "\n")

    buildResult.success = installCode == 0
    buildResult.exitCode = installCode
    buildResult.buildTime = cpuTime() - startTime

    # Collect artifacts
    if dirExists(executor.environment.installDir):
      for file in walkDirRec(executor.environment.installDir):
        buildResult.artifacts.add(file)
      buildResult.outputSize = buildResult.artifacts.mapIt(getFileSize(it)).foldl(a + b, 0'i64)

  except Exception as e:
    buildResult.success = false
    buildResult.buildLog.add("Build failed: " & e.msg)

  return buildResult

proc executeAutotoolsBuild*(executor: NimplateExecutor): BuildResult =
  ## Execute Autotools build using Nimplate
  var buildResult = BuildResult(phase: PhaseConfigure)
  let startTime = cpuTime()

  try:
    # Configure phase
    let configureScript = executor.environment.sourceDir / "configure"
    if not fileExists(configureScript):
      buildResult.buildLog.add("Error: configure script not found\n")
      buildResult.success = false
      return buildResult

    let configureCmd = configureScript & " " & executor.buildTemplate.configureArgs.join(" ")

    let (configOutput, configCode) = execCmdEx(configureCmd, workingDir = executor.environment.sourceDir)
    buildResult.buildLog.add("=== Configure Phase ===\n" & configOutput & "\n")

    if configCode != 0:
      buildResult.exitCode = configCode
      buildResult.success = false
      return buildResult

    # Build phase
    buildResult.phase = PhaseBuild
    let buildCmd = "make " & executor.buildTemplate.buildArgs.join(" ")

    let (buildOutput, buildCode) = execCmdEx(buildCmd, workingDir = executor.environment.sourceDir)
    buildResult.buildLog.add("=== Build Phase ===\n" & buildOutput & "\n")

    if buildCode != 0:
      buildResult.exitCode = buildCode
      buildResult.success = false
      return buildResult

    # Install phase
    buildResult.phase = PhaseInstall
    let installCmd = "make install " & executor.buildTemplate.installArgs.join(" ")

    let (installOutput, installCode) = execCmdEx(installCmd, workingDir = executor.environment.sourceDir)
    buildResult.buildLog.add("=== Install Phase ===\n" & installOutput & "\n")

    buildResult.success = installCode == 0
    buildResult.exitCode = installCode
    buildResult.buildTime = cpuTime() - startTime

    # Collect artifacts
    if dirExists(executor.environment.installDir):
      for file in walkDirRec(executor.environment.installDir):
        buildResult.artifacts.add(file)
      buildResult.outputSize = buildResult.artifacts.mapIt(getFileSize(it)).foldl(a + b, 0'i64)

  except Exception as e:
    buildResult.success = false
    buildResult.buildLog.add("Build failed: " & e.msg)

  return buildResult

# =============================================================================
# Main Nimplate Execution Engine
# =============================================================================

proc executeNimplate*(executor: NimplateExecutor): BuildResult =
  ## Execute build using the appropriate Nimplate for the build system
  echo fmt"🔨 Building with {executor.buildTemplate.system} Nimplate..."
  echo fmt"   Source: {executor.environment.sourceDir}"
  echo fmt"   Build: {executor.environment.buildDir}"
  echo fmt"   Install: {executor.environment.installDir}"

  # Create necessary directories
  if executor.environment.isolated:
    createDir(executor.environment.workDir)
    createDir(executor.environment.buildDir)
    createDir(executor.environment.installDir)

  # Execute based on build system type
  case executor.buildTemplate.system:
  of CMake:
    return executeCMakeBuild(executor)
  of Autotools:
    return executeAutotoolsBuild(executor)
  of Meson, Cargo, NimBuild:
    # Simplified implementations for now
    var buildResult = BuildResult(phase: PhaseBuild)
    buildResult.buildLog.add(fmt"Build system {executor.buildTemplate.system} not fully implemented yet\n")
    buildResult.success = false
    return buildResult
  of Custom:
    # Custom build system - execute user-provided commands
    var buildResult = BuildResult(phase: PhaseBuild)
    buildResult.buildLog.add("Custom build system not yet implemented\n")
    buildResult.success = false
    return buildResult

proc buildFromTemplate*(buildTmpl: BuildTemplate, sourceDir: string): BuildResult =
  ## High-level function to build from a source directory using a template
  let executor = newNimplateExecutor(buildTmpl, sourceDir)
  return executeNimplate(executor)

# =============================================================================
# Build Caching and Incremental Compilation
# =============================================================================

proc calculateBuildHash*(sourceDir: string, buildTmpl: BuildTemplate): string =
  ## Calculate hash for build caching based on source and template
  # This is a simplified implementation - in production would use proper hashing
  let sourceHash = $getLastModificationTime(sourceDir).toUnix()
  let templateHash = $buildTmpl.system & buildTmpl.configureArgs.join("") & buildTmpl.buildArgs.join("")
  return "build-" & sourceHash & "-" & templateHash

proc isBuildCached*(buildHash: string, cacheDir: string): bool =
  ## Check if build result is already cached
  let cacheFile = cacheDir / buildHash & ".cache"
  return fileExists(cacheFile)

proc saveBuildCache*(buildResult: BuildResult, buildHash: string, cacheDir: string) =
  ## Save build result to cache
  createDir(cacheDir)
  let cacheFile = cacheDir / buildHash & ".cache"
  # In production, would serialize BuildResult to JSON/binary format
  writeFile(cacheFile, buildResult.buildLog)

# =============================================================================
# Build Environment Isolation and Sandboxing
# =============================================================================

proc setupSandbox*(environment: var BuildEnvironment) =
  ## Set up sandboxed build environment (simplified implementation)
  # In production, would use containers, chroot, or other isolation mechanisms
  environment.environment["SANDBOX"] = "true"
  environment.environment["HOME"] = environment.workDir
  environment.environment["TMPDIR"] = environment.workDir / "tmp"
  createDir(environment.environment["TMPDIR"])

proc cleanupBuildEnvironment*(environment: BuildEnvironment) =
  ## Clean up build environment after build completion
  if environment.isolated and dirExists(environment.workDir):
    try:
      removeDir(environment.workDir)
    except:
      discard # Best effort cleanup

# =============================================================================
# Export main functions
# =============================================================================

export BuildSystemType, BuildTemplate, BuildResult, BuildEnvironment
export NimplateExecutor, BuildPhase, BuildError
export newNimplateExecutor, executeNimplate, buildFromTemplate
export calculateBuildHash, isBuildCached, saveBuildCache
export setupSandbox, cleanupBuildEnvironment