## nimpak/cli/core.nim
## Core CLI infrastructure for NimPak
##
## This module provides the foundation for all CLI commands including:
## - Command parsing and dispatch
## - Output formatting (colorful, plain, structured)
## - Error handling and user feedback
## - Global options processing

import std/[os, strutils, times, json, tables, terminal, strformat]
import ../types_fixed

# =============================================================================
# Output Format Conversion Helpers
# =============================================================================

proc jsonToYaml(node: JsonNode, indent: int = 0): string =
  ## Convert JSON to YAML-like format
  let spaces = "  ".repeat(indent)
  case node.kind:
  of JObject:
    result = ""
    for key, value in node.pairs:
      result.add(spaces & key & ": ")
      if value.kind in {JObject, JArray}:
        result.add("\n" & jsonToYaml(value, indent + 1))
      else:
        result.add(jsonToYaml(value, 0) & "\n")
  of JArray:
    result = ""
    for item in node.items:
      result.add(spaces & "- ")
      if item.kind in {JObject, JArray}:
        result.add("\n" & jsonToYaml(item, indent + 1))
      else:
        result.add(jsonToYaml(item, 0) & "\n")
  of JString:
    result = "\"" & node.str.replace("\"", "\\\"") & "\""
  of JInt:
    result = $node.num
  of JFloat:
    result = $node.fnum
  of JBool:
    result = $node.bval
  of JNull:
    result = "null"

proc jsonToKdl(node: JsonNode, indent: int = 0): string =
  ## Convert JSON to KDL-like format
  let spaces = "  ".repeat(indent)
  case node.kind:
  of JObject:
    result = "{\n"
    for key, value in node.pairs:
      result.add(spaces & "  " & key & " ")
      if value.kind == JObject:
        result.add(jsonToKdl(value, indent + 1) & "\n")
      elif value.kind == JArray:
        result.add("[\n")
        for item in value.items:
          result.add(spaces & "    " & jsonToKdl(item, indent + 2) & "\n")
        result.add(spaces & "  ]\n")
      else:
        result.add(jsonToKdl(value, 0) & "\n")
    result.add(spaces & "}")
  of JArray:
    result = "["
    var first = true
    for item in node.items:
      if not first: result.add(" ")
      result.add(jsonToKdl(item, 0))
      first = false
    result.add("]")
  of JString:
    result = "\"" & node.str.replace("\"", "\\\"") & "\""
  of JInt:
    result = $node.num
  of JFloat:
    result = $node.fnum
  of JBool:
    result = $node.bval
  of JNull:
    result = "null"

type
  OutputFormat* = enum
    OutputHuman = "human"
    OutputJson = "json"
    OutputYaml = "yaml"
    OutputKdl = "kdl"

  LogLevel* = enum
    LogDebug = "debug"
    LogInfo = "info"
    LogWarn = "warn"
    LogError = "error"

  GlobalOptions* = object
    outputFormat*: OutputFormat
    logLevel*: LogLevel
    dryRun*: bool
    noColor*: bool
    verbose*: bool
    quiet*: bool

  CliContext* = object
    options*: GlobalOptions
    startTime*: times.DateTime

  CommandResult* = object
    success*: bool
    exitCode*: int
    message*: string
    data*: JsonNode

# =============================================================================
# Global CLI State
# =============================================================================

var globalContext*: CliContext

proc initCliContext*(options: GlobalOptions): CliContext =
  ## Initialize CLI context with global options
  result = CliContext(
    options: options,
    startTime: now()
  )
  globalContext = result

# =============================================================================
# Output Formatting and Colors
# =============================================================================

proc isColorEnabled*(): bool =
  ## Check if colored output should be used
  not globalContext.options.noColor and isatty(stdout)

proc colorize*(text: string, color: ForegroundColor): string =
  ## Apply color to text if colors are enabled
  if isColorEnabled():
    result = ansiForegroundColorCode(color) & text & ansiResetCode
  else:
    result = text

proc bold*(text: string): string =
  ## Make text bold if colors are enabled
  if isColorEnabled():
    result = ansiStyleCode(styleBright) & text & ansiResetCode
  else:
    result = text

# Color helpers for common use cases
proc success*(text: string): string = colorize(text, fgGreen)
proc error*(text: string): string = colorize(text, fgRed)
proc warning*(text: string): string = colorize(text, fgYellow)
proc info*(text: string): string = colorize(text, fgBlue)
proc highlight*(text: string): string = colorize(text, fgCyan)

# Symbol helpers for modern CLI design
proc successSymbol*(): string =
  if isColorEnabled(): "✅" else: "[OK]"

proc errorSymbol*(): string =
  if isColorEnabled(): "❌" else: "[ERROR]"

