///|
/// MoonBit binding code generator for the WIT core subset.
///
/// Maps WIT types and definitions to idiomatic MoonBit:
/// record→struct, enum→unit enum, variant→payload enum, flags→Bool struct,
/// resource→`#external` opaque type, type alias→`type`,
/// func→a `pub fn` stub that aborts. Functions whose bodies are not yet bound
/// are emitted as stubs so the generated package always compiles.
/// Design inspired by bytecodealliance/wit-bindgen.

///|
/// Generate the contents of a `bindings.mbt` file for `pkg`.
pub fn generate(pkg : WitPackage) -> String {
  let mut out = "// Generated by moon-wit. DO NOT EDIT.\n"
  out = out + "// Design inspired by bytecodealliance/wit-bindgen.\n"
  out = out + "\n"
  if needs_future(pkg) {
    out = out + "///|\n#external\ntype WitFuture[T]\n\n"
  }
  if needs_stream(pkg) {
    out = out + "///|\n#external\ntype WitStream[T]\n\n"
  }
  for i in 0.. Bool {
  for item in pkg.items {
    if item_uses_list(item) {
      return true
    }
  }
  false
}

///|
/// Contents of the `moon.pkg` for a generated package.
pub fn generate_moon_pkg(use_list : Bool) -> String {
  if use_list {
    "import {\n  \"moonbitlang/core/list\",\n}\n"
  } else {
    "\n"
  }
}

///|
fn generate_item(item : WitItem) -> String {
  match item {
    Interface(iface) => generate_interface(iface)
    World(w) => generate_world(w)
    Use(u) => "// \{u.to_string()}\n"
  }
}

///|
fn generate_interface(iface : Interface) -> String {
  let mut out = ""
  for i in 0.. String {
  match item {
    Type(t) => generate_typedef(t)
    Func(f) => generate_func(f)
    Use(u) => "// \{u.to_string()}\n"
  }
}

///|
fn generate_world(world : World) -> String {
  let mut out = ""
  for i in 0.. String {
  match item {
    Import(imp) => generate_impexp("import", imp)
    Export(exp) => generate_impexp("export", exp)
    ImportFunc(f) => "// import \{f.name}\n\n" + generate_func(f)
    ExportFunc(f) => "// export \{f.name}\n\n" + generate_func(f)
    Include(path, names) => "// \{Include(path, names).to_string()}\n"
    Use(u) => "// \{u.to_string()}\n"
  }
}

///|
fn generate_impexp(kw : String, imp : ImpExp) -> String {
  let mut out = "// \{kw} \{imp.name}"
  match imp.asName {
    Some(a) => out = out + " as \{a}"
    None => ()
  }
  out = out + "\n"
  match imp.inline {
    Some(iface) => out = out + "\n" + generate_interface(iface)
    None => ()
  }
  out
}

///|
fn generate_typedef(def : TypeDef) -> String {
  let tn = type_name(def.name)
  match def.kind {
    Record(fields) => {
      let mut out = "///|\npub struct \{tn} {\n"
      for f in fields {
        out = out + "  \{field_name(f.name)} : \{type_str(f.ty)}\n"
      }
      out + "}\n"
    }
    Enum(names) => {
      let mut out = "///|\npub enum \{tn} {\n"
      for n in names {
        out = out + "  \{case_name(n)}\n"
      }
      out + "}\n"
    }
    Variant(cases) => {
      let mut out = "///|\npub enum \{tn} {\n"
      for c in cases {
        match c.ty {
          Some(t) => out = out + "  \{case_name(c.name)}(\{type_str(t)})\n"
          None => out = out + "  \{case_name(c.name)}\n"
        }
      }
      out + "}\n"
    }
    Flags(names) => {
      let mut out = "///|\npub type \{tn} = UInt\n"
      for i in 0.. {
      let mut out = "///|\n#external\ntype \{tn}\n"
      for f in funcs {
        let prefix = match f.kind {
          Constructor => "new"
          Static => field_name(f.sig.name)
          Method => field_name(f.sig.name)
        }
        let params = f.sig.params
        let results = match f.kind {
          Constructor => [Unnamed(Name(def.name))]
          _ => f.sig.results
        }
        let generated_name = match f.kind {
          Method => "\{type_name(def.name)}::\{field_name(f.sig.name)}"
          _ => "\{field_name(def.name)}_\{prefix}"
        }
        let sig : FuncSig = { name: generated_name, params, results }
        out = out + "\n" + generate_func(sig)
      }
      out
    }
    Alias(t) => "///|\npub type \{tn} = \{type_str(t)}\n"
  }
}

///|
fn generate_func(f : FuncSig) -> String {
  let mut out = "///|\npub fn \{field_name(f.name)}(\{params_str(f.params)})"
  if f.results.length() == 0 {
    out = out + " -> Unit"
  } else {
    out = out + " -> \{results_str(f.results)}"
  }
  out = out + " {\n"
  for p in f.params {
    out = out + "  ignore(\{field_name(p.name)})\n"
  }
  out = out + "  abort(\"stub: \{f.name}\")\n}\n"
  out
}

///|
fn params_str(params : Array[Field]) -> String {
  join_strings(
    params.map(fn(p) { "\{field_name(p.name)} : \{type_str(p.ty)}" }),
    ", ",
  )
}

///|
fn results_str(results : Array[FuncResult]) -> String {
  if results.length() == 1 {
    match results[0] {
      Unnamed(t) => return type_str(t)
      Named(f) => return type_str(f.ty)
    }
  }
  "(\{join_strings(results.map(fn(r) { type_str_of_result(r) }), ", ")})"
}

///|
fn type_str_of_result(r : FuncResult) -> String {
  match r {
    Unnamed(t) => type_str(t)
    Named(f) => type_str(f.ty)
  }
}

///|
/// WIT type → MoonBit type.
pub fn type_str(t : WitType) -> String {
  match t {
    Name(n) => type_name(n)
    U8 => "Byte"
    U16 => "UInt16"
    U32 => "UInt"
    U64 => "UInt64"
    S8 => "Int"
    S16 => "Int16"
    S32 => "Int"
    S64 => "Int64"
    F32 => "Float"
    F64 => "Double"
    TChar => "Char"
    TString => "String"
    TBool => "Bool"
    List(t) => "@list.List[\{type_str(t)}]"
    Option(t) => "Option[\{type_str(t)}]"
    Result(ok, err) => "Result[\{slot_str(ok)}, \{slot_str(err)}]"
    Tuple(ts) => "(\{join_strings(ts.map(fn(t) { type_str(t) }), ", ")})"
    Own(n) => type_name(n)
    Borrow(n) => type_name(n)
    Future(t) => "WitFuture[\{slot_str(t)}]"
    Stream(t) => "WitStream[\{slot_str(t)}]"
  }
}

///|
fn slot_str(slot : WitType?) -> String {
  match slot {
    Some(t) => type_str(t)
    None => "Unit"
  }
}

///|
/// `hello-world` → `HelloWorld` (MoonBit type/case naming).
pub fn type_name(name : String) -> String {
  kebab_to_pascal(name)
}

///|
/// `first-name` → `FirstName` (MoonBit enum case naming).
pub fn case_name(name : String) -> String {
  kebab_to_pascal(name)
}

///|
/// `first-name` → `first_name` (MoonBit field/function naming).
pub fn field_name(name : String) -> String {
  let ident = kebab_to_snake(name)
  if is_moonbit_keyword(ident) {
    ident + "_"
  } else {
    ident
  }
}

///|
fn is_moonbit_keyword(name : String) -> Bool {
  match name {
    "_"
    | "as"
    | "async"
    | "break"
    | "catch"
    | "const"
    | "continue"
    | "derive"
    | "else"
    | "enum"
    | "extern"
    | "fn"
    | "for"
    | "guard"
    | "if"
    | "impl"
    | "in"
    | "is"
    | "let"
    | "loop"
    | "match"
    | "mut"
    | "priv"
    | "pub"
    | "raise"
    | "return"
    | "struct"
    | "test"
    | "trait"
    | "try"
    | "type"
    | "typealias"
    | "while"
    | "with"
    | "yield" => true
    _ => false
  }
}

///|
fn kebab_to_pascal(s : String) -> String {
  let mut out = ""
  let mut cap_next = true
  for ch in s {
    if ch == '%' {
      continue
    } else if ch == '-' || ch == '_' {
      cap_next = true
    } else if cap_next {
      out = out + upcase_char(ch).to_string()
      cap_next = false
    } else {
      out = out + ch.to_string()
    }
  }
  out
}

///|
fn kebab_to_snake(s : String) -> String {
  let mut out = ""
  for ch in s {
    if ch == '%' {
      continue
    } else if ch == '-' {
      out = out + "_"
    } else {
      out = out + ch.to_string()
    }
  }
  out
}

///|
fn upcase_char(ch : Char) -> Char {
  let code = ch.to_int()
  if code >= 97 && code <= 122 {
    (code - 32).unsafe_to_char()
  } else {
    ch
  }
}

///|
fn item_uses_list(item : WitItem) -> Bool {
  match item {
    Interface(iface) => {
      for i in iface.items {
        if interface_item_uses_list(i) {
          return true
        }
      }
      false
    }
    World(w) => {
      for wi in w.items {
        if world_item_uses_list(wi) {
          return true
        }
      }
      false
    }
    Use(_) => false
  }
}

///|
fn interface_item_uses_list(item : InterfaceItem) -> Bool {
  match item {
    Type(t) => typedef_uses_list(t)
    Func(f) => func_uses_list(f)
    Use(_) => false
  }
}

///|
fn world_item_uses_list(item : WorldItem) -> Bool {
  match item {
    Import(imp) => impexp_uses_list(imp)
    Export(imp) => impexp_uses_list(imp)
    ImportFunc(f) | ExportFunc(f) => func_uses_list(f)
    Include(_, _) => false
    Use(_) => false
  }
}

///|
fn impexp_uses_list(imp : ImpExp) -> Bool {
  match imp.inline {
    Some(iface) => interface_uses_list(iface)
    None => false
  }
}

///|
fn interface_uses_list(iface : Interface) -> Bool {
  for item in iface.items {
    if interface_item_uses_list(item) {
      return true
    }
  }
  false
}

///|
fn typedef_uses_list(def : TypeDef) -> Bool {
  match def.kind {
    Record(fields) => {
      for f in fields {
        if type_uses_list(f.ty) {
          return true
        }
      }
      false
    }
    Variant(cases) => {
      for c in cases {
        match c.ty {
          Some(t) => if type_uses_list(t) { return true }
          None => ()
        }
      }
      false
    }
    Alias(t) => type_uses_list(t)
    Resource(funcs) => {
      for f in funcs {
        if func_uses_list(f.sig) {
          return true
        }
      }
      false
    }
    Enum(_) | Flags(_) => false
  }
}

///|
fn func_uses_list(f : FuncSig) -> Bool {
  for p in f.params {
    if type_uses_list(p.ty) {
      return true
    }
  }
  for r in f.results {
    match r {
      Unnamed(t) => if type_uses_list(t) { return true }
      Named(field) => if type_uses_list(field.ty) { return true }
    }
  }
  false
}

///|
fn type_uses_list(t : WitType) -> Bool {
  match t {
    List(_) => true
    Option(inner) => type_uses_list(inner)
    Result(ok, err) => slot_uses_list(ok) || slot_uses_list(err)
    Future(inner) | Stream(inner) => slot_uses_list(inner)
    Tuple(ts) => {
      for inner in ts {
        if type_uses_list(inner) {
          return true
        }
      }
      false
    }
    _ => false
  }
}

///|
fn needs_future(pkg : WitPackage) -> Bool {
  package_uses_type(pkg, fn(t) { t is Future(_) })
}

///|
fn needs_stream(pkg : WitPackage) -> Bool {
  package_uses_type(pkg, fn(t) { t is Stream(_) })
}

///|
fn package_uses_type(pkg : WitPackage, predicate : (WitType) -> Bool) -> Bool {
  for item in pkg.items {
    if item_contains_type(item, predicate) {
      return true
    }
  }
  false
}

///|
fn item_contains_type(item : WitItem, predicate : (WitType) -> Bool) -> Bool {
  match item {
    Interface(iface) => interface_contains_type(iface, predicate)
    World(world) => {
      for item in world.items {
        let found = match item {
          Import(imp) | Export(imp) =>
            match imp.inline {
              Some(iface) => interface_contains_type(iface, predicate)
              None => false
            }
          ImportFunc(f) | ExportFunc(f) => func_contains_type(f, predicate)
          Include(_, _) | Use(_) => false
        }
        if found {
          return true
        }
      }
      false
    }
    Use(_) => false
  }
}

///|
fn interface_contains_type(
  iface : Interface,
  predicate : (WitType) -> Bool,
) -> Bool {
  for item in iface.items {
    let found = match item {
      Type(def) => typedef_contains_type(def, predicate)
      Func(f) => func_contains_type(f, predicate)
      Use(_) => false
    }
    if found {
      return true
    }
  }
  false
}

///|
fn typedef_contains_type(def : TypeDef, predicate : (WitType) -> Bool) -> Bool {
  match def.kind {
    Record(fields) => fields_contain_type(fields, predicate)
    Variant(cases) => {
      for c in cases {
        match c.ty {
          Some(t) => if wit_type_contains(t, predicate) { return true }
          None => ()
        }
      }
      false
    }
    Alias(t) => wit_type_contains(t, predicate)
    Resource(funcs) => {
      for f in funcs {
        if func_contains_type(f.sig, predicate) {
          return true
        }
      }
      false
    }
    Enum(_) | Flags(_) => false
  }
}

///|
fn func_contains_type(f : FuncSig, predicate : (WitType) -> Bool) -> Bool {
  if fields_contain_type(f.params, predicate) {
    return true
  }
  for result in f.results {
    let t = match result {
      Unnamed(t) => t
      Named(field) => field.ty
    }
    if wit_type_contains(t, predicate) {
      return true
    }
  }
  false
}

///|
fn fields_contain_type(
  fields : Array[Field],
  predicate : (WitType) -> Bool,
) -> Bool {
  for field in fields {
    if wit_type_contains(field.ty, predicate) {
      return true
    }
  }
  false
}

///|
fn wit_type_contains(t : WitType, predicate : (WitType) -> Bool) -> Bool {
  if predicate(t) {
    return true
  }
  match t {
    List(inner) | Option(inner) => wit_type_contains(inner, predicate)
    Result(ok, err) =>
      optional_type_contains(ok, predicate) ||
      optional_type_contains(err, predicate)
    Tuple(items) => {
      for item in items {
        if wit_type_contains(item, predicate) {
          return true
        }
      }
      false
    }
    Future(inner) | Stream(inner) => optional_type_contains(inner, predicate)
    _ => false
  }
}

///|
fn optional_type_contains(
  value : WitType?,
  predicate : (WitType) -> Bool,
) -> Bool {
  match value {
    Some(t) => wit_type_contains(t, predicate)
    None => false
  }
}

///|
fn slot_uses_list(slot : WitType?) -> Bool {
  match slot {
    Some(t) => type_uses_list(t)
    None => false
  }
}