// mbts - Generate TypeScript definitions from MoonBit .mbti files
//
// Uses moonbitlang/parser to correctly parse MBTI and generate TypeScript

///|
/// Preprocess MBTI content to remove unsupported syntax
/// (pub impl and pub using are not yet supported by mbti_parser)
pub fn preprocess_mbti(content : String) -> String {
  let lines = content.split("\n")
  let filtered = lines.filter(fn(line) {
    let trimmed = line.trim_space().to_string()
    // Skip unsupported syntax in mbti_parser
    not(trimmed.has_prefix("pub impl ")) &&
    not(trimmed.has_prefix("pub using ")) &&
    not(trimmed.has_prefix("pub let ")) &&
    not(is_type_alias(trimmed)) // type alias (pub type X = Y) not supported
  })
  // Convert "pub fn" to "fn" for parser compatibility
  let processed = filtered.map(fn(line) {
    let s = line.to_string()
    if s.trim_space().to_string().has_prefix("pub fn ") {
      s.replace(old="pub fn ", new="fn ")
    } else {
      s
    }
  })
  processed.collect().join("\n")
}

///|
/// Check if line is a type alias (pub type X = Y), not abstract type (pub type X)
fn is_type_alias(line : String) -> Bool {
  if line.has_prefix("pub type ") {
    line.contains(" = ")
  } else {
    false
  }
}

///|
/// Parse MBTI content and return the AST
pub fn parse_mbti(content : String, filename : String) -> @mbti_ast.Mbti raise {
  let preprocessed = preprocess_mbti(content)
  let lex_result = @lexer.tokens_from_string(
    name=filename,
    preprocessed,
    comment=false,
  )
  let tokens = lex_result.tokens.filter(fn(triple) {
    not(triple.0 is (NEWLINE | COMMENT(_)))
  })
  @mbti_parser.mbti(tokens, initial_pos=@basic.Position::{
    fname: filename,
    lnum: 1,
    bol: 0,
    cnum: 0,
  })
}

///|
/// Extract namespace name from package name
/// e.g., "moonbitlang/parser/basic" -> "basic"
pub fn package_to_namespace(package_name : String) -> String {
  let parts : Array[String] = package_name
    .split("/")
    .map(fn(s) { s.to_string() })
    .collect()
  if parts.length() > 0 {
    parts[parts.length() - 1]
  } else {
    package_name
  }
}

///|
/// Generate preamble with runtime type definitions
fn generate_preamble() -> String {
  let buf = StringBuilder::new()
  buf.write_string("// Runtime types\n")
  buf.write_string("export interface Ref { value: T }\n")
  buf.write_string("\n")
  buf.to_string()
}

///|
/// Generate TypeScript .d.ts content from MBTI AST
pub fn generate_dts(mbti : @mbti_ast.Mbti) -> String {
  generate_dts_with_preamble(mbti, include_preamble=false)
}

///|
/// Generate TypeScript .d.ts with optional preamble
pub fn generate_dts_with_preamble(
  mbti : @mbti_ast.Mbti,
  include_preamble~ : Bool,
) -> String {
  let buf = StringBuilder::new()
  buf.write_string("// Generated from .mbti file - DO NOT EDIT\n\n")
  if include_preamble {
    buf.write_string(generate_preamble())
  }

  // Generate each signature
  for item in mbti.sigs {
    let (sig, _loc) = item
    let ts = generate_sig(sig)
    if ts.length() > 0 {
      buf.write_string(ts)
      buf.write_string("\n\n")
    }
  }
  buf.to_string().trim_space().to_string()
}

///|
/// Generate TypeScript .d.ts content wrapped in a namespace
pub fn generate_dts_namespace(mbti : @mbti_ast.Mbti) -> String {
  let ns = package_to_namespace(mbti.package_name)
  let buf = StringBuilder::new()
  buf.write_string("declare namespace ")
  buf.write_string(ns)
  buf.write_string(" {\n")

  // Generate each signature with indentation
  for item in mbti.sigs {
    let (sig, _loc) = item
    let ts = generate_sig(sig)
    if ts.length() > 0 {
      // Indent each line
      for line in ts.split("\n") {
        buf.write_string("  ")
        buf.write_string(line.to_string())
        buf.write_string("\n")
      }
      buf.write_string("\n")
    }
  }
  buf.write_string("}\n")
  buf.to_string()
}