proc warningSymbol*(): string =
  if isColorEnabled(): "⚠️ " else: "[WARN]"

proc infoSymbol*(): string =
  if isColorEnabled(): "ℹ️ " else: "[INFO]"

proc packageSymbol*(): string =
  if isColorEnabled(): "📦" else: "[PKG]"

proc graftSymbol*(): string =
  if isColorEnabled(): "🌱" else: "[GRAFT]"

proc cellSymbol*(): string =
  if isColorEnabled(): "🏠" else: "[CELL]"

proc lockSymbol*(): string =
  if isColorEnabled(): "🔒" else: "[LOCK]"

# =============================================================================
# Progress and Status Display
# =============================================================================

type
  ProgressBar* = object
    total*: int
    current*: int
    width*: int
    label*: string

proc newProgressBar*(total: int, label: string = "", width: int = 40): ProgressBar =
  ## Create a new progress bar
  ProgressBar(
    total: total,
    current: 0,
    width: width,
    label: label
  )

proc update*(pb: var ProgressBar, current: int) =
  ## Update progress bar
  pb.current = current
  if not globalContext.options.quiet and isColorEnabled():
    let percentage = if pb.total > 0: (pb.current * 100) div pb.total else: 0
    let filled = (pb.current * pb.width) div pb.total
    let empty = pb.width - filled

    let bar = "█".repeat(filled) & "░".repeat(empty)
    let status = fmt"{pb.label} [{bar}] {percentage}% ({pb.current}/{pb.total})"

    stdout.write("\r" & status)
    stdout.flushFile()

    if pb.current >= pb.total:
      stdout.write("\n")

# =============================================================================
# Structured Output
# =============================================================================

proc outputResult*(result: CommandResult) =
  ## Output command result in the requested format
  case globalContext.options.outputFormat:
  of OutputHuman:
    if result.success:
      if not globalContext.options.quiet:
        echo successSymbol() & " " & result.message
    else:
      echo errorSymbol() & " " & error(result.message)

  of OutputJson:
    let output = %*{
      "success": result.success,
      "exit_code": result.exitCode,
      "message": result.message,
      "data": result.data,
      "timestamp": $now()
    }
    echo output.pretty()

  of OutputYaml:
    # YAML output using JSON-compatible structure
    echo "success: " & $result.success
    echo "message: \"" & result.message.replace("\"", "\\\"") & "\""
    echo "exit_code: " & $result.exitCode
    echo "timestamp: \"" & $now() & "\""
    if result.data != nil:
      echo "data:"
      echo jsonToYaml(result.data, 1)

  of OutputKdl:
    # KDL output using structured format
    echo "result {"
    echo "  success " & $result.success
    echo "  message \"" & result.message.replace("\"", "\\\"") & "\""
    echo "  exit_code " & $result.exitCode
    echo "  timestamp \"" & $now() & "\""
    if result.data != nil:
      echo "  data " & jsonToKdl(result.data, 1)
    echo "}"

proc outputData*(data: JsonNode, title: string = "") =
  ## Output structured data in the requested format
  case globalContext.options.outputFormat:
  of OutputHuman:
    if title != "":
      echo bold(title)
      echo "=".repeat(title.len)
    echo data.pretty()

  of OutputJson:
    echo data.pretty()

  of OutputYaml:
    # Convert JSON to YAML-like format
    echo jsonToYaml(data)

  of OutputKdl:
    # Convert JSON to KDL-like format
    echo jsonToKdl(data)

# =============================================================================
# Error Handling and User Feedback
# =============================================================================

proc logMessage*(level: LogLevel, message: string) =
  ## Log a message at the specified level
  if level >= globalContext.options.logLevel:
    let timestamp = now().format("HH:mm:ss")
    let levelStr = case level:
      of LogDebug: info("[DEBUG]")
      of LogInfo: info("[INFO]")
      of LogWarn: warning("[WARN]")
      of LogError: error("[ERROR]")

    if globalContext.options.outputFormat == OutputHuman:
      echo fmt"{timestamp} {levelStr} {message}"
    else:
      let logEntry = %*{
        "timestamp": timestamp,
        "level": $level,
        "message": message
      }
      echo logEntry

proc debugLog*(message: string) = logMessage(LogDebug, message)
proc infoLog*(message: string) = logMessage(LogInfo, message)
proc warnLog*(message: string) = logMessage(LogWarn, message)
proc errorLog*(message: string) = logMessage(LogError, message)

proc showError*(message: string, suggestions: seq[string] = @[]) =
  ## Show an error with optional suggestions
  echo errorSymbol() & " " & error(message)

  if suggestions.len > 0:
    echo ""
    echo info("💡 Suggestions:")
    for suggestion in suggestions:
      echo "  • " & suggestion

