// explain.cm — Query module over a semantic index.
//
// Usage:
//   var explain = use('explain').make(index)
//   explain.at_span(line, col)
//   explain.by_symbol(name)
//   explain.call_chain(symbol_id, depth)

// Check if a position (line, col) falls inside a span.
var span_contains = function(span, line, col) {
  if (line < span.from_row || line > span.to_row) return false
  if (line == span.from_row && col < span.from_col) return false
  if (line == span.to_row && col > span.to_col) return false
  return true
}

// Create an explain interface bound to a single file index.
var make = function(index) {

  // Find symbol or reference at a given line/col position.
  var at_span = function(line, col) {
    var _i = 0
    var sym = null
    var ref = null
    var found_sym = null
    var found_ref = null
    var result_refs = []
    var result_calls = []

    // Search symbols for one whose decl_span contains (line, col).
    _i = 0
    while (_i < length(index.symbols)) {
      sym = index.symbols[_i]
      if (sym.decl_span != null && span_contains(sym.decl_span, line, col)) {
        found_sym = sym
        break
      }
      _i = _i + 1
    }

    // If no symbol found, search references.
    if (found_sym == null) {
      _i = 0
      while (_i < length(index.references)) {
        ref = index.references[_i]
        if (ref.span != null && span_contains(ref.span, line, col)) {
          found_ref = ref
          // Look up the symbol this reference points to.
          if (ref.symbol_id != null) {
            _i = 0
            while (_i < length(index.symbols)) {
              if (index.symbols[_i].symbol_id == ref.symbol_id) {
                found_sym = index.symbols[_i]
                break
              }
              _i = _i + 1
            }
          }
          break
        }
        _i = _i + 1
      }
    }

    if (found_sym == null && found_ref == null) return null

    // Gather all references to this symbol.
    if (found_sym != null && index.reverse_refs[found_sym.name] != null) {
      result_refs = index.reverse_refs[found_sym.name]
    }

    // Gather call sites.
    _i = 0
    while (_i < length(index.call_sites)) {
      if (found_sym != null) {
        if (index.call_sites[_i].callee_symbol_id == found_sym.symbol_id ||
            (index.call_sites[_i].callee_symbol_id == null && index.call_sites[_i].callee == found_sym.name)) {
          result_calls[] = index.call_sites[_i]
        }
      }
      _i = _i + 1
    }

    return {
      symbol: found_sym,
      reference: found_ref,
      references: result_refs,
      call_sites: result_calls,
      imports: index.imports
    }
  }

  // Find all symbols matching a name.
  var by_symbol = function(name) {
    var _i = 0
    var matches = []
    var result_refs = []
    var result_calls = []

    // Find matching symbols.
    _i = 0
    while (_i < length(index.symbols)) {
      if (index.symbols[_i].name == name) {
        matches[] = index.symbols[_i]
      }
      _i = _i + 1
    }

    // Gather all references to this name.
    if (index.reverse_refs[name] != null) {
      result_refs = index.reverse_refs[name]
    }

    // Gather call sites where this name is the callee.
    _i = 0
    while (_i < length(index.call_sites)) {
      if (index.call_sites[_i].callee == name) {
        result_calls[] = index.call_sites[_i]
      }
      _i = _i + 1
    }

    return {
      symbols: matches,
      references: result_refs,
      call_sites: result_calls
    }
  }

  // Build a call chain from/to a symbol.
  var call_chain = function(symbol_id, depth) {
    var max_depth = (depth != null) ? depth : 2
    var callers = []
    var callees = []
    var _i = 0
    var cs = null

    // Callees: calls made FROM this symbol.
    _i = 0
    while (_i < length(index.call_sites)) {
      cs = index.call_sites[_i]
      if (cs.enclosing == symbol_id) {
        callees[] = {
          callee: cs.callee,
          callee_symbol_id: cs.callee_symbol_id,
          span: cs.span,
          args_count: cs.args_count
        }
      }
      _i = _i + 1
    }

    // Callers: calls TO this symbol.
    _i = 0
    while (_i < length(index.call_sites)) {
      cs = index.call_sites[_i]
      if (cs.callee_symbol_id == symbol_id) {
        callers[] = {
          from: cs.enclosing,
          span: cs.span,
          args_count: cs.args_count
        }
      }
      _i = _i + 1
    }

    return {
      symbol_id: symbol_id,
      callers: callers,
      callees: callees,
      depth: max_depth
    }
  }

  return {
    at_span: at_span,
    by_symbol: by_symbol,
    call_chain: call_chain
  }
}

// Search across multiple file indexes.
var explain_across = function(indexes, name) {
  var _i = 0
  var _j = 0
  var all_symbols = []
  var all_refs = []
  var all_calls = []
  var idx = null
  var refs = null

  _i = 0
  while (_i < length(indexes)) {
    idx = indexes[_i]

    // Gather symbols.
    _j = 0
    while (_j < length(idx.symbols)) {
      if (idx.symbols[_j].name == name) {
        all_symbols[] = idx.symbols[_j]
      }
      _j = _j + 1
    }

    // Gather references.
    refs = idx.reverse_refs[name]
    if (refs != null) {
      _j = 0
      while (_j < length(refs)) {
        all_refs[] = refs[_j]
        _j = _j + 1
      }
    }

    // Gather call sites.
    _j = 0
    while (_j < length(idx.call_sites)) {
      if (idx.call_sites[_j].callee == name) {
        all_calls[] = idx.call_sites[_j]
      }
      _j = _j + 1
    }

    _i = _i + 1
  }

  return {
    symbols: all_symbols,
    references: all_refs,
    call_sites: all_calls
  }
}

return {
  make: make,
  explain_across: explain_across,
  span_contains: span_contains
}
