// slots.ce — slot data flow / use-def chains
//
// Usage:
//   cell slots --fn <N|name> <file>             Slot summary for function
//   cell slots --slot <N> --fn <N|name> <file>  Trace slot N in function
//   cell slots <file>                           Slot summary for all functions

var shop = use("internal/shop")

var pad_right = function(s, w) {
  var r = s
  while (length(r) < w) {
    r = r + " "
  }
  return r
}

var fmt_val = function(v) {
  if (is_null(v)) return "null"
  if (is_number(v)) return text(v)
  if (is_text(v)) return `"${v}"`
  if (is_object(v)) return text(v)
  if (is_logical(v)) return v ? "true" : "false"
  return text(v)
}

// DEF/USE functions — populated from streamline's log hooks
var sl_get_defs = null
var sl_get_uses = null

var run = function() {
  var filename = null
  var fn_filter = null
  var slot_filter = null
  var i = 0
  var compiled = null
  var type_info = {}
  var sl_log = null
  var td = null
  var main_name = null
  var fi = 0
  var func = null
  var fname = null

  while (i < length(args)) {
    if (args[i] == '--fn') {
      i = i + 1
      fn_filter = args[i]
    } else if (args[i] == '--slot') {
      i = i + 1
      slot_filter = number(args[i])
    } else if (args[i] == '--help' || args[i] == '-h') {
      log.console("Usage: cell slots [--fn <N|name>] [--slot <N>] <file>")
      log.console("")
      log.console("  --fn <N|name>   Filter to function by index or name")
      log.console("  --slot <N>      Trace a specific slot")
      return null
    } else if (!starts_with(args[i], '-')) {
      filename = args[i]
    }
    i = i + 1
  }

  if (!filename) {
    log.console("Usage: cell slots [--fn <N|name>] [--slot <N>] <file>")
    return null
  }

  compiled = shop.mcode_file(filename)

  // Try to get type info from streamline
  var get_type_info = function() {
    var mcode_copy = shop.mcode_file(filename)
    var streamline = use("streamline")
    var ti = 0
    sl_log = {
      passes: [],
      events: null,
      type_deltas: [],
      request_def_use: true
    }
    streamline(mcode_copy, sl_log)
    if (sl_log.get_slot_defs != null) {
      sl_get_defs = sl_log.get_slot_defs
      sl_get_uses = sl_log.get_slot_uses
    }
    if (sl_log.type_deltas != null) {
      ti = 0
      while (ti < length(sl_log.type_deltas)) {
        td = sl_log.type_deltas[ti]
        if (td.fn != null) {
          type_info[td.fn] = td.slot_types
        }
        ti = ti + 1
      }
    }
    return null
  } disruption {
    // Type info is optional
  }
  get_type_info()

  var fn_matches = function(index, name) {
    var match = null
    if (fn_filter == null) return true
    if (index >= 0 && fn_filter == text(index)) return true
    if (name != null) {
      match = search(name, fn_filter)
      if (match != null && match >= 0) return true
    }
    return false
  }

  var analyze_function = function(func, name, index) {
    var nr_args = func.nr_args != null ? func.nr_args : 0
    var nr_slots = func.nr_slots != null ? func.nr_slots : 0
    var instrs = func.instructions
    var defs = {}
    var uses = {}
    var first_def = {}
    var first_def_op = {}
    var events = []
    var pc = 0
    var ii = 0
    var instr = null
    var op = null
    var n = 0
    var def_positions = null
    var use_positions = null
    var di = 0
    var ui = 0
    var slot_num = null
    var operand_val = null
    var parts = null
    var j = 0
    var operands = null
    var slot_types = null
    var type_key = null
    var ei = 0
    var evt = null
    var found = false
    var line_str = null
    var si = 0
    var slot_key = null
    var d_count = 0
    var u_count = 0
    var t = null
    var first = null
    var dead_marker = null

    if (instrs == null) instrs = []

    // Walk instructions, build def/use chains
    ii = 0
    while (ii < length(instrs)) {
      instr = instrs[ii]
      if (is_text(instr)) {
        ii = ii + 1
        continue
      }
      if (!is_array(instr)) {
        ii = ii + 1
        continue
      }

      op = instr[0]
      n = length(instr)
      def_positions = sl_get_defs(instr)
      use_positions = sl_get_uses(instr)

      di = 0
      while (di < length(def_positions)) {
        operand_val = instr[def_positions[di]]
        if (is_number(operand_val)) {
          slot_num = text(operand_val)
          if (!defs[slot_num]) defs[slot_num] = 0
          defs[slot_num] = defs[slot_num] + 1
          if (first_def[slot_num] == null) {
            first_def[slot_num] = pc
            first_def_op[slot_num] = op
          }
          push(events, {kind: "DEF", slot: operand_val, pc: pc, instr: instr})
        }
        di = di + 1
      }

      ui = 0
      while (ui < length(use_positions)) {
        operand_val = instr[use_positions[ui]]
        if (is_number(operand_val)) {
          slot_num = text(operand_val)
          if (!uses[slot_num]) uses[slot_num] = 0
          uses[slot_num] = uses[slot_num] + 1
          push(events, {kind: "USE", slot: operand_val, pc: pc, instr: instr})
        }
        ui = ui + 1
      }

      pc = pc + 1
      ii = ii + 1
    }

    // Get type info for this function
    type_key = func.name != null ? func.name : name
    if (type_info[type_key]) {
      slot_types = type_info[type_key]
    }

    // --slot mode: show trace
    if (slot_filter != null) {
      log.compile(`\n=== slot ${text(slot_filter)} in ${name} ===`)
      ei = 0
      found = false
      while (ei < length(events)) {
        evt = events[ei]
        if (evt.slot == slot_filter) {
          found = true
          n = length(evt.instr)
          parts = []
          j = 1
          while (j < n - 2) {
            push(parts, fmt_val(evt.instr[j]))
            j = j + 1
          }
          operands = text(parts, ", ")
          line_str = evt.instr[n - 2] != null ? `:${text(evt.instr[n - 2])}` : ""
          log.compile(`  ${pad_right(evt.kind, 5)}pc ${pad_right(text(evt.pc) + ":", 6)} ${pad_right(evt.instr[0], 15)}${pad_right(operands, 30)}${line_str}`)
        }
        ei = ei + 1
      }
      if (!found) {
        log.compile("  (no activity)")
      }
      return null
    }

    // Summary mode
    log.compile(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}) ===`)
    log.compile(`  ${pad_right("slot", 8)}${pad_right("defs", 8)}${pad_right("uses", 8)}${pad_right("type", 12)}first-def`)

    si = 0
    while (si < nr_slots) {
      slot_key = text(si)
      d_count = defs[slot_key] != null ? defs[slot_key] : 0
      u_count = uses[slot_key] != null ? uses[slot_key] : 0

      // Skip slots with no activity unless they're args or have type info
      if (d_count == 0 && u_count == 0 && si >= nr_args + 1) {
        si = si + 1
        continue
      }

      t = "-"
      if (slot_types != null && slot_types[slot_key] != null) {
        t = slot_types[slot_key]
      }

      first = ""
      if (si == 0) {
        first = "(this)"
      } else if (si > 0 && si <= nr_args) {
        first = `(arg ${text(si - 1)})`
      } else if (first_def[slot_key] != null) {
        first = `pc ${text(first_def[slot_key])}: ${first_def_op[slot_key]}`
      }

      dead_marker = ""
      if (d_count > 0 && u_count == 0 && si > nr_args) {
        dead_marker = "  <- dead"
      }

      log.compile(`  ${pad_right("s" + slot_key, 8)}${pad_right(text(d_count), 8)}${pad_right(text(u_count), 8)}${pad_right(t, 12)}${first}${dead_marker}`)
      si = si + 1
    }
    return null
  }

  // Process functions
  main_name = compiled.name != null ? compiled.name : "<main>"

  if (compiled.main != null) {
    if (fn_matches(-1, main_name)) {
      analyze_function(compiled.main, main_name, -1)
    }
  }

  if (compiled.functions != null) {
    fi = 0
    while (fi < length(compiled.functions)) {
      func = compiled.functions[fi]
      fname = func.name != null ? func.name : "<anonymous>"
      if (fn_matches(fi, fname)) {
        analyze_function(func, `[${text(fi)}] ${fname}`, fi)
      }
      fi = fi + 1
    }
  }

  return null
}

run()
$stop()
