// streamline.ce — run the full compile + optimize pipeline
//
// Usage:
//   cell streamline <file>              Full optimized IR as JSON (default)
//   cell streamline --stats <file>      Summary stats per function
//   cell streamline --ir <file>         Human-readable IR
//   cell streamline --check <file>      Warnings only (e.g. high slot count)
//   cell streamline --types <file>      Optimized IR with type annotations
//   cell streamline --diagnose <file>   Run diagnostics (type errors/warnings)

var fd = use("fd")
var json = use("json")
var shop = use("internal/shop")

var show_stats = false
var show_ir = false
var show_check = false
var show_types = false
var show_diagnose = false
var no_inline = false
var filename = null
var i = 0
var di = 0
var diag = null

for (i = 0; i < length(args); i++) {
  if (args[i] == '--stats') {
    show_stats = true
  } else if (args[i] == '--ir') {
    show_ir = true
  } else if (args[i] == '--check') {
    show_check = true
  } else if (args[i] == '--types') {
    show_types = true
  } else if (args[i] == '--diagnose') {
    show_diagnose = true
  } else if (args[i] == '--no-inline') {
    no_inline = true
  } else if (args[i] == '--help' || args[i] == '-h') {
    log.console("Usage: cell streamline [--stats] [--ir] [--check] [--types] [--diagnose] <file>")
    $stop()
  } else if (!starts_with(args[i], '-')) {
    filename = args[i]
  }
}

if (!filename) {
  log.compile("usage: cell streamline [--stats] [--ir] [--check] [--types] <file>")
  $stop()
}

// Deep copy mcode for before snapshot (needed by --stats, streamline mutates)
var before = null
if (show_stats) {
  before = json.decode(json.encode(shop.mcode_file(filename)))
}

// For --diagnose, compile with _warn enabled to get diagnostics
var optimized = null
var compiled = null
if (show_diagnose) {
  compiled = shop.mcode_file(filename)
  compiled._warn = true
  if (no_inline) compiled._no_inline = true
  optimized = use('streamline')(compiled)
} else if (no_inline) {
  compiled = shop.mcode_file(filename)
  compiled._no_inline = true
  optimized = use('streamline')(compiled)
} else {
  optimized = shop.compile_file(filename)
}

// If no flags, default to full JSON output
if (!show_stats && !show_ir && !show_check && !show_types && !show_diagnose) {
  log.compile(json.encode(optimized, true))
  $stop()
}

// --- Helpers ---

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 json.encode(v)
  if (is_logical(v)) return v ? "true" : "false"
  return text(v)
}

var count_nops = function(func) {
  var instrs = func.instructions
  var nops = 0
  var i = 0
  if (instrs == null) return 0
  while (i < length(instrs)) {
    if (is_text(instrs[i]) && starts_with(instrs[i], "_nop_")) {
      nops = nops + 1
    }
    i = i + 1
  }
  return nops
}

// --- Stats mode ---

var ir_stats = null
if (show_stats || show_ir) {
  ir_stats = use("ir_stats")
}

var print_func_stats = function(func, before_func, name) {
  var nr_args = func.nr_args != null ? func.nr_args : 0
  var nr_slots = func.nr_slots != null ? func.nr_slots : 0
  var nr_close = func.nr_close_slots != null ? func.nr_close_slots : 0
  var stats = ir_stats.detailed_stats(func)
  var nops = count_nops(func)
  var before_stats = before_func ? ir_stats.detailed_stats(before_func) : null
  var before_total = before_stats ? before_stats.instr : stats.instr

  log.compile(`  ${name}`)
  log.compile(`    args=${text(nr_args)}  slots=${text(nr_slots)}  close_slots=${text(nr_close)}`)
  log.compile(`    instructions: ${text(stats.instr)} total, ${text(nops)} nops eliminated`)
  if (before_stats) {
    log.compile(`    before: ${text(before_total)}  after: ${text(stats.instr - nops)}`)
  }
  log.compile(`    load=${text(stats.load)} store=${text(stats.store)} branch=${text(stats.branch)} call=${text(stats.call)}`)
  log.compile(`    guard=${text(stats.guard)} arith=${text(stats.arith)} move=${text(stats.move)} const=${text(stats.const)}`)

  if (nr_slots > 200) {
    log.compile(`    WARNING: nr_slots=${text(nr_slots)} approaching 255 limit`)
  }
}

