///|
enum CmdOptimizerReportMode {
  Thresholds
  ExpandedPassQueue
  RuntimeInterface
} derive(Eq, Debug)

///|
fn cmd_optimizer_report_flag_mode(flag : String) -> CmdOptimizerReportMode? {
  match flag {
    "--emit-optimizer-thresholds-json" => Some(Thresholds)
    "--emit-expanded-pass-queue-json" => Some(ExpandedPassQueue)
    "--emit-runtime-interface-json" => Some(RuntimeInterface)
    _ => None
  }
}

///|
fn cmd_extract_optimizer_report_mode(
  args : Array[String],
) -> Result[(Array[String], CmdOptimizerReportMode?), CmdError] {
  let filtered : Array[String] = []
  let mut report_mode : CmdOptimizerReportMode? = None
  for arg in args {
    match cmd_optimizer_report_flag_mode(arg) {
      Some(mode) =>
        match report_mode {
          Some(existing) if existing != mode =>
            return Err(
              CmdError::OptimizeFailed(
                "optimizer JSON report flags are mutually exclusive",
              ),
            )
          _ => report_mode = Some(mode)
        }
      None => filtered.push(arg)
    }
  }
  Ok((filtered, report_mode))
}

///|
fn cmd_write_optimizer_report_stdout(
  io : CmdIO,
  report : String,
) -> Result[Unit, CmdError] {
  match (io.write_stdout)(string_to_bytes("\{report}\n")) {
    Ok(_) => Ok(())
    Err(err) =>
      Err(CmdError::OutputWriteFailed("optimizer JSON report: \{err}"))
  }
}

///|
fn cmd_optimizer_json_escape(text : String) -> String {
  let out = StringBuilder::new()
  for ch in text {
    match ch {
      '"' => out.write_string("\\\"")
      '\\' => out.write_string("\\\\")
      '\n' => out.write_string("\\n")
      '\r' => out.write_string("\\r")
      '\t' => out.write_string("\\t")
      _ => out.write_char(ch)
    }
  }
  out.to_string()
}

///|
fn cmd_optimizer_json_string(text : String) -> String {
  "\"\{cmd_optimizer_json_escape(text)}\""
}

///|
fn cmd_optimizer_json_string_array(values : Array[String]) -> String {
  let out = StringBuilder::new()
  out.write_char('[')
  for idx, value in values {
    if idx > 0 {
      out.write_char(',')
    }
    out.write_string(cmd_optimizer_json_string(value))
  }
  out.write_char(']')
  out.to_string()
}

///|
fn cmd_optimizer_hash_hex_digit(value : UInt64) -> Char {
  let nibble = value.to_int()
  if nibble < 10 {
    (48 + nibble).unsafe_to_char()
  } else {
    (87 + nibble).unsafe_to_char()
  }
}

///|
fn cmd_optimizer_hash_bytes(bytes : Bytes) -> String {
  let mut hash = 1469598103934665603UL
  let prime = 1099511628211UL
  for byte in bytes {
    hash = (hash ^ byte.to_uint64()) * prime
  }
  let out = StringBuilder::new()
  let mut shift = 60
  while shift >= 0 {
    out.write_char(cmd_optimizer_hash_hex_digit((hash >> shift) & 0xFUL))
    shift -= 4
  }
  "fnv1a64:\{out}"
}

///|
fn cmd_optimizer_hash_text(text : String) -> String {
  cmd_optimizer_hash_bytes(string_to_bytes_lossy(text))
}

///|
fn cmd_optimizer_threshold_entry(
  name : String,
  value : String,
  source : String,
) -> String {
  "{\"schema\":\"starshine.optimizer-threshold.v1\",\"name\":\{cmd_optimizer_json_string(name)},\"value\":\{value},\"source\":\{cmd_optimizer_json_string(source)}}"
}