///|
/// Generate combined TypeScript .d.ts from multiple MBTI packages
pub fn generate_combined_dts(packages : Array[@mbti_ast.Mbti]) -> String {
  let buf = StringBuilder::new()
  buf.write_string("// Generated from .mbti files - DO NOT EDIT\n")
  buf.write_string("// MoonBit Parser TypeScript Definitions\n\n")
  for pkg in packages {
    buf.write_string(generate_dts_namespace(pkg))
    buf.write_string("\n")
  }
  buf.to_string().trim_space().to_string()
}

///|
fn generate_sig(sig : @mbti_ast.Sig) -> String {
  match sig {
    Func(func_sig) => generate_func_sig(func_sig)
    Type(type_sig) => generate_type_sig(type_sig)
    Alias(alias_sig) => generate_alias_sig(alias_sig)
    Trait(trait_sig) => generate_trait_sig(trait_sig)
    Impl(_) => "" // Skip impl signatures for now
    Const(const_sig) => generate_const_sig(const_sig)
    Value(value_sig) => generate_value_sig(value_sig)
  }
}

///|
/// Replace Self with actual type name in generated code
fn replace_self(code : String, type_name : String) -> String {
  let buf = StringBuilder::new()
  let chars : Array[Char] = code.to_array()
  let len = chars.length()
  let mut i = 0
  while i < len {
    // Check for "Self" (must not be preceded/followed by alphanumeric)
    if i + 4 <= len &&
      chars[i] == 'S' &&
      chars[i + 1] == 'e' &&
      chars[i + 2] == 'l' &&
      chars[i + 3] == 'f' {
      // Check if preceded by alphanumeric
      let preceded_by_alnum = i > 0 && is_alnum(chars[i - 1])
      // Check if followed by alphanumeric
      let followed_by_alnum = i + 4 < len && is_alnum(chars[i + 4])
      if not(preceded_by_alnum) && not(followed_by_alnum) {
        buf.write_string(type_name)
        i += 4
        continue
      }
    }
    buf.write_char(chars[i])
    i += 1
  }
  buf.to_string()
}

///|
fn is_alnum(c : Char) -> Bool {
  let code = c.to_int()
  (code >= 48 && code <= 57) || // 0-9
  (code >= 65 && code <= 90) || // A-Z
  (code >= 97 && code <= 122) || // a-z
  c == '_'
}

///|
fn generate_func_sig(func : @mbti_ast.FuncSig) -> String {
  let buf = StringBuilder::new()
  let func_name = escape_func_name(to_camel_case(func.name.name))

  // Generate type parameters
  let type_params = generate_type_params(func.type_params)

  // Generate parameters
  let params = generate_params(func.params)

  // Generate return type
  let (ret_type, _err_type) = func.return_
  let return_ts = generate_type(ret_type)
  match func.type_name {
    Some(type_name) => {
      // Method: Type::method -> Type$method
      let ts_type_name = type_name.name
      buf.write_string("export function ")
      buf.write_string(ts_type_name)
      buf.write_string("$")
      buf.write_string(to_camel_case(func.name.name))
      buf.write_string(type_params)
      buf.write_string("(")
      buf.write_string(params)
      buf.write_string("): ")
      buf.write_string(return_ts)
      buf.write_string(";")
      // Replace Self with actual type name
      replace_self(buf.to_string(), ts_type_name)
    }
    None => {
      // Standalone function
      buf.write_string("export function ")
      buf.write_string(func_name)
      buf.write_string(type_params)
      buf.write_string("(")
      buf.write_string(params)
      buf.write_string("): ")
      buf.write_string(return_ts)
      buf.write_string(";")
      buf.to_string()
    }
  }
}

///|
/// Check if type name conflicts with TypeScript built-in types
fn is_ts_builtin_type(name : String) -> Bool {
  match name {
    "Promise"
    | "Error"
    | "Array"
    | "Map"
    | "Set"
    | "Date"
    | "RegExp"
    | "Symbol"
    | "Function"
    | "Object"
    | "Number"
    | "String"
    | "Boolean" => true
    _ => false
  }
}