var print_func_ir = function(func, name) {
  var ir_text = ir_stats.canonical_ir(func, name, {show_nops: true})
  log.compile(ir_text)
}

var check_func = function(func, name) {
  var nr_slots = func.nr_slots != null ? func.nr_slots : 0
  if (nr_slots > 200) {
    log.compile(`WARNING: ${name} has ${text(nr_slots)} slots (approaching 255 limit)`)
  }
}

// --- Types mode (from dump_types.cm) ---

def T_UNKNOWN = "unknown"
def T_INT = "int"
def T_FLOAT = "float"
def T_NUM = "num"
def T_TEXT = "text"
def T_BOOL = "bool"
def T_NULL = "null"
def T_ARRAY = "array"
def T_RECORD = "record"
def T_FUNCTION = "function"

def int_result_ops = {
  bitnot: true, bitand: true, bitor: true,
  bitxor: true, shl: true, shr: true, ushr: true
}
def bool_result_ops = {
  eq_int: true, ne_int: true, lt_int: true, gt_int: true,
  le_int: true, ge_int: true,
  eq_float: true, ne_float: true, lt_float: true, gt_float: true,
  le_float: true, ge_float: true,
  eq_text: true, ne_text: true, lt_text: true, gt_text: true,
  le_text: true, ge_text: true,
  eq_bool: true, ne_bool: true,
  not: true, and: true, or: true,
  is_int: true, is_text: true, is_num: true,
  is_bool: true, is_null: true, is_identical: true,
  is_array: true, is_func: true, is_record: true, is_stone: true
}

var access_value_type = function(val) {
  if (is_number(val)) return is_integer(val) ? T_INT : T_FLOAT
  if (is_text(val)) return T_TEXT
  return T_UNKNOWN
}

var track_types = function(slot_types, instr) {
  var op = instr[0]
  var src_type = null
  if (op == "access") {
    slot_types[text(instr[1])] = access_value_type(instr[2])
  } else if (op == "int") {
    slot_types[text(instr[1])] = T_INT
  } else if (op == "true" || op == "false") {
    slot_types[text(instr[1])] = T_BOOL
  } else if (op == "null") {
    slot_types[text(instr[1])] = T_NULL
  } else if (op == "move") {
    src_type = slot_types[text(instr[2])]
    slot_types[text(instr[1])] = src_type != null ? src_type : T_UNKNOWN
  } else if (int_result_ops[op] == true) {
    slot_types[text(instr[1])] = T_INT
  } else if (op == "concat") {
    slot_types[text(instr[1])] = T_TEXT
  } else if (bool_result_ops[op] == true) {
    slot_types[text(instr[1])] = T_BOOL
  } else if (op == "typeof") {
    slot_types[text(instr[1])] = T_TEXT
  } else if (op == "array") {
    slot_types[text(instr[1])] = T_ARRAY
  } else if (op == "record") {
    slot_types[text(instr[1])] = T_RECORD
  } else if (op == "function") {
    slot_types[text(instr[1])] = T_FUNCTION
  } else if (op == "invoke" || op == "tail_invoke") {
    slot_types[text(instr[2])] = T_UNKNOWN
  } else if (op == "load_field" || op == "load_index" || op == "load_dynamic") {
    slot_types[text(instr[1])] = T_UNKNOWN
  } else if (op == "pop" || op == "get") {
    slot_types[text(instr[1])] = T_UNKNOWN
  } else if (op == "length") {
    slot_types[text(instr[1])] = T_INT
  } else if (op == "add" || op == "subtract" || op == "multiply" ||
             op == "divide" || op == "modulo" || op == "pow" || op == "negate") {
    slot_types[text(instr[1])] = T_UNKNOWN
  }
  return null
}