///|
fn cmd_optimizer_threshold_report_json(options : OptimizeOptions) -> String {
  let source = "src/cmd/cmd.mbt:resolved OptimizeOptions"
  let entries = [
    cmd_optimizer_threshold_entry(
      "always-inline-max-function-size",
      options.inlining.always_inline_max_size.to_string(),
      source,
    ),
    cmd_optimizer_threshold_entry(
      "one-caller-inline-max-function-size",
      options.inlining.one_caller_inline_max_size.to_string(),
      source,
    ),
    cmd_optimizer_threshold_entry(
      "flexible-inline-max-function-size",
      options.inlining.flexible_inline_max_size.to_string(),
      source,
    ),
    cmd_optimizer_threshold_entry(
      "inline-max-combined-binary-size",
      options.inlining.max_combined_binary_size.to_string(),
      source,
    ),
    cmd_optimizer_threshold_entry(
      "partial-inlining-ifs",
      options.inlining.partial_inlining_ifs.to_string(),
      source,
    ),
    cmd_optimizer_threshold_entry(
      "monomorphize-min-benefit",
      options.monomorphize_min_benefit.to_string(),
      source,
    ),
    cmd_optimizer_threshold_entry(
      "low-memory-bound",
      options.low_memory_bound.to_string(),
      source,
    ),
  ]
  "{\"schema\":\"starshine.optimizer-threshold-registry.v1\",\"source\":\"moonbit-resolved-options\",\"thresholds\":[\{entries.join(",")}]}"
}

///|
fn cmd_optimizer_pass_queue_report_json(
  mod_ : Module,
  options : OptimizeOptions,
  requested_passes : Array[String],
) -> Result[String, String] {
  let expanded = match
    @passes.pass_registry_expanded_names_for_module(
      mod_,
      optimize_options_to_hot_pipeline_options(options),
      requested_passes,
    ) {
    Ok(names) => names
    Err(err) => return Err(err)
  }
  let entries : Array[String] = []
  for ordinal, name in expanded {
    entries.push(
      "{\"ordinal\":\{ordinal},\"name\":\{cmd_optimizer_json_string(name)}}",
    )
  }
  Ok(
    "{\"schema\":\"starshine.optimizer-expanded-pass-queue.v1\",\"requested\":\{cmd_optimizer_json_string_array(requested_passes)},\"optimizeLevel\":\{options.optimize_level},\"shrinkLevel\":\{options.shrink_level},\"passes\":[\{entries.join(",")}]}",
  )
}

///|
fn cmd_optimizer_val_type_name(value_type : @lib.ValType) -> String {
  match value_type {
    @lib.NumTypeValType(@lib.I32NumType) => "i32"
    @lib.NumTypeValType(@lib.I64NumType) => "i64"
    @lib.NumTypeValType(@lib.F32NumType) => "f32"
    @lib.NumTypeValType(@lib.F64NumType) => "f64"
    @lib.VecTypeValType => "v128"
    @lib.RefTypeValType(ref_type) => cmd_optimizer_ref_type_name(ref_type)
    @lib.BotValType => "bottom"
  }
}

///|
fn cmd_optimizer_abs_heap_type_name(heap_type : @lib.AbsHeapType) -> String {
  match heap_type {
    @lib.FuncAbsHeapType | @lib.NoFuncAbsHeapType => "func"
    @lib.ExternAbsHeapType | @lib.NoExternAbsHeapType => "extern"
    @lib.AnyAbsHeapType | @lib.NoneAbsHeapType => "any"
    @lib.EqAbsHeapType => "eq"
    @lib.I31AbsHeapType => "i31"
    @lib.StructAbsHeapType => "struct"
    @lib.ArrayAbsHeapType => "array"
    @lib.ExnAbsHeapType | @lib.NoExnAbsHeapType => "exn"
    @lib.ContAbsHeapType | @lib.NoContAbsHeapType => "cont"
    @lib.WaitqueueAbsHeapType | @lib.NoWaitqueueAbsHeapType => "waitqueue"
    @lib.StringAbsHeapType => "string"
  }
}

///|
fn cmd_optimizer_heap_type_name(heap_type : @lib.HeapType) -> String {
  match heap_type {
    @lib.AbsHeapTypeHeapType(abs) => cmd_optimizer_abs_heap_type_name(abs)
    @lib.SharedAbsHeapTypeHeapType(abs) =>
      "shared \{cmd_optimizer_abs_heap_type_name(abs)}"
    @lib.HeapType(@lib.TypeIdx(index)) => "type[\{index}]"
    @lib.HeapType(@lib.RecIdx(index)) => "rec[\{index}]"
    @lib.DefTypeHeapType(_) => "defined"
  }
}