///|
fn generate_type_sig(type_sig : @mbti_ast.TypeSig) -> String {
  let buf = StringBuilder::new()
  let name = type_sig.name.name
  let type_params = generate_type_params_no_constraints(type_sig.type_params)
  match type_sig.components {
    Abstract =>
      // Skip TypeScript built-in types (use TS native ones)
      if is_ts_builtin_type(name) {
        buf.write_string("// Using TypeScript built-in: ")
        buf.write_string(name)
      } else {
        buf.write_string("export interface ")
        buf.write_string(name)
        buf.write_string(type_params)
        buf.write_string(" {\n  readonly __brand: \"")
        buf.write_string(name)
        buf.write_string("\";\n}")
      }
    Extern =>
      // Skip TypeScript built-in types (use TS native ones)
      if is_ts_builtin_type(name) {
        buf.write_string("// Using TypeScript built-in: ")
        buf.write_string(name)
      } else {
        buf.write_string("export interface ")
        buf.write_string(name)
        buf.write_string(type_params)
        buf.write_string(" {\n  readonly __brand: \"")
        buf.write_string(name)
        buf.write_string("\";\n}")
      }
    Newtype(ty) => {
      buf.write_string("export type ")
      buf.write_string(name)
      buf.write_string(type_params)
      buf.write_string(" = ")
      buf.write_string(generate_type(ty))
      buf.write_string(";")
    }
    Variant(constrs) => generate_enum_type(buf, name, type_params, constrs)
    Record(fields) => generate_struct_type(buf, name, type_params, fields)
    TupleStruct(types) => {
      buf.write_string("export type ")
      buf.write_string(name)
      buf.write_string(type_params)
      buf.write_string(" = [")
      let types_arr = types.to_array()
      for i, ty in types_arr {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(generate_type(ty))
      }
      buf.write_string("];")
    }
    Alias(ty) => {
      buf.write_string("export type ")
      buf.write_string(name)
      buf.write_string(type_params)
      buf.write_string(" = ")
      buf.write_string(generate_type(ty))
      buf.write_string(";")
    }
    Error(exception_decl) =>
      match exception_decl {
        NoPayload => {
          // Simple error type with no payload
          buf.write_string("export interface ")
          buf.write_string(name)
          buf.write_string(" extends Error {\n  readonly $tag: \"")
          buf.write_string(name)
          buf.write_string("\";\n}")
        }
        SinglePayload(ty) => {
          // Error with single payload
          buf.write_string("export interface ")
          buf.write_string(name)
          buf.write_string(" extends Error {\n  readonly $tag: \"")
          buf.write_string(name)
          buf.write_string("\";\n  readonly $0: ")
          buf.write_string(generate_type(ty))
          buf.write_string(";\n}")
        }
        EnumPayload(constrs) =>
          // Error with enum-like variants - same as enum
          generate_error_enum_type(buf, name, type_params, constrs)
      }
  }
  buf.to_string()
}

///|
fn generate_error_enum_type(
  buf : StringBuilder,
  name : String,
  type_params : String,
  constrs : @list.List[@syntax.ConstrDecl],
) -> Unit {
  // Same as generate_enum_type but extends Error
  generate_enum_type_internal(
    buf,
    name,
    type_params,
    constrs,
    extends_error=true,
  )
}

///|
fn generate_enum_type(
  buf : StringBuilder,
  name : String,
  type_params : String,
  constrs : @list.List[@syntax.ConstrDecl],
) -> Unit {
  generate_enum_type_internal(
    buf,
    name,
    type_params,
    constrs,
    extends_error=false,
  )
}

///|
fn generate_enum_type_internal(
  buf : StringBuilder,
  name : String,
  type_params : String,
  constrs : @list.List[@syntax.ConstrDecl],
  extends_error~ : Bool,
) -> Unit {
  let constrs_arr = constrs.to_array()

  // Generate individual variant types
  for constr in constrs_arr {
    let variant_name = constr.name.name
    buf.write_string("export interface ")
    buf.write_string(name)
    buf.write_string("_")
    buf.write_string(variant_name)
    buf.write_string(type_params)
    if extends_error {
      buf.write_string(" extends Error")
    }
    buf.write_string(" { readonly $tag: \"")
    buf.write_string(variant_name)
    buf.write_string("\"")

    // Add payload fields if any
    match constr.args {
      Some(args) => {
        let args_arr = args.to_array()
        for i, arg in args_arr {
          buf.write_string("; readonly ")
          // Use label name if available, otherwise use positional $N
          match arg.label {
            Some(label) => buf.write_string(escape_func_name(label.name))
            None => {
              buf.write_string("$")
              buf.write_string(i.to_string())
            }
          }
          buf.write_string(": ")
          buf.write_string(generate_type(arg.ty))
        }
      }
      None => ()
    }
    buf.write_string("; }\n")
  }

  // Generate union type
  buf.write_string("export type ")
  buf.write_string(name)
  buf.write_string(type_params)
  buf.write_string(" = ")
  for i, constr in constrs_arr {
    if i > 0 {
      buf.write_string(" | ")
    }
    buf.write_string(name)
    buf.write_string("_")
    buf.write_string(constr.name.name)
    buf.write_string(type_params)
  }
  buf.write_string(";\n\n")

  // Generate flat variant constructors (tree-shakeable)
  for constr in constrs_arr {
    let variant_name = constr.name.name
    match constr.args {
      Some(args) => {
        // Tuple variant - constructor function
        let args_arr = args.to_array()
        buf.write_string("export function ")
        buf.write_string(name)
        buf.write_string("$")
        buf.write_string(variant_name)
        buf.write_string("(")
        for i, arg in args_arr {
          if i > 0 {
            buf.write_string(", ")
          }
          // Use label name if available, escape reserved words
          let param_name = match arg.label {
            Some(label) => escape_func_name(label.name)
            None => "$\{i}"
          }
          buf.write_string(param_name)
          buf.write_string(": ")
          buf.write_string(generate_type(arg.ty))
        }
        buf.write_string("): ")
        buf.write_string(name)
        buf.write_string("_")
        buf.write_string(variant_name)
        buf.write_string(";\n")
      }
      None => {
        // Unit variant - constant
        buf.write_string("export const ")
        buf.write_string(name)
        buf.write_string("$")
        buf.write_string(variant_name)
        buf.write_string(": ")
        buf.write_string(name)
        buf.write_string("_")
        buf.write_string(variant_name)
        buf.write_string(";\n")
      }
    }
  }
}

