// cell graph [<locator>] - Emit dependency graph
//
// Usage:
//   cell graph             Graph current directory package
//   cell graph .           Graph current directory package
//   cell graph <locator>   Graph specific package
//   cell graph --world     Graph all packages in shop (world set)
//
// Options:
//   --format <fmt>   Output format: tree (default), dot, json
//   --resolved       Show resolved view with links applied (default)
//   --locked         Show lock view without links
//   --world          Graph all packages in shop

var shop = use('internal/shop')
var pkg = use('package')
var link = use('link')
var fd = use('fd')
var json = use('json')

var target_locator = null
var format = 'tree'
var show_locked = false
var show_world = false
var i = 0
var resolved = null

for (i = 0; i < length(args); i++) {
  if (args[i] == '--format' || args[i] == '-f') {
    if (i + 1 < length(args)) {
      format = args[++i]
      if (format != 'tree' && format != 'dot' && format != 'json') {
        log.error('Invalid format: ' + format + '. Must be tree, dot, or json')
        $stop()
      }
    } else {
      log.error('--format requires a format type')
      $stop()
    }
  } else if (args[i] == '--resolved') {
    show_locked = false
  } else if (args[i] == '--locked') {
    show_locked = true
  } else if (args[i] == '--world') {
    show_world = true
  } else if (args[i] == '--help' || args[i] == '-h') {
    log.console("Usage: cell graph [<locator>] [options]")
    log.console("")
    log.console("Emit the dependency graph.")
    log.console("")
    log.console("Options:")
    log.console("  --format <fmt>  Output format: tree (default), dot, json")
    log.console("  --resolved      Show resolved view with links applied (default)")
    log.console("  --locked        Show lock view without links")
    log.console("  --world         Graph all packages in shop")
    $stop()
  } else if (!starts_with(args[i], '-')) {
    target_locator = args[i]
  }
}

var links = show_locked ? {} : link.load()

// Get effective locator (after links)
function get_effective(locator) {
  return links[locator] || locator
}

// Build graph data structure
var nodes = {}
var edges = []

function add_node(locator) {
  if (nodes[locator]) return

  var lock = shop.load_lock()
  var lock_entry = lock[locator]
  var link_target = links[locator]
  var info = shop.resolve_package_info(locator)

  nodes[locator] = {
    id: locator,
    effective: get_effective(locator),
    linked: link_target != null,
    local: info == 'local',
    commit: lock_entry && lock_entry.commit ? text(lock_entry.commit, 0, 8) : null
  }
}

function gather_graph(locator, visited) {
  if (visited[locator]) return
  visited[locator] = true

  add_node(locator)

  var _gather = function() {
    var deps = pkg.dependencies(locator)
    if (deps) {
      arrfor(array(deps), function(alias) {
        var dep_locator = deps[alias]
        add_node(dep_locator)
        push(edges, { from: locator, to: dep_locator, alias: alias })
        gather_graph(dep_locator, visited)
      })
    }
  } disruption {
    // Package might not have dependencies
  }
  _gather()
}

// Gather graph from roots
var roots = []

var packages = null
if (show_world) {
  // Use all packages in shop as roots
  packages = shop.list_packages()
  arrfor(packages, function(p) {
    if (p != 'core') {
      push(roots, p)
    }
  })
} else {
  // Default to current directory
  if (!target_locator) {
    target_locator = '.'
  }

  // Resolve local paths
  if (target_locator == '.' || starts_with(target_locator, './') || starts_with(target_locator, '../') || fd.is_dir(target_locator)) {
    resolved = fd.realpath(target_locator)
    if (resolved) {
      target_locator = resolved
    }
  }

  push(roots, target_locator)
}

arrfor(roots, function(root) {
  gather_graph(root, {})
})

// Output based on format
var children = null
var j = 0
var output = null
if (format == 'tree') {
  function print_tree(locator, prefix, is_last, visited) {
    var node = null
    var suffix = null
    var children = null
    var j = 0
    var child_prefix = null
    if (visited[locator]) {
      log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + " (circular)")
      return
    }
    visited[locator] = true

    node = nodes[locator]
    suffix = ""
    if (node.linked) suffix += " -> " + node.effective
    if (node.commit) suffix += " @" + node.commit
    if (node.local) suffix += " (local)"

    log.console(prefix + (is_last ? "\\-- " : "|-- ") + locator + suffix)

    // Get children
    children = []
    arrfor(edges, function(e) {
      if (e.from == locator) {
        push(children, e)
      }
    })

    for (j = 0; j < length(children); j++) {
      child_prefix = prefix + (is_last ? "    " : "|   ")
      print_tree(children[j].to, child_prefix, j == length(children) - 1, visited)
    }
  }

  for (i = 0; i < length(roots); i++) {
    log.console(roots[i])

    children = []
    arrfor(edges, function(e) {
      if (e.from == roots[i]) {
        push(children, e)
      }
    })

    for (j = 0; j < length(children); j++) {
      print_tree(children[j].to, "", j == length(children) - 1, {})
    }

    if (i < length(roots) - 1) log.console("")
  }

} else if (format == 'dot') {
  log.console("digraph dependencies {")
  log.console("  rankdir=TB;")
  log.console("  node [shape=box];")
  log.console("")

  // Node definitions
  arrfor(array(nodes), function(id) {
    var node = nodes[id]
    var label = id
    if (node.commit) label += "\\n@" + node.commit
    var attrs = 'label="' + label + '"'
    if (node.linked) attrs += ', style=dashed'
    if (node.local) attrs += ', color=blue'

    // Safe node ID for dot
    var safe_id = replace(id, /[^a-zA-Z0-9]/g, '_')
    log.console('  ' + safe_id + ' [' + attrs + '];')
  })

  log.console("")

  // Edges
  arrfor(edges, function(e) {
    var from_id = replace(e.from, /[^a-zA-Z0-9]/g, '_')
    var to_id = replace(e.to, /[^a-zA-Z0-9]/g, '_')
    var label = e.alias != e.to ? 'label="' + e.alias + '"' : ''
    log.console('  ' + from_id + ' -> ' + to_id + (label ? ' [' + label + ']' : '') + ';')
  })

  log.console("}")

} else if (format == 'json') {
  output = {
    nodes: [],
    edges: []
  }

  arrfor(array(nodes), function(id) {
    push(output.nodes, nodes[id])
  })

  output.edges = edges

  log.console(json.encode(output))
}

$stop()