///|
fn cmd_optimizer_ref_type_name(ref_type : @lib.RefType) -> String {
  match ref_type {
    @lib.AbsHeapTypeRefType(abs) =>
      match abs {
        @lib.FuncAbsHeapType => "funcref"
        @lib.ExternAbsHeapType => "externref"
        @lib.AnyAbsHeapType => "anyref"
        @lib.EqAbsHeapType => "eqref"
        @lib.I31AbsHeapType => "i31ref"
        @lib.StructAbsHeapType => "structref"
        @lib.ArrayAbsHeapType => "arrayref"
        @lib.ExnAbsHeapType => "exnref"
        @lib.ContAbsHeapType => "contref"
        _ => "(ref null \{cmd_optimizer_abs_heap_type_name(abs)})"
      }
    @lib.HeapTypeRefType(nullable, _, heap_type) => {
      let null_text = if nullable { " null" } else { "" }
      "(ref\{null_text} \{cmd_optimizer_heap_type_name(heap_type)})"
    }
  }
}

///|
fn cmd_optimizer_ref_type_nullable(ref_type : @lib.RefType) -> Bool {
  match ref_type {
    @lib.AbsHeapTypeRefType(_) => true
    @lib.HeapTypeRefType(nullable, _, _) => nullable
  }
}

///|
fn cmd_optimizer_value_type_support(value_type : String) -> String {
  match value_type {
    "i32" | "i64" | "f32" | "f64" => "directly-constructible"
    "v128" => "scalar-adapter"
    "funcref"
    | "externref"
    | "anyref"
    | "eqref"
    | "i31ref"
    | "structref"
    | "arrayref" => "retained-fixture"
    _ =>
      if value_type.contains("exn") || value_type.contains("cont") {
        "unsupported"
      } else if value_type.contains("ref null") ||
        value_type.starts_with("(ref type[") ||
        value_type.starts_with("(ref rec[") ||
        value_type == "(ref defined)" {
        "retained-fixture"
      } else {
        "unsupported"
      }
  }
}

///|
fn cmd_optimizer_typed_value_support(
  env : @validate.Env,
  value_type : @lib.ValType,
) -> String {
  match value_type {
    @lib.RefTypeValType(@lib.AbsHeapTypeRefType(abs)) =>
      match abs {
        @lib.ExnAbsHeapType
        | @lib.NoExnAbsHeapType
        | @lib.ContAbsHeapType
        | @lib.NoContAbsHeapType => "unsupported"
        _ =>
          cmd_optimizer_value_type_support(
            cmd_optimizer_val_type_name(value_type),
          )
      }
    @lib.RefTypeValType(@lib.HeapTypeRefType(_, _, heap_type)) =>
      match heap_type {
        @lib.AbsHeapTypeHeapType(abs) | @lib.SharedAbsHeapTypeHeapType(abs) =>
          match abs {
            @lib.ExnAbsHeapType
            | @lib.NoExnAbsHeapType
            | @lib.ContAbsHeapType
            | @lib.NoContAbsHeapType => "unsupported"
            _ => "retained-fixture"
          }
        _ =>
          match env.resolve_heaptype_subtype(heap_type) {
            Some(subtype) =>
              match subtype.get_comptype() {
                @lib.ContCompType(_) => "unsupported"
                _ => "retained-fixture"
              }
            None =>
              cmd_optimizer_value_type_support(
                cmd_optimizer_val_type_name(value_type),
              )
          }
      }
    _ =>
      cmd_optimizer_value_type_support(cmd_optimizer_val_type_name(value_type))
  }
}

///|
fn cmd_optimizer_signature_support(
  env : @validate.Env,
  params : Array[@lib.ValType],
  results : Array[@lib.ValType],
) -> String {
  let mut has_ref = false
  for value_type in params + results {
    if value_type is @lib.VecTypeValType {
      return "scalar-adapter"
    }
    let support = cmd_optimizer_typed_value_support(env, value_type)
    if support == "unsupported" {
      return "unsupported"
    }
    if support == "retained-fixture" {
      has_ref = true
    }
  }
  if has_ref {
    "retained-fixture"
  } else {
    "directly-constructible"
  }
}