///|
fn generate_struct_type(
  buf : StringBuilder,
  name : String,
  type_params : String,
  fields : @list.List[@syntax.FieldDecl],
) -> Unit {
  buf.write_string("export interface ")
  buf.write_string(name)
  buf.write_string(type_params)
  buf.write_string(" {\n")
  for field in fields {
    let field_name = field.name.label
    let ts_type = generate_type(field.ty)
    let is_readonly = if field.mut_ { "" } else { "readonly " }
    buf.write_string("  ")
    buf.write_string(is_readonly)
    buf.write_string(field_name)
    buf.write_string(": ")
    buf.write_string(ts_type)
    buf.write_string(";\n")
  }
  buf.write_string("}")
}

///|
fn generate_alias_sig(alias_sig : @mbti_ast.AliasSig) -> String {
  match alias_sig {
    TypeAlias(name~, type_params~, type_~, vis~) => {
      ignore(vis)
      let buf = StringBuilder::new()
      buf.write_string("export type ")
      buf.write_string(name.name)
      buf.write_string(generate_type_params_no_constraints(type_params))
      buf.write_string(" = ")
      buf.write_string(generate_type(type_))
      buf.write_string(";")
      buf.to_string()
    }
    TraitAlias(name~, trait_name~, vis~) => {
      ignore(vis)
      let buf = StringBuilder::new()
      buf.write_string("// Trait alias: ")
      buf.write_string(name.name)
      buf.write_string(" = ")
      buf.write_string(qualified_name_to_string(trait_name))
      buf.to_string()
    }
    FnAlias(name~, type_name~, loc~) => {
      ignore(loc)
      let buf = StringBuilder::new()
      buf.write_string("// Fn alias: ")
      buf.write_string(name.name)
      buf.write_string(" = ")
      buf.write_string(qualified_name_to_string(type_name))
      buf.to_string()
    }
  }
}

///|
fn generate_trait_sig(trait_sig : @mbti_ast.TraitSig) -> String {
  let trait_name = trait_sig.name.name
  let buf = StringBuilder::new()
  buf.write_string("// trait ")
  buf.write_string(trait_name)
  buf.write_string("\n")
  for m in trait_sig.methods {
    let params = generate_trait_method_params(m.params)
    let (ret_type, _) = m.return_
    buf.write_string("export function ")
    buf.write_string(trait_name)
    buf.write_string("$")
    buf.write_string(to_camel_case(m.name.name))
    buf.write_string("(")
    buf.write_string(params)
    buf.write_string("): ")
    buf.write_string(generate_type(ret_type))
    buf.write_string(";\n")
  }
  buf.to_string().trim_space().to_string()
}

///|
fn generate_const_sig(const_sig : @mbti_ast.ConstSig) -> String {
  let buf = StringBuilder::new()
  buf.write_string("export const ")
  buf.write_string(const_sig.name.name)
  buf.write_string(": ")
  buf.write_string(generate_type(const_sig.type_))
  buf.write_string(";")
  buf.to_string()
}

///|
fn generate_value_sig(value_sig : @mbti_ast.ValueSig) -> String {
  let buf = StringBuilder::new()
  buf.write_string("export const ")
  buf.write_string(value_sig.name.name)
  buf.write_string(": ")
  buf.write_string(generate_type(value_sig.type_))
  buf.write_string(";")
  buf.to_string()
}

