///|
async fn main {
  let args = @env.args()
  // Parse CLI arguments following wasmi pattern:
  // wasm5  --invoke  [...]
  let parsed = parse_args(args)
  match parsed {
    Some((wasm_path, func_name, func_args)) =>
      run_wasm(wasm_path, func_name, func_args)
    None => print_usage()
  }
}

///|
fn print_usage() -> Unit {
  println("Usage: wasm5  --invoke  [...]")
  println("")
  println("Execute a WebAssembly module and invoke an exported function.")
  println("")
  println("Arguments:")
  println("      Path to the WebAssembly binary file (.wasm)")
  println("  --invoke       Specify the exported function to call")
  println("      Name of the exported function")
  println(
    "      Arguments to pass to the function (integers or floats)",
  )
  println("")
  println("Examples:")
  println("  wasm5 myprogram.wasm --invoke add 5 3")
  println("  wasm5 counter.wasm --invoke run 1000000")
}

///|
fn parse_args(args : Array[String]) -> (String, String, Array[String])? {
  // args[0] is the program name
  if args.length() < 4 {
    return None
  }
  let wasm_path = args[1]
  // Find --invoke flag
  let mut invoke_idx = -1
  for i in 2..= args.length() {
    return None
  }
  let func_name = args[invoke_idx + 1]
  // Collect function arguments (everything after function name)
  let func_args : Array[String] = []
  for i = invoke_idx + 2; i < args.length(); i = i + 1 {
    func_args.push(args[i])
  }
  Some((wasm_path, func_name, func_args))
}

///|
fn parse_value(s : String, expected_type : @wasm5.ValType) -> @wasm5.Value? {
  match expected_type {
    I32 =>
      try {
        let n = @string.parse_int(s)
        Some(@wasm5.Value::I32(n.reinterpret_as_uint()))
      } catch {
        _ => None
      }
    I64 =>
      try {
        let n = @string.parse_int64(s)
        Some(@wasm5.Value::I64(n.reinterpret_as_uint64()))
      } catch {
        _ => None
      }
    F32 =>
      try {
        let f = @string.parse_double(s)
        Some(@wasm5.Value::F32(Float::from_double(f)))
      } catch {
        _ => None
      }
    F64 =>
      try {
        let f = @string.parse_double(s)
        Some(@wasm5.Value::F64(f))
      } catch {
        _ => None
      }
    _ => None // Reference types not supported via CLI
  }
}

///|
async fn run_wasm(
  wasm_path : String,
  func_name : String,
  arg_strings : Array[String],
) -> Unit {
  let wasm_bytes = @fs.read_file(wasm_path).binary()
  let module_ = @wasm5.parse(wasm_bytes)
  // Load and compile runtime
  let runtime = @wasm5.Instance::new(module_, @wasm5.Imports::spectest())
  runtime.run_start()
  // Find the function's type to parse arguments correctly
  let func_type = find_export_func_type(module_, func_name)
  match func_type {
    Some(ft) => {
      // Parse arguments according to function signature
      if arg_strings.length() != ft.params.length() {
        println(
          "Error: function '\{func_name}' expects \{ft.params.length()} arguments, got \{arg_strings.length()}",
        )
        return
      }
      let args : Array[@wasm5.Value] = []
      for i in 0.. args.push(v)
          None => {
            println(
              "Error: could not parse argument \{i + 1} '\{arg_strings[i]}' as \{ft.params[i]}",
            )
            return
          }
        }
      }
      let results = runtime.call_compiled(@utf8.encode(func_name), args)
      for line in runtime.get_output() {
        println(line)
      }
      runtime.clear_output()
      // Print results (one per line, just the value)
      for result in results {
        println(format_value(result))
      }
    }
    None =>
      println("Error: function '\{func_name}' not found in module exports")
  }
}

///|
fn format_value(v : @wasm5.Value) -> String {
  match v {
    I32(n) => n.reinterpret_as_int().to_string()
    I64(n) => n.reinterpret_as_int64().to_string()
    F32(f) => f.to_string()
    F64(f) => f.to_string()
    Ref(Some(n)) => "ref(\{n})"
    Ref(None) => "ref.null"
    Funcref(Some(n)) => "funcref(\{n})"
    Funcref(None) => "ref.null func"
    Externref(Some(n)) => "externref(\{n})"
    Externref(None) => "ref.null extern"
  }
}

///|
fn find_export_func_type(
  module_ : @wasm5.Module,
  name : String,
) -> @wasm5.FuncType? {
  // Find the export with the given name
  let name_bytes = @utf8.encode(name)
  for exp in module_.exports {
    if exp.name == name_bytes {
      match exp.desc {
        Func(func_idx) => {
          // Get the type index for this function
          let func_idx_int = func_idx.reinterpret_as_int()
          // Count imported functions
          let mut num_imported = 0
          for imp in module_.imports {
            match imp.desc {
              Func(_) => num_imported += 1
              _ => ()
            }
          }
          // Determine type index
          let type_idx = if func_idx_int < num_imported {
            // Imported function - find type from imports
            let mut import_func_idx = 0
            let mut found_type_idx = -1
            for imp in module_.imports {
              match imp.desc {
                Func(tidx) =>
                  if import_func_idx == func_idx_int {
                    found_type_idx = tidx.reinterpret_as_int()
                    break
                  } else {
                    import_func_idx += 1
                  }
                _ => ()
              }
            }
            found_type_idx
          } else {
            // Local function - index into funcs array (which only has local functions)
            let local_idx = func_idx_int - num_imported
            if local_idx >= 0 && local_idx < module_.funcs.length() {
              module_.funcs[local_idx].reinterpret_as_int()
            } else {
              -1
            }
          }
          if type_idx >= 0 && type_idx < module_.types.length() {
            match module_.types[type_idx] {
              Func(ft) => return Some(ft)
              _ => ()
            }
          }
        }
        _ => ()
      }
    }
  }
  None
}