var type_annotation = function(slot_types, instr) {
  var n = length(instr)
  var parts = []
  var j = 1
  var v = null
  var t = null
  while (j < n - 2) {
    v = instr[j]
    if (is_number(v)) {
      t = slot_types[text(v)]
      if (t != null && t != T_UNKNOWN) {
        push(parts, `s${text(v)}:${t}`)
      }
    }
    j = j + 1
  }
  if (length(parts) == 0) return ""
  return text(parts, " ")
}

var dump_function_typed = function(func, name) {
  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 slot_types = {}
  var i = 0
  var pc = 0
  var instr = null
  var op = null
  var n = 0
  var annotation = null
  var operand_parts = null
  var j = 0
  var operands = null
  var pc_str = null
  var op_str = null
  var line = null
  log.compile(`\n=== ${name} (args=${text(nr_args)}, slots=${text(nr_slots)}) ===`)
  if (instrs == null || length(instrs) == 0) {
    log.compile("  (empty)")
    return null
  }
  while (i < length(instrs)) {
    instr = instrs[i]
    if (is_text(instr)) {
      if (starts_with(instr, "_nop_")) {
        i = i + 1
        continue
      }
      slot_types = {}
      log.compile(`${instr}:`)
    } else if (is_array(instr)) {
      op = instr[0]
      n = length(instr)
      annotation = type_annotation(slot_types, instr)
      operand_parts = []
      j = 1
      while (j < n - 2) {
        push(operand_parts, fmt_val(instr[j]))
        j = j + 1
      }
      operands = text(operand_parts, ", ")
      pc_str = pad_right(text(pc), 5)
      op_str = pad_right(op, 14)
      line = pad_right(`  ${pc_str} ${op_str} ${operands}`, 50)
      if (length(annotation) > 0) {
        log.compile(`${line} ; ${annotation}`)
      } else {
        log.compile(line)
      }
      track_types(slot_types, instr)
      pc = pc + 1
    }
    i = i + 1
  }
  return null
}

// --- Process functions ---

var main_name = optimized.name != null ? optimized.name : "<main>"
var fi = 0
var func = null
var bfunc = null
var fname = null

if (show_stats) {
  log.compile(`\n--- Stats for ${filename} ---`)
}

// Main function
if (optimized.main != null) {
  if (show_stats) {
    print_func_stats(optimized.main, before ? before.main : null, main_name)
  }
  if (show_ir) {
    print_func_ir(optimized.main, main_name)
  }
  if (show_check) {
    check_func(optimized.main, main_name)
  }
  if (show_types) {
    dump_function_typed(optimized.main, main_name)
  }
}

// Sub-functions
if (optimized.functions != null) {
  fi = 0
  while (fi < length(optimized.functions)) {
    func = optimized.functions[fi]
    bfunc = before ? before.functions[fi] : null
    fname = func.name != null ? func.name : `<func_${text(fi)}>`

    if (show_stats) {
      print_func_stats(func, bfunc, fname)
    }
    if (show_ir) {
      print_func_ir(func, fname)
    }
    if (show_check) {
      check_func(func, fname)
    }
    if (show_types) {
      dump_function_typed(func, fname)
    }
    fi = fi + 1
  }
}

if (show_stats) {
  log.compile('---')
}

if (show_diagnose) {
  if (optimized._diagnostics != null && length(optimized._diagnostics) > 0) {
    di = 0
    while (di < length(optimized._diagnostics)) {
      diag = optimized._diagnostics[di]
      log.compile(`${diag.file}:${text(diag.line)}:${text(diag.col)}: ${diag.severity}: ${diag.message}`)
      di = di + 1
    }
    log.compile(`\n${text(length(optimized._diagnostics))} diagnostic(s)`)
  } else {
    log.compile("No diagnostics.")
  }
}

$stop()