///|
fn generate_type(ty : @syntax.Type) -> String {
  match ty {
    Any(_) => "any"
    Arrow(args~, res~, err~, is_async~, loc~) => {
      ignore(err)
      ignore(loc)
      let buf = StringBuilder::new()
      match is_async {
        Some(_) => {
          buf.write_string("Promise<")
          buf.write_string(generate_type(res))
          buf.write_string(">")
        }
        None => {
          buf.write_string("(")
          let args_arr = args.to_array()
          for i, arg in args_arr {
            if i > 0 {
              buf.write_string(", ")
            }
            buf.write_string("arg")
            buf.write_string(i.to_string())
            buf.write_string(": ")
            buf.write_string(generate_type(arg))
          }
          buf.write_string(") => ")
          buf.write_string(generate_type(res))
        }
      }
      buf.to_string()
    }
    Tuple(tys~, loc~) => {
      ignore(loc)
      let buf = StringBuilder::new()
      buf.write_string("[")
      let tys_arr = tys.to_array()
      for i, t in tys_arr {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(generate_type(t))
      }
      buf.write_string("]")
      buf.to_string()
    }
    Name(constr_id~, tys~, loc~) => {
      ignore(loc)
      let name = long_ident_to_string(constr_id.id)
      // External package reference (e.g., @pkg.Type) - output as any with TODO comment
      if name.contains(".") {
        "any /* TODO: @\{name} */"
      } else {
        let mapped = map_moonbit_type(name)
        let tys_arr = tys.to_array()
        if tys_arr.length() == 0 {
          mapped
        } else {
          let buf = StringBuilder::new()
          buf.write_string(mapped)
          buf.write_string("<")
          for i, t in tys_arr {
            if i > 0 {
              buf.write_string(", ")
            }
            buf.write_string(generate_type(t))
          }
          buf.write_string(">")
          buf.to_string()
        }
      }
    }
    Option(ty~, loc~, question_loc~) => {
      ignore(loc)
      ignore(question_loc)
      let buf = StringBuilder::new()
      buf.write_string(generate_type(ty))
      buf.write_string(" | undefined")
      buf.to_string()
    }
    Object(constr_id) => long_ident_to_string(constr_id.id)
  }
}

///|
fn map_moonbit_type(name : String) -> String {
  match name {
    "String" => "string"
    "Int" | "UInt" | "Float" | "Double" | "Int64" | "UInt64" => "number"
    "Byte" => "number"
    "Bool" => "boolean"
    "Unit" => "void"
    "Bytes" => "Uint8Array"
    "BigInt" => "bigint"
    "Array" | "FixedArray" => "Array"
    "Map" => "Map"
    "Json" => "any"
    "Ref" => "Ref" // Will be defined in preamble
    _ => name
  }
}

///|
fn generate_params(params : @list.List[@mbti_ast.Parameter]) -> String {
  let buf = StringBuilder::new()
  let params_arr = params.to_array()
  let mut pos_index = 0
  for i, param in params_arr {
    if i > 0 {
      buf.write_string(", ")
    }
    match param {
      Positional(ty) => {
        buf.write_string("arg")
        buf.write_string(pos_index.to_string())
        buf.write_string(": ")
        buf.write_string(generate_type(ty))
        pos_index += 1
      }
      Labelled(label, ty) => {
        buf.write_string(label.name)
        buf.write_string(": ")
        buf.write_string(generate_type(ty))
      }
      Autofill(label, ty) => {
        buf.write_string(label.name)
        buf.write_string("?: ")
        buf.write_string(generate_type(ty))
      }
      OptionalDefault(label, ty) => {
        buf.write_string(label.name)
        buf.write_string("?: ")
        buf.write_string(generate_type(ty))
      }
      OptionalOption(label, ty) => {
        buf.write_string(label.name)
        buf.write_string("?: ")
        buf.write_string(generate_type(ty))
      }
    }
  }
  buf.to_string()
}

///|
fn generate_trait_method_params(
  params : @list.List[@mbti_ast.TraitMethodParameter],
) -> String {
  let buf = StringBuilder::new()
  let params_arr = params.to_array()
  let mut pos_index = 0
  for i, param in params_arr {
    if i > 0 {
      buf.write_string(", ")
    }
    match param {
      Positional(ty) => {
        buf.write_string("arg")
        buf.write_string(pos_index.to_string())
        buf.write_string(": ")
        buf.write_string(generate_type(ty))
        pos_index += 1
      }
      Labelled(label, ty) => {
        buf.write_string(label.name)
        buf.write_string(": ")
        buf.write_string(generate_type(ty))
      }
    }
  }
  buf.to_string()
}

