// cell query — Semantic queries across packages.
//
// Usage:
//   cell query <name>                     Find all references to <name>
//   cell query <name> --top               Top-level references only
//   cell query <name> --fn                Inside-function references only
//   cell query <name> --channels          Show <name>.<prop> usage summary
//   cell query --decl <name>              Find declarations of <name>
//   cell query --decl <name> --fn         Only function declarations
//   cell query --intrinsic <name>         Find intrinsic usage
//   cell query --excess-args              Find call sites with >4 args
//   cell query --help

var shop = use('internal/shop')
var analyze_mod = use('analyze')
var fd = use('fd')

var mode = null
var name = null
var scope_filter = null
var kind_filter = null
var pkg_filter = null
var show_help = false
var i = 0

for (i = 0; i < length(args); i++) {
  if (args[i] == '--top') {
    scope_filter = "top"
  } else if (args[i] == '--fn') {
    if (mode == "decl") {
      kind_filter = "fn"
    } else {
      scope_filter = "fn"
    }
  } else if (args[i] == '--channels') {
    mode = "channels"
  } else if (args[i] == '--intrinsic') {
    mode = "intrinsic"
    if (i + 1 < length(args) && !starts_with(args[i + 1], '-')) {
      name = args[i + 1]
      i = i + 1
    } else {
      log.error('--intrinsic requires a name')
      mode = "error"
    }
  } else if (args[i] == '--decl') {
    mode = "decl"
    if (i + 1 < length(args) && !starts_with(args[i + 1], '-')) {
      name = args[i + 1]
      i = i + 1
    } else {
      log.error('--decl requires a name')
      mode = "error"
    }
  } else if (args[i] == '--excess-args') {
    mode = "excess_args"
  } else if (args[i] == '--help' || args[i] == '-h') {
    show_help = true
  } else if (!starts_with(args[i], '-')) {
    if (name == null && mode == null) {
      name = args[i]
      mode = "refs"
    } else {
      pkg_filter = args[i]
    }
  }
}

// --channels requires a name from positional arg
if (mode == "channels" && name == null) {
  log.error('--channels requires a name (e.g., cell query log --channels)')
  mode = "error"
}

var all_files = null
var files = []
var j = 0
var idx = null
var hits = null
var hit = null
var k = 0
var ch_result = null
var props = null
var prop = null
var parts = null

// Use return pattern to avoid closure-over-object issue with disruption.
var safe_index = function(path) {
  return shop.index_file(path)
} disruption {
  return null
}

if (show_help) {
  log.console("Usage: cell query [options] [<name>] [<package>]")
  log.console("")
  log.console("Semantic queries across packages.")
  log.console("")
  log.console("Commands:")
  log.console("  <name>                     Find all references to <name>")
  log.console("  <name> --top               Top-level references only")
  log.console("  <name> --fn                Inside-function references only")
  log.console("  <name> --channels          Show <name>.<prop> usage summary")
  log.console("  --decl <name>              Find declarations of <name>")
  log.console("  --decl <name> --fn         Only function declarations")
  log.console("  --intrinsic <name>         Find intrinsic usage")
  log.console("  --excess-args              Find call sites with >4 args")
  log.console("")
  log.console("Without a package argument, searches all installed packages.")
} else if (mode == null || mode == "error") {
  if (mode != "error") {
    log.error('Specify a name or --decl/--intrinsic/--excess-args. Use --help for usage.')
  }
} else {
  all_files = shop.all_script_paths()

  if (pkg_filter != null) {
    for (j = 0; j < length(all_files); j++) {
      if (all_files[j].package == pkg_filter) {
        files[] = all_files[j]
      }
    }
  } else {
    files = all_files
  }

  for (j = 0; j < length(files); j++) {
    idx = safe_index(files[j].full_path)
    if (idx == null) continue

    if (mode == "refs") {
      hits = analyze_mod.find_refs(idx, name, scope_filter)
      if (hits != null && length(hits) > 0) {
        for (k = 0; k < length(hits); k++) {
          hit = hits[k]
          if (hit.span != null) {
            if (hit.enclosing != null) {
              log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name} (in: ${hit.enclosing})`)
            } else {
              log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name} (top-level)`)
            }
          }
        }
      }
    } else if (mode == "channels") {
      ch_result = analyze_mod.channels(idx, name)
      if (ch_result != null && ch_result.summary != null) {
        props = array(ch_result.summary)
        if (length(props) > 0) {
          parts = []
          for (k = 0; k < length(props); k++) {
            prop = props[k]
            parts[] = `${prop}(${text(ch_result.summary[prop])})`
          }
          log.console(`${files[j].package}:${files[j].rel_path}: ${text(parts, " ")}`)
        }
      }
    } else if (mode == "intrinsic") {
      hits = analyze_mod.find_intrinsic(idx, name)
      if (hits != null && length(hits) > 0) {
        for (k = 0; k < length(hits); k++) {
          hit = hits[k]
          if (hit.span != null) {
            log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.name}`)
          }
        }
      }
    } else if (mode == "decl") {
      hits = analyze_mod.find_decls(idx, name, kind_filter)
      if (hits != null && length(hits) > 0) {
        for (k = 0; k < length(hits); k++) {
          hit = hits[k]
          if (hit.decl_span != null) {
            log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.decl_span.from_row)}:${text(hit.decl_span.from_col)}: ${hit.kind} ${hit.name}`)
          }
        }
      }
    } else if (mode == "excess_args") {
      hits = analyze_mod.excess_args(idx)
      if (hits != null && length(hits) > 0) {
        for (k = 0; k < length(hits); k++) {
          hit = hits[k]
          if (hit.span != null) {
            log.console(`${files[j].package}:${files[j].rel_path}:${text(hit.span.from_row)}:${text(hit.span.from_col)}: ${hit.callee}() called with ${text(hit.args_count)} args (max 4)`)
          }
        }
      }
    }
  }
}

$stop()
