## nimpak/cli/dependency_graph.nim
## Dependency graph visualization and filesystem impact analysis
##
## This module implements the advanced CLI output features for Task 10.3:
## - Dependency graph visualization before installation
## - Filesystemysis showing symlink changes
## - Interactive confirmation with dependency preview

import std/[os, strutils, times, json, tables, sequtils, algorithm, sets, strformat]
import core, ../types_fixed

type
  DependencyNode* = object
    name*: string
    version*: string
    stream*: string
    reason*: string  # "direct", "runtime", "build", "optional"
    level*: int      # Depth in dependency tree
    children*: seq[string]  # Names of dependent packages

  FilesystemChange* = object
    action*: string    # "create", "update", "remove"
    path*: string      # Target path in /System/Index
    target*: string    # Source path in /Programs (for symlinks)
    size*: int64       # File size in bytes
    permissions*: string

  DependencyGraph* = object
    rootPackage*: string
    nodes*: Table[string, DependencyNode]
    filesystemChanges*: seq[FilesystemChange]
    totalSize*: int64
    conflictingFiles*: seq[string]

  GraphDisplayFormat* = enum
    GraphTree      # Tree-like ASCII art
    GraphCompact   # Compact list format
    GraphDetailed  # Detailed with reasons and sizes

# =============================================================================
# Dependency Graph Construction
# =============================================================================

proc newDependencyGraph*(rootPackage: string): DependencyGraph =
  ## Create a new dependency graph for a package
  DependencyGraph(
    rootPackage: rootPackage,
    nodes: initTable[string, DependencyNode](),
    filesystemChanges: @[],
    totalSize: 0,
    conflictingFiles: @[]
  )

proc addDependency*(graph: var DependencyGraph, name: string, version: string,
                   stream: string, reason: string, level: int,
                   parent: string = ""): DependencyNode =
  ## Add a dependency to the graph
  let node = DependencyNode(
    name: name,
    version: version,
    stream: stream,
    reason: reason,
    level: level,
    children: @[]
  )

  graph.nodes[name] = node

  # Add to parent's children if specified
  if parent != "" and parent in graph.nodes:
    graph.nodes[parent].children.add(name)

  return node

proc addFilesystemChange*(graph: var DependencyGraph, action: string,
                         path: string, target: string = "", size: int64 = 0,
                         permissions: string = "755"): FilesystemChange =
  ## Add a filesystem change to the graph
  let change = FilesystemChange(
    action: action,
    path: path,
    target: target,
    size: size,
    permissions: permissions
  )

  graph.filesystemChanges.add(change)
  graph.totalSize += size

  return change

# =============================================================================
# Mock Dependency Resolution (TODO: Replace with real implementation)
# =============================================================================