///|
fn generate_type_params(
  params : @list.List[@mbti_ast.TypeParamWithConstraints],
) -> String {
  let params_arr = params.to_array()
  if params_arr.length() == 0 {
    return ""
  }
  let buf = StringBuilder::new()
  buf.write_string("<")
  for i, param in params_arr {
    if i > 0 {
      buf.write_string(", ")
    }
    buf.write_string(param.name.name)
  }
  buf.write_string(">")
  buf.to_string()
}

///|
fn generate_type_params_no_constraints(
  params : @list.List[@mbti_ast.TypeParamNoConstraints],
) -> String {
  let params_arr = params.to_array()
  if params_arr.length() == 0 {
    return ""
  }
  let buf = StringBuilder::new()
  buf.write_string("<")
  for i, param in params_arr {
    if i > 0 {
      buf.write_string(", ")
    }
    match param {
      Name(name) => buf.write_string(name.name)
      Underscore(_) => buf.write_string("_")
    }
  }
  buf.write_string(">")
  buf.to_string()
}

///|
fn long_ident_to_string(id : @syntax.LongIdent) -> String {
  match id {
    Ident(name~) => name
    Dot(pkg~, id~) => "\{pkg}.\{id}"
  }
}

///|
fn qualified_name_to_string(qn : @mbti_ast.QualifiedName) -> String {
  qualified_ident_to_string(qn.name)
}

///|
fn qualified_ident_to_string(qi : @mbti_ast.QualifiedIdent) -> String {
  match qi {
    Ident(name~) => name
    Dot(pkg~, id~) => "\{pkg}.\{id}"
  }
}

///|
fn to_camel_case(s : String) -> String {
  // Convert snake_case to camelCase
  let buf = StringBuilder::new()
  let mut next_upper = false
  for c in s {
    if c == '_' {
      next_upper = true
    } else if next_upper {
      buf.write_char(char_to_upper(c))
      next_upper = false
    } else {
      buf.write_char(c)
    }
  }
  buf.to_string()
}

///|
fn char_to_upper(c : Char) -> Char {
  let code = c.to_int()
  if code >= 97 && code <= 122 {
    // 'a' to 'z'
    (code - 32).unsafe_to_char()
  } else {
    c
  }
}

///|
/// Check if a name is a TypeScript/JavaScript reserved word
fn is_ts_reserved(name : String) -> Bool {
  match name {
    // JavaScript reserved words
    "break"
    | "case"
    | "catch"
    | "continue"
    | "debugger"
    | "default"
    | "delete"
    | "do"
    | "else"
    | "finally"
    | "for"
    | "function"
    | "if"
    | "in"
    | "instanceof"
    | "new"
    | "return"
    | "switch"
    | "this"
    | "throw"
    | "try"
    | "typeof"
    | "var"
    | "void"
    | "while"
    | "with"
    | "class"
    | "const"
    | "enum"
    | "export"
    | "extends"
    | "import"
    | "super"
    | "implements"
    | "interface"
    | "let"
    | "package"
    | "private"
    | "protected"
    | "public"
    | "static"
    | "yield"
    // Literal values
    | "null"
    | "undefined"
    | "true"
    | "false"
    | "NaN"
    | "Infinity"
    // TypeScript specific
    | "any"
    | "boolean"
    | "number"
    | "string"
    | "symbol"
    | "bigint"
    | "never"
    | "unknown"
    | "object"
    | "type"
    | "declare"
    | "as"
    | "from"
    | "async"
    | "await" => true
    _ => false
  }
}

///|
/// Escape function name if it's a reserved word
fn escape_func_name(name : String) -> String {
  if is_ts_reserved(name) {
    name + "_"
  } else {
    name
  }
}

// ============================================================
// .mbt file parsing and .d.ts generation
// ============================================================

///|
/// Parse .mbt content and return the AST
pub fn parse_mbt(
  content : String,
  filename : String,
) -> (@list.List[@syntax.Impl], Array[@basic.Report]) {
  @parser.parse_string(content, name=filename)
}

///|
/// Generate TypeScript .d.ts from .mbt source code
pub fn generate_dts_from_mbt(content : String, filename : String) -> String {
  let (impls, _reports) = parse_mbt(content, filename)
  let buf = StringBuilder::new()
  buf.write_string("// Generated from .mbt file - DO NOT EDIT\n\n")

  // Generate each top-level declaration
  for impl_ in impls {
    let ts = generate_impl(impl_)
    if ts.length() > 0 {
      buf.write_string(ts)
      buf.write_string("\n\n")
    }
  }
  buf.to_string().trim_space().to_string()
}