///|
fn cmd_optimizer_val_type_array_json(values : Array[@lib.ValType]) -> String {
  let names : Array[String] = []
  for value in values {
    names.push(cmd_optimizer_val_type_name(value))
  }
  cmd_optimizer_json_string_array(names)
}

///|
fn cmd_optimizer_signature_json(func_type : @lib.FuncType?) -> String {
  match func_type {
    Some(@lib.FuncType(params, results)) =>
      "{\"params\":\{cmd_optimizer_val_type_array_json(params)},\"results\":\{cmd_optimizer_val_type_array_json(results)}}"
    None => "{\"params\":[],\"results\":[]}"
  }
}

///|
fn cmd_optimizer_signature_support_for_type(
  env : @validate.Env,
  func_type : @lib.FuncType?,
) -> String {
  match func_type {
    Some(@lib.FuncType(params, results)) =>
      cmd_optimizer_signature_support(env, params, results)
    None => "unsupported"
  }
}

///|
fn cmd_optimizer_limits_parts(
  limits : @lib.Limits,
) -> (String, String, String, Bool) {
  match limits {
    @lib.I32Limits(minimum, maximum) =>
      (
        "i32",
        minimum.to_string(),
        match maximum {
          Some(value) => cmd_optimizer_json_string(value.to_string())
          None => "null"
        },
        false,
      )
    @lib.I64Limits(minimum, maximum) =>
      (
        "i64",
        minimum.to_string(),
        match maximum {
          Some(value) => cmd_optimizer_json_string(value.to_string())
          None => "null"
        },
        true,
      )
  }
}

///|
fn cmd_optimizer_memory_json(
  module_name : String,
  field_name : String,
  index : Int32,
  memory_type : @lib.MemType,
) -> String {
  let @lib.MemType(limits, shared) = memory_type
  let (index_type, minimum, maximum, memory64) = cmd_optimizer_limits_parts(
    limits,
  )
  let support = "directly-constructible"
  "{\"module\":\{cmd_optimizer_json_string(module_name)},\"field\":\{cmd_optimizer_json_string(field_name)},\"kind\":\"memory\",\"index\":\{index},\"indexType\":\{cmd_optimizer_json_string(index_type)},\"minimum\":\{cmd_optimizer_json_string(minimum)},\"maximum\":\{maximum},\"shared\":\{shared},\"memory64\":\{memory64},\"support\":\{cmd_optimizer_json_string(support)}}"
}

///|
fn cmd_optimizer_table_json(
  module_name : String,
  field_name : String,
  index : Int32,
  table_type : @lib.TableType,
) -> String {
  let @lib.TableType(ref_type, limits) = table_type
  let (_, minimum, maximum, _) = cmd_optimizer_limits_parts(limits)
  let element_type = cmd_optimizer_ref_type_name(ref_type)
  let support = if element_type == "funcref" || element_type == "externref" {
    "directly-constructible"
  } else {
    "unsupported"
  }
  "{\"module\":\{cmd_optimizer_json_string(module_name)},\"field\":\{cmd_optimizer_json_string(field_name)},\"kind\":\"table\",\"index\":\{index},\"elementType\":\{cmd_optimizer_json_string(element_type)},\"nullable\":\{cmd_optimizer_ref_type_nullable(ref_type)},\"minimum\":\{cmd_optimizer_json_string(minimum)},\"maximum\":\{maximum},\"support\":\{cmd_optimizer_json_string(support)}}"
}