proc showWarning*(message: string) =
  ## Show a warning message
  echo warningSymbol() & " " & warning(message)

proc showInfo*(message: string) =
  ## Show an info message
  if not globalContext.options.quiet:
    echo infoSymbol() & " " & info(message)

proc showSuccess*(message: string) =
  ## Show a success message
  if not globalContext.options.quiet:
    echo successSymbol() & " " & success(message)

# =============================================================================
# Command Execution Helpers
# =============================================================================

proc executeWithTiming*(operation: proc(): CommandResult, operationName: string): CommandResult =
  ## Execute an operation and show timing information
  let startTime = cpuTime()

  if globalContext.options.verbose:
    infoLog(fmt"Starting {operationName}")

  result = operation()

  let duration = cpuTime() - startTime

  if globalContext.options.verbose:
    infoLog(fmt"Completed {operationName} in {duration:.2f}s")

proc confirmAction*(message: string, defaultYes: bool = false): bool =
  ## Ask user for confirmation
  if globalContext.options.dryRun:
    echo info("[DRY RUN] Would execute: " & message)
    return false

  let prompt = if defaultYes: " [Y/n]: " else: " [y/N]: "
  stdout.write(message & prompt)
  stdout.flushFile()

  let response = readLine(stdin).strip().toLower()

  if response == "":
    return defaultYes

  return response in ["y", "yes", "true", "1"]

# =============================================================================
# Utility Functions
# =============================================================================

proc formatFileSize*(bytes: int64): string =
  ## Format file size in human-readable format
  const units = ["B", "KB", "MB", "GB", "TB"]
  var size = bytes.float
  var unitIndex = 0

  while size >= 1024.0 and unitIndex < units.high:
    size /= 1024.0
    inc unitIndex

  if unitIndex == 0:
    result = fmt"{size:.0f} {units[unitIndex]}"
  else:
    result = fmt"{size:.1f} {units[unitIndex]}"

proc formatDuration*(seconds: float): string =
  ## Format duration in human-readable format
  if seconds < 1.0:
    result = fmt"{seconds * 1000:.0f}ms"
  elif seconds < 60.0:
    result = fmt"{seconds:.1f}s"
  elif seconds < 3600.0:
    let minutes = seconds / 60.0
    result = fmt"{minutes:.1f}m"
  else:
    let hours = seconds / 3600.0
    result = fmt"{hours:.1f}h"

proc parseGlobalOptions*(args: seq[string]): (GlobalOptions, seq[string]) =
  ## Parse global options from command line arguments
  var options = GlobalOptions(
    outputFormat: OutputHuman,
    logLevel: LogInfo,
    dryRun: false,
    noColor: false,
    verbose: false,
    quiet: false
  )

  var remainingArgs: seq[string] = @[]
  var i = 0

  while i < args.len:
    case args[i]:
    of "--output":
      if i + 1 < args.len:
        case args[i + 1].toLower():
        of "human": options.outputFormat = OutputHuman
        of "json": options.outputFormat = OutputJson
        of "yaml": options.outputFormat = OutputYaml
        of "kdl": options.outputFormat = OutputKdl
        else:
          raise newException(ValueError, "Invalid output format: " & args[i + 1])
        i += 1
      else:
        raise newException(ValueError, "--output requires a value")

    of "--log-level":
      if i + 1 < args.len:
        case args[i + 1].toLower():
        of "debug": options.logLevel = LogDebug
        of "info": options.logLevel = LogInfo
        of "warn": options.logLevel = LogWarn
        of "error": options.logLevel = LogError
        else:
          raise newException(ValueError, "Invalid log level: " & args[i + 1])
        i += 1
      else:
        raise newException(ValueError, "--log-level requires a value")

    of "--dry-run":
      options.dryRun = true

    of "--no-color":
      options.noColor = true

    of "--verbose", "-v":
      options.verbose = true

    of "--quiet", "-q":
      options.quiet = true

    of "--json":
      options.outputFormat = OutputJson

    of "--yaml":
      options.outputFormat = OutputYaml

    of "--kdl":
      options.outputFormat = OutputKdl

    else:
      remainingArgs.add(args[i])

    i += 1

  result = (options, remainingArgs)

# =============================================================================
# Command Result Helpers
# =============================================================================

proc successResult*(message: string, data: JsonNode = newJNull()): CommandResult =
  ## Create a successful command result
  CommandResult(
    success: true,
    exitCode: 0,
    message: message,
    data: data
  )

proc errorResult*(message: string, exitCode: int = 1, data: JsonNode = newJNull()): CommandResult =
  ## Create an error command result
  CommandResult(
    success: false,
    exitCode: exitCode,
    message: message,
    data: data
  )