///|
/// Generate TypeScript for a single Impl (top-level declaration)
fn generate_impl(impl_ : @syntax.Impl) -> String {
  match impl_ {
    TopTypeDef(type_decl) => generate_type_decl(type_decl)
    TopFuncDef(fun_decl~, decl_body~, loc~) => {
      ignore(decl_body)
      ignore(loc)
      generate_fun_decl(fun_decl)
    }
    TopTrait(trait_decl) => generate_mbt_trait_decl(trait_decl)
    TopLetDef(binder~, ty~, expr~, vis~, is_constant~, loc~, attrs~, doc~) => {
      ignore(expr)
      ignore(is_constant)
      ignore(loc)
      ignore(attrs)
      ignore(doc)
      // Only export pub items
      if is_pub_visibility(vis) {
        let buf = StringBuilder::new()
        buf.write_string("export const ")
        buf.write_string(binder.name)
        buf.write_string(": ")
        match ty {
          Some(t) => buf.write_string(generate_type(t))
          None => buf.write_string("any")
        }
        buf.write_string(";")
        buf.to_string()
      } else {
        ""
      }
    }
    _ => "" // Skip other declarations for now
  }
}

///|
/// Check if visibility is pub
fn is_pub_visibility(vis : @syntax.Visibility) -> Bool {
  match vis {
    Pub(..) => true
    _ => false
  }
}

///|
/// Generate TypeScript for TypeDecl
fn generate_type_decl(decl : @syntax.TypeDecl) -> String {
  // Only export pub types
  if not(is_pub_visibility(decl.type_vis)) {
    return ""
  }
  let name = decl.tycon
  let type_params = generate_mbt_type_params(decl.params)
  let buf = StringBuilder::new()
  match decl.components {
    Abstract =>
      if is_ts_builtin_type(name) {
        buf.write_string("// Using TypeScript built-in: ")
        buf.write_string(name)
      } else {
        buf.write_string("export interface ")
        buf.write_string(name)
        buf.write_string(type_params)
        buf.write_string(" {\n  readonly __brand: \"")
        buf.write_string(name)
        buf.write_string("\";\n}")
      }
    Extern =>
      if is_ts_builtin_type(name) {
        buf.write_string("// Using TypeScript built-in: ")
        buf.write_string(name)
      } else {
        buf.write_string("export interface ")
        buf.write_string(name)
        buf.write_string(type_params)
        buf.write_string(" {\n  readonly __brand: \"")
        buf.write_string(name)
        buf.write_string("\";\n}")
      }
    Newtype(ty) => {
      buf.write_string("export type ")
      buf.write_string(name)
      buf.write_string(type_params)
      buf.write_string(" = ")
      buf.write_string(generate_type(ty))
      buf.write_string(";")
    }
    Variant(constrs) => generate_enum_type(buf, name, type_params, constrs)
    Record(fields) => generate_struct_type(buf, name, type_params, fields)
    TupleStruct(types) => {
      buf.write_string("export type ")
      buf.write_string(name)
      buf.write_string(type_params)
      buf.write_string(" = [")
      let types_arr = types.to_array()
      for i, ty in types_arr {
        if i > 0 {
          buf.write_string(", ")
        }
        buf.write_string(generate_type(ty))
      }
      buf.write_string("];")
    }
    Alias(ty) => {
      buf.write_string("export type ")
      buf.write_string(name)
      buf.write_string(type_params)
      buf.write_string(" = ")
      buf.write_string(generate_type(ty))
      buf.write_string(";")
    }
    Error(_) => {
      buf.write_string("export interface ")
      buf.write_string(name)
      buf.write_string(" extends Error {\n  readonly $tag: \"")
      buf.write_string(name)
      buf.write_string("\";\n}")
    }
  }
  buf.to_string()
}

///|
/// Generate TypeScript type params from MBT TypeDeclBinder list
fn generate_mbt_type_params(
  params : @list.List[@syntax.TypeDeclBinder],
) -> String {
  let params_arr = params.to_array()
  if params_arr.length() == 0 {
    return ""
  }
  let buf = StringBuilder::new()
  buf.write_string("<")
  for i, param in params_arr {
    if i > 0 {
      buf.write_string(", ")
    }
    match param.name {
      Some(name) => buf.write_string(name)
      None => buf.write_string("_")
    }
  }
  buf.write_string(">")
  buf.to_string()
}