proc resolveDependencies*(packageName: string, stream: string = "stable"): DependencyGraph =
  ## Resolve dependencies for a package (mock implementation)
  var graph = newDependencyGraph(packageName)

  # Add root package
  discard graph.addDependency(packageName, "3.2.2", stream, "direct", 0)

  # Mock dependency resolution based on common packages
  case packageName.toLower():
  of "htop":
    discard graph.addDependency("libc", "2.38", "stable", "runtime", 1, packageName)
    discard graph.addDependency("ncurses", "6.4", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libprocps", "4.0.3", "stable", "runtime", 1, packageName)

    # Add filesystem changes
    discard graph.addFilesystemChange("create", "/System/Index/bin/htop",
                                     "/Programs/htop/3.2.2/bin/htop", 156672)
    discard graph.addFilesystemChange("create", "/System/Index/share/man/man1/htop.1",
                                     "/Programs/htop/3.2.2/share/man/man1/htop.1", 8192)
    discard graph.addFilesystemChange("create", "/System/Index/share/applications/htop.desktop",
                                     "/Programs/htop/3.2.2/share/applications/htop.desktop", 512)

  of "vim":
    discard graph.addDependency("libc", "2.38", "stable", "runtime", 1, packageName)
    discard graph.addDependency("ncurses", "6.4", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libacl", "2.3.1", "stable", "runtime", 1, packageName)
    discard graph.addDependency("gpm", "1.20.7", "stable", "optional", 1, packageName)

    # Vim has many files
    discard graph.addFilesystemChange("create", "/System/Index/bin/vim",
                                     "/Programs/vim/9.0.2/bin/vim", 3145728)
    discard graph.addFilesystemChange("create", "/System/Index/bin/vi",
                                     "/Programs/vim/9.0.2/bin/vi", 3145728)  # Usually a symlink
    discard graph.addFilesystemChange("create", "/System/Index/share/vim",
                                     "/Programs/vim/9.0.2/share/vim", 15728640)

  of "git":
    discard graph.addDependency("libc", "2.38", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libcurl", "8.2.1", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libssl", "3.0.9", "stable", "runtime", 1, packageName)
    discard graph.addDependency("zlib", "1.2.13", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libpcre2", "10.42", "stable", "runtime", 1, packageName)

    # libcurl has its own dependencies
    discard graph.addDependency("libnghttp2", "1.55.1", "stable", "runtime", 2, "libcurl")
    discard graph.addDependency("libidn2", "2.3.4", "stable", "runtime", 2, "libcurl")

    # Git has many executables
    discard graph.addFilesystemChange("create", "/System/Index/bin/git",
                                     "/Programs/git/2.41.0/bin/git", 2097152)
    discard graph.addFilesystemChange("create", "/System/Index/libexec/git-core",
                                     "/Programs/git/2.41.0/libexec/git-core", 45875200)

  of "firefox":
    # Firefox is a complex package with many dependencies
    discard graph.addDependency("libc", "2.38", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libgtk3", "3.24.38", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libx11", "1.8.6", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libpulse", "16.1", "stable", "runtime", 1, packageName)
    discard graph.addDependency("libffi", "3.4.4", "stable", "runtime", 1, packageName)
    discard graph.addDependency("nss", "3.91", "stable", "runtime", 1, packageName)

    # GTK3 dependencies
    discard graph.addDependency("libglib2", "2.76.4", "stable", "runtime", 2, "libgtk3")
    discard graph.addDependency("libcairo", "1.17.8", "stable", "runtime", 2, "libgtk3")
    discard graph.addDependency("libpango", "1.50.14", "stable", "runtime", 2, "libgtk3")

    # Firefox is huge
    discard graph.addFilesystemChange("create", "/System/Index/bin/firefox",
                                     "/Programs/firefox/116.0/bin/firefox", 268435456)
    discard graph.addFilesystemChange("create", "/System/Index/lib/firefox",
                                     "/Programs/firefox/116.0/lib/firefox", 536870912)

  else:
    # Generic package with basic dependencies
    discard graph.addDependency("libc", "2.38", "stable", "runtime", 1, packageName)
    discard graph.addFilesystemChange("create", fmt"/System/Index/bin/{packageName}",
                                     fmt"/Programs/{packageName}/1.0.0/bin/{packageName}", 1048576)

  return graph

# =============================================================================
# Graph Display Functions
# =============================================================================

proc displayTreeGraph*(graph: DependencyGraph, showSizes: bool = false) =
  ## Display dependency graph as ASCII tree
  echo bold("Dependency Tree for: " & highlight(graph.rootPackage))
  echo "=".repeat(50)

  proc displayNode(name: string, level: int, isLast: bool, prefix: string = "") =
    if name notin graph.nodes:
      return

    let node = graph.nodes[name]
    let connector = if isLast: "└── " else: "├── "
    let nodePrefix = if level == 0: "" else: prefix & connector

    var nodeInfo = bold(node.name) & " " & highlight(node.version)
    if node.reason != "direct":
      nodeInfo.add(" (" & node.reason & ")")

    if showSizes and node.name in graph.nodes:
      # Calculate size for this package's files
      var packageSize: int64 = 0
      for change in graph.filesystemChanges:
        if change.target.contains(node.name):
          packageSize += change.size
      if packageSize > 0:
        nodeInfo.add(" [" & formatFileSize(packageSize) & "]")

    echo nodePrefix & nodeInfo

    # Display children
    let children = node.children
    for i, child in children:
      let childIsLast = i == children.high
      let childPrefix = if level == 0: "" else: prefix & (if isLast: "    " else: "│   ")
      displayNode(child, level + 1, childIsLast, childPrefix)

  displayNode(graph.rootPackage, 0, true)

proc displayCompactGraph*(graph: DependencyGraph) =
  ## Display dependency graph in compact format
  echo bold("Dependencies for: " & highlight(graph.rootPackage))
  echo "=".repeat(40)

  # Group by dependency level
  var levelGroups = initTable[int, seq[DependencyNode]]()
  for node in graph.nodes.values:
    if node.level notin levelGroups:
      levelGroups[node.level] = @[]
    levelGroups[node.level].add(node)

  for level in sorted(toSeq(levelGroups.keys)):
    if level == 0:
      continue  # Skip root package

    let levelName = case level:
      of 1: "Direct Dependencies"
      of 2: "Transitive Dependencies"
      else: fmt"Level {level} Dependencies"

    echo ""
    echo bold(levelName & ":")

    let nodes = levelGroups[level].sortedByIt(it.name)
    for node in nodes:
      let reasonColor = case node.reason:
        of "runtime": info("runtime")
        of "build": warning("build")
        of "optional": highlight("optional")
        else: node.reason

      echo fmt"  • {node.name} {node.version} ({reasonColor})"

proc displayDetailedGraph*(graph: DependencyGraph) =
  ## Display detailed dependency graph with full information
  echo bold("Detailed Dependency Analysis for: " & highlight(graph.rootPackage))
  echo "=".repeat(60)

  # Summary
  echo ""
  echo bold("Summary:")
  echo fmt"  Total packages: {graph.nodes.len}"
  echo fmt"  Total download size: {formatFileSize(graph.totalSize)}"
  echo fmt"  Filesystem changes: {graph.filesystemChanges.len}"

  if graph.conflictingFiles.len > 0:
    echo warning(fmt"  Conflicting files: {graph.conflictingFiles.len}")

  # Dependencies by type
  var runtimeDeps, buildDeps, optionalDeps: seq[DependencyNode]
  for node in graph.nodes.values:
    if node.level == 0: continue  # Skip root
    case node.reason:
    of "runtime": runtimeDeps.add(node)
    of "build": buildDeps.add(node)
    of "optional": optionalDeps.add(node)

  if runtimeDeps.len > 0:
    echo ""
    echo bold("Runtime Dependencies:")
    for dep in runtimeDeps.sortedByIt(it.name):
      echo fmt"  📦 {dep.name} {dep.version} ({dep.stream})"

  if buildDeps.len > 0:
    echo ""
    echo bold("Build Dependencies:")
    for dep in buildDeps.sortedByIt(it.name):
      echo fmt"  🔧 {dep.name} {dep.version} ({dep.stream})"

  if optionalDeps.len > 0:
    echo ""
    echo bold("Optional Dependencies:")
    for dep in optionalDeps.sortedByIt(it.name):
      echo fmt"  ⚙️  {dep.name} {dep.version} ({dep.stream})"

# =============================================================================
# Filesystem Impact Analysis
# =============================================================================

proc displayFilesystemImpact*(graph: DependencyGraph, verbose: bool = false) =
  ## Display filesystem impact analysis
  echo ""
  echo bold("Filesystem Impact Analysis")
  echo "=".repeat(40)

  # Group changes by action
  var creates, updates, removes: seq[FilesystemChange]
  for change in graph.filesystemChanges:
    case change.action:
    of "create": creates.add(change)
    of "update": updates.add(change)
    of "remove": removes.add(change)

  # Summary
  echo fmt"  Files to create: {creates.len}"
  echo fmt"  Files to update: {updates.len}"
  echo fmt"  Files to remove: {removes.len}"
  echo fmt"  Total size: {formatFileSize(graph.totalSize)}"

  if verbose:
    if creates.len > 0:
      echo ""
      echo success("Files to be created:")
      for change in creates.sortedByIt(it.path):
        echo fmt"  + {change.path}"
        if change.target != "":
          echo fmt"    → {change.target}"
        if change.size > 0:
          echo fmt"    Size: {formatFileSize(change.size)}"

    if updates.len > 0:
      echo ""
      echo warning("Files to be updated:")
      for change in updates.sortedByIt(it.path):
        echo fmt"  ~ {change.path}"
        if change.target != "":
          echo fmt"    → {change.target}"

    if removes.len > 0:
      echo ""
      echo error("Files to be removed:")
      for change in removes.sortedByIt(it.path):
        echo fmt"  - {change.path}"

  # Check for conflicts
  if graph.conflictingFiles.len > 0:
    echo ""
    echo warning("Potential conflicts:")
    for conflict in graph.conflictingFiles:
      echo fmt"  ⚠️  {conflict}"

proc displayQuickOverview*(graph: DependencyGraph) =
  ## Display quick dependency overview
  echo bold(fmt"Quick Overview: {graph.rootPackage}")
  echo "=".repeat(30)

  let directDeps = graph.nodes.values.toSeq.filterIt(it.level == 1).len
  let totalDeps = graph.nodes.len - 1  # Exclude root package

  echo fmt"Dependencies: {directDeps} direct, {totalDeps} total"
  echo fmt"Download size: {formatFileSize(graph.totalSize)}"
  echo fmt"Files affected: {graph.filesystemChanges.len}"

  if directDeps > 0:
    echo ""
    echo "Direct dependencies:"
    for node in graph.nodes.values:
      if node.level == 1:
        echo fmt"  • {node.name} {node.version}"

# =============================================================================
# Interactive Preview Functions
# =============================================================================

proc showInstallationPreview*(packageName: string, stream: string = "stable",
                              format: GraphDisplayFormat = GraphTree,
                              showFilesystem: bool = false,
                              verbose: bool = false): bool =
  ## Show installation preview and ask for confirmation
  core.showInfo(fmt"Analyzing dependencies for: {packageName}")

  let graph = resolveDependencies(packageName, stream)

  echo ""
  case format:
  of GraphTree:
    displayTreeGraph(graph, showSizes = verbose)
  of GraphCompact:
    displayCompactGraph(graph)
  of GraphDetailed:
    displayDetailedGraph(graph)

  if showFilesystem:
    displayFilesystemImpact(graph, verbose)

  echo ""
  return confirmAction(fmt"Proceed with installation of {packageName}?", defaultYes = true)

proc showDependencyGraph*(packageName: string, format: string = "tree",
                         showSizes: bool = false): CommandResult =
  ## Show dependency graph for a package (command interface)
  try:
    let graph = resolveDependencies(packageName)

    case format.toLower():
    of "tree":
      displayTreeGraph(graph, showSizes)
    of "compact":
      displayCompactGraph(graph)
    of "detailed":
      displayDetailedGraph(graph)
    of "quick":
      displayQuickOverview(graph)
    else:
      return errorResult(fmt"Unknown format: {format}. Use: tree, compact, detailed, quick")

    let graphData = %*{
      "package": packageName,
      "total_dependencies": graph.nodes.len - 1,
      "total_size": graph.totalSize,
      "filesystem_changes": graph.filesystemChanges.len
    }

    return successResult(fmt"Dependency graph displayed for {packageName}", graphData)

  except Exception as e:
    return errorResult(fmt"Failed to show dependency graph: {e.msg}")

# Export main functions
export showInstallationPreview, showDependencyGraph, GraphDisplayFormat