///|
fn cmd_optimizer_runtime_interface_report_json(mod_ : Module) -> String {
  let env = @validate.Env::new().with_module(mod_)
  let function_types : Array[@lib.TypeIdx] = []
  let global_types : Array[@lib.GlobalType] = []
  let memory_types : Array[@lib.MemType] = []
  let table_types : Array[@lib.TableType] = []
  let tag_types : Array[@lib.TagType] = []
  let import_functions : Array[String] = []
  let import_globals : Array[String] = []
  let import_memories : Array[String] = []
  let import_tables : Array[String] = []
  let import_tags : Array[String] = []
  match mod_.import_sec {
    Some(@lib.ImportSec(imports)) =>
      for import_ in imports {
        let @lib.Import(module_name_raw, field_name_raw, extern_type) = import_
        let module_name = cmd_name_to_string(module_name_raw)
        let field_name = cmd_name_to_string(field_name_raw)
        match extern_type {
          @lib.FuncExternType(type_idx) => {
            let index = function_types.length()
            function_types.push(type_idx)
            let func_type = env.resolve_functype(type_idx)
            import_functions.push(
              "{\"module\":\{cmd_optimizer_json_string(module_name)},\"field\":\{cmd_optimizer_json_string(field_name)},\"kind\":\"function\",\"index\":\{index},\"signature\":\{cmd_optimizer_signature_json(func_type)},\"support\":\{cmd_optimizer_json_string(cmd_optimizer_signature_support_for_type(env, func_type))}}",
            )
          }
          @lib.GlobalExternType(global_type) => {
            let index = global_types.length()
            global_types.push(global_type)
            let @lib.GlobalType(value_type, mutable) = global_type
            let value_name = cmd_optimizer_val_type_name(value_type)
            import_globals.push(
              "{\"module\":\{cmd_optimizer_json_string(module_name)},\"field\":\{cmd_optimizer_json_string(field_name)},\"kind\":\"global\",\"index\":\{index},\"valueType\":\{cmd_optimizer_json_string(value_name)},\"mutable\":\{mutable},\"support\":\{cmd_optimizer_json_string(cmd_optimizer_typed_value_support(env, value_type))}}",
            )
          }
          @lib.MemExternType(memory_type) => {
            let index = memory_types.length()
            memory_types.push(memory_type)
            import_memories.push(
              cmd_optimizer_memory_json(
                module_name, field_name, index, memory_type,
              ),
            )
          }
          @lib.TableExternType(table_type) => {
            let index = table_types.length()
            table_types.push(table_type)
            import_tables.push(
              cmd_optimizer_table_json(
                module_name, field_name, index, table_type,
              ),
            )
          }
          @lib.TagExternType(tag_type) => {
            let index = tag_types.length()
            tag_types.push(tag_type)
            let @lib.TagType(type_idx) = tag_type
            let func_type = env.resolve_functype(type_idx)
            import_tags.push(
              "{\"module\":\{cmd_optimizer_json_string(module_name)},\"field\":\{cmd_optimizer_json_string(field_name)},\"kind\":\"tag\",\"index\":\{index},\"signature\":\{cmd_optimizer_signature_json(func_type)},\"support\":\"unsupported\"}",
            )
          }
        }
      }
    None => ()
  }
  match mod_.func_sec {
    Some(@lib.FuncSec(types)) =>
      for type_idx in types {
        function_types.push(type_idx)
      }
    None => ()
  }
  match mod_.global_sec {
    Some(@lib.GlobalSec(globals)) =>
      for global in globals {
        let @lib.Global(global_type, _) = global
        global_types.push(global_type)
      }
    None => ()
  }
  match mod_.mem_sec {
    Some(@lib.MemSec(memories)) => memory_types.append(memories)
    None => ()
  }
  match mod_.table_sec {
    Some(@lib.TableSec(tables)) =>
      for table in tables {
        let @lib.Table(table_type, _) = table
        table_types.push(table_type)
      }
    None => ()
  }
  match mod_.tag_sec {
    Some(@lib.TagSec(tags)) => tag_types.append(tags)
    None => ()
  }
  let exports : Array[String] = []
  match mod_.export_sec {
    Some(@lib.ExportSec(entries)) =>
      for export_ in entries {
        let @lib.Export(name_raw, extern_idx) = export_
        let name = cmd_name_to_string(name_raw)
        let record = match extern_idx {
          @lib.FuncExternIdx(@lib.FuncIdx(index)) => {
            let func_type = if index.to_int() < function_types.length() {
              env.resolve_functype(function_types[index.to_int()])
            } else {
              None
            }
            "{\"name\":\{cmd_optimizer_json_string(name)},\"kind\":\"function\",\"index\":\{index},\"signature\":\{cmd_optimizer_signature_json(func_type)},\"support\":\{cmd_optimizer_json_string(cmd_optimizer_signature_support_for_type(env, func_type))}}"
          }
          @lib.GlobalExternIdx(@lib.GlobalIdx(index)) =>
            if index.to_int() < global_types.length() {
              let @lib.GlobalType(value_type, mutable) = global_types[index.to_int()]
              let value_name = cmd_optimizer_val_type_name(value_type)
              "{\"name\":\{cmd_optimizer_json_string(name)},\"kind\":\"global\",\"index\":\{index},\"globalType\":{\"valueType\":\{cmd_optimizer_json_string(value_name)},\"mutable\":\{mutable}},\"support\":\{cmd_optimizer_json_string(cmd_optimizer_typed_value_support(env, value_type))}}"
            } else {
              "{\"name\":\{cmd_optimizer_json_string(name)},\"kind\":\"global\",\"index\":\{index},\"support\":\"unsupported\"}"
            }
          @lib.MemExternIdx(@lib.MemIdx(index)) => {
            let support = if index.to_int() < memory_types.length() {
              "directly-constructible"
            } else {
              "unsupported"
            }
            "{\"name\":\{cmd_optimizer_json_string(name)},\"kind\":\"memory\",\"index\":\{index},\"support\":\{cmd_optimizer_json_string(support)}}"
          }
          @lib.TableExternIdx(@lib.TableIdx(index)) => {
            let support = if index.to_int() < table_types.length() {
              let @lib.TableType(ref_type, _) = table_types[index.to_int()]
              let element_type = cmd_optimizer_ref_type_name(ref_type)
              if element_type == "funcref" || element_type == "externref" {
                "directly-constructible"
              } else {
                "unsupported"
              }
            } else {
              "unsupported"
            }
            "{\"name\":\{cmd_optimizer_json_string(name)},\"kind\":\"table\",\"index\":\{index},\"support\":\{cmd_optimizer_json_string(support)}}"
          }
          @lib.TagExternIdx(@lib.TagIdx(index)) =>
            "{\"name\":\{cmd_optimizer_json_string(name)},\"kind\":\"tag\",\"index\":\{index},\"support\":\"unsupported\"}"
        }
        exports.push(record)
      }
    None => ()
  }
  let features = @validate.module_proposal_features(mod_).map(
    @validate.proposal_feature_name,
  )
  if mod_.start_sec is Some(_) {
    features.push("start")
  }
  if memory_types.length() > 1 {
    features.push("multi-memory")
  }
  if table_types.length() > 1 {
    features.push("multi-table")
  }
  for memory_type in memory_types {
    let @lib.MemType(limits, shared) = memory_type
    if shared && !features.contains("threads") {
      features.push("threads")
    }
    if limits is @lib.I64Limits(_, _) && !features.contains("memory64") {
      features.push("memory64")
    }
  }
  let function_imports_json = import_functions.join(",")
  let global_imports_json = import_globals.join(",")
  let memory_imports_json = import_memories.join(",")
  let table_imports_json = import_tables.join(",")
  let tag_imports_json = import_tags.join(",")
  let exports_json = exports.join(",")
  let has_start = mod_.start_sec is Some(_)
  let imports_json = "{\"functions\":[\{function_imports_json}],\"globals\":[\{global_imports_json}],\"memories\":[\{memory_imports_json}],\"tables\":[\{table_imports_json}],\"tags\":[\{tag_imports_json}]}"
  let body = "\"features\":\{cmd_optimizer_json_string_array(features)},\"hasStart\":\{has_start},\"imports\":\{imports_json},\"exports\":[\{exports_json}]"
  let module_hash = match encode_module(mod_) {
    Ok(bytes) => cmd_optimizer_hash_bytes(bytes)
    Err(_) => "unavailable"
  }
  "{\"schema\":\"starshine.optimizer-runtime-interface.v1\",\"moduleHash\":\{cmd_optimizer_json_string(module_hash)},\"interfaceHash\":\{cmd_optimizer_json_string(cmd_optimizer_hash_text(body))},\{body}}"
}