///|
/// Generate TypeScript for FunDecl
fn generate_fun_decl(decl : @syntax.FunDecl) -> String {
  // Only export pub functions
  if not(is_pub_visibility(decl.vis)) {
    return ""
  }
  let buf = StringBuilder::new()
  let func_name = escape_func_name(to_camel_case(decl.name.name))

  // Generate type parameters
  let type_params = generate_mbt_func_type_params(decl.quantifiers)

  // Handle method vs standalone function
  match decl.type_name {
    Some(type_name) => {
      // Method: Type::method -> Type$method
      let ts_type_name = long_ident_to_string(type_name.name)
      buf.write_string("export function ")
      buf.write_string(ts_type_name)
      buf.write_string("$")
      buf.write_string(to_camel_case(decl.name.name))
      buf.write_string(type_params)
      buf.write_string("(")
      // Generate parameters
      match decl.decl_params {
        Some(params) => buf.write_string(generate_mbt_func_params(params))
        None => ()
      }
      buf.write_string("): ")
      // Generate return type
      match decl.return_type {
        Some(ty) => buf.write_string(generate_type(ty))
        None => buf.write_string("void")
      }
      buf.write_string(";")
      // Replace Self with actual type name
      replace_self(buf.to_string(), ts_type_name)
    }
    None => {
      // Standalone function
      buf.write_string("export function ")
      buf.write_string(func_name)
      buf.write_string(type_params)
      buf.write_string("(")
      // Generate parameters
      match decl.decl_params {
        Some(params) => buf.write_string(generate_mbt_func_params(params))
        None => ()
      }
      buf.write_string("): ")
      // Generate return type
      match decl.return_type {
        Some(ty) => buf.write_string(generate_type(ty))
        None => buf.write_string("void")
      }
      buf.write_string(";")
      buf.to_string()
    }
  }
}

///|
/// Generate type params from TypeVarBinder list
fn generate_mbt_func_type_params(
  binders : @list.List[@syntax.TypeVarBinder],
) -> String {
  let arr = binders.to_array()
  if arr.length() == 0 {
    return ""
  }
  let buf = StringBuilder::new()
  buf.write_string("<")
  for i, binder in arr {
    if i > 0 {
      buf.write_string(", ")
    }
    buf.write_string(binder.name)
  }
  buf.write_string(">")
  buf.to_string()
}

///|
/// Generate params from Parameter list
fn generate_mbt_func_params(params : @syntax.Parameters) -> String {
  let buf = StringBuilder::new()
  let arr = params.to_array()
  let mut pos_index = 0
  for i, param in arr {
    if i > 0 {
      buf.write_string(", ")
    }
    match param {
      DiscardPositional(ty~, loc~) => {
        ignore(loc)
        buf.write_string("arg")
        buf.write_string(pos_index.to_string())
        buf.write_string(": ")
        match ty {
          Some(t) => buf.write_string(generate_type(t))
          None => buf.write_string("any")
        }
        pos_index += 1
      }
      Positional(binder~, ty~) => {
        buf.write_string(escape_func_name(binder.name))
        buf.write_string(": ")
        match ty {
          Some(t) => buf.write_string(generate_type(t))
          None => buf.write_string("any")
        }
        pos_index += 1
      }
      Labelled(binder~, ty~) => {
        buf.write_string(escape_func_name(binder.name))
        buf.write_string(": ")
        match ty {
          Some(t) => buf.write_string(generate_type(t))
          None => buf.write_string("any")
        }
      }
      Optional(binder~, default~, ty~) => {
        ignore(default)
        buf.write_string(escape_func_name(binder.name))
        buf.write_string("?: ")
        match ty {
          Some(t) => buf.write_string(generate_type(t))
          None => buf.write_string("any")
        }
      }
      QuestionOptional(binder~, ty~) => {
        buf.write_string(escape_func_name(binder.name))
        buf.write_string("?: ")
        match ty {
          Some(t) => buf.write_string(generate_type(t))
          None => buf.write_string("any")
        }
      }
    }
  }
  buf.to_string()
}

///|
/// Generate TypeScript for TraitDecl
fn generate_mbt_trait_decl(decl : @syntax.TraitDecl) -> String {
  // Only export pub traits
  if not(is_pub_visibility(decl.vis)) {
    return ""
  }
  let trait_name = decl.name.name
  let buf = StringBuilder::new()
  buf.write_string("// trait ")
  buf.write_string(trait_name)
  buf.write_string("\n")
  for m in decl.methods {
    buf.write_string("export function ")
    buf.write_string(trait_name)
    buf.write_string("$")
    buf.write_string(to_camel_case(m.name.name))
    buf.write_string("(")
    buf.write_string(generate_mbt_func_params(m.params))
    buf.write_string("): ")
    match m.return_type {
      Some(ty) => buf.write_string(generate_type(ty))
      None => buf.write_string("void")
    }
    buf.write_string(";\n")
  }
  buf.to_string().trim_space().to_string()
}