///|
async fn emit_files(
  files : Array[@resolver.GeneratedFile],
  output_dir : @path.Path,
) -> Unit {
  // Ensure output directory exists
  let dir_exists = @fs.exists(output_dir.0)
  if !dir_exists {
    @fs.mkdir(output_dir.0, permission=0o755, recursive=true)
  }
  // Write output files
  let c_files : Array[String] = []
  let mbt_files : Array[String] = []
  for file in files {
    let path = output_dir.join(@path.Path(file.filename))
    @fs.write_file(
      path.0,
      file.content,
      create_mode=CreateOrTruncate,
      permission=0o644,
    )
    @stdio.stdout.write("  " + path.0 + "\n")
    let ext = path.extname()
    if ext == ".c" || ext == ".h" {
      c_files.push(path.0)
    } else if ext == ".mbt" {
      mbt_files.push(path.0)
    }
  }
  // Format generated files
  if !c_files.is_empty() {
    let args : Array[String] = ["-i"]
    args.append(c_files[:])
    let exit_code = @process.run("clang-format", args[:])
    if exit_code != 0 {
      @stdio.stderr.write("Warning: clang-format failed\n")
    }
  }
  for path in mbt_files {
    let (exit_code, output) = @process.collect_stdout("moonfmt", [path][:])
    if exit_code == 0 {
      @fs.write_file(
        path,
        output,
        create_mode=CreateOrTruncate,
        permission=0o644,
      )
    } else {
      @stdio.stderr.write("Warning: moonfmt failed for " + path + "\n")
    }
  }
}

///|
async fn main {
  let cmd = @argparse.Command(
    "gir",
    about="Generate MoonBit FFI bindings from GIR XML",
    positionals=[
      @argparse.PositionArg("gir-name", about="GIR name, e.g. Gtk-4.0"),
    ],
    options=[
      @argparse.OptionArg(
        "output",
        short='o',
        long="output",
        about="Output directory",
        default_values=["src/gtk"],
      ),
      @argparse.OptionArg("pkg", short='p', long="pkg", about="Package name", default_values=[
        "gtk",
      ]),
      @argparse.OptionArg(
        "classes",
        short='c',
        long="classes",
        about="Comma-separated class whitelist (e.g. Widget,Button)",
      ),
      @argparse.OptionArg(
        "config",
        long="config",
        about="Path to gir.json config file",
      ),
      @argparse.OptionArg(
        "gir-dir",
        long="gir-dir",
        about="Directory containing GIR files",
        default_values=["/usr/share/gir-1.0"],
      ),
    ],
  )
  let matches = cmd.parse() catch {
    e => {
      @stdio.stderr.write(e.to_string())
      return
    }
  }
  let output_dir = match matches.values.get("output") {
    Some(vals) => @path.Path(vals[0])
    None => @path.Path("src/gtk")
  }
  let gir_dir = match matches.values.get("gir-dir") {
    Some(vals) => vals[0]
    None => "/usr/share/gir-1.0"
  }
  // Config mode
  match matches.values.get("config") {
    Some(config_vals) => {
      let config_path = config_vals[0]
      let config_data = @fs.read_file(config_path)
      let config_text = config_data.text() catch {
        e => {
          @stdio.stderr.write(
            "Failed to read config file: " + e.to_string() + "\n",
          )
          return
        }
      }
      let config : @config.GirConfig = @json.from_json(@json.parse(config_text)) catch {
        e => {
          @stdio.stderr.write("Failed to parse config: " + e.to_string() + "\n")
          return
        }
      }
      // Read primary GIR
      let gir_path = @path.Path(gir_dir).join(@path.Path(config.gir + ".gir"))
      let gir_exists = @fs.exists(gir_path.0)
      if !gir_exists {
        @stdio.stderr.write("GIR file not found: " + gir_path.0 + "\n")
        return
      }
      let data = @fs.read_file(gir_path.0)
      let content = data.text() catch {
        e => {
          @stdio.stderr.write(
            "Failed to read GIR file: " + e.to_string() + "\n",
          )
          return
        }
      }
      let pkg_name = match matches.values.get("pkg") {
        Some(vals) => vals[0]
        None => "gtk"
      }
      let ns = @parser.parse_gir(content) catch {
        e => {
          @stdio.stderr.write("Generation failed: " + e.to_string() + "\n")
          return
        }
      }
      let resolver = @resolver.BindingResolver::new(
        pkg_name,
        config.header,
        version_macro=config.version_macro,
        extra_c_includes=config.includes,
      )
      // Cairo is a built-in introspection namespace and commonly has no
      // standalone cairo-1.0.gir. Register the value type needed by
      // PangoCairo so signatures can still resolve to the Cairo package.
      resolver.register_enum(
        @model.GirEnum::new(
          name="cairo.FontType",
          c_type="cairo_font_type_t",
          import_path="@cairo",
        ),
        "cairo.FontType",
      )
      // Load dependencies first
      for dep_name, dep_config in config.deps {
        let dep_gir_path = @path.Path(gir_dir).join(
          @path.Path(dep_name + ".gir"),
        )
        let dep_exists = @fs.exists(dep_gir_path.0)
        if !dep_exists {
          @stdio.stderr.write(
            "Dependency GIR not found: " + dep_gir_path.0 + "\n",
          )
          continue
        }
        let dep_data = @fs.read_file(dep_gir_path.0)
        let dep_content = dep_data.text() catch {
          e => {
            @stdio.stderr.write(
              "Failed to read dep GIR: " + e.to_string() + "\n",
            )
            continue
          }
        }
        let dep_ns = @parser.parse_gir(dep_content) catch {
          e => {
            @stdio.stderr.write(
              "Failed to parse dep GIR: " + e.to_string() + "\n",
            )
            continue
          }
        }
        resolver.register_dependency(dep_ns, dep_config, dep_ns.name())
        resolver.register_callbacks(dep_ns.callbacks())
      }
      // Register primary namespace
      resolver.register_namespace(ns, config)
      resolver.register_callbacks(ns.callbacks())
      // Register manual types not in GIR as classes
      resolver.register_manual_type(
        "GLib.Variant",
        c_type="GVariant",
        codegen_stem="variant",
        import_path="@glib",
      )
      // Resolve declarations directly into package destinations
      for name in config.enums {
        match ns.get_enum(name) {
          Some(e) => {
            let qualified = ns.name() + "." + name
            resolver.resolve_enum(e, qualified)
          }
          None => ()
        }
      }
      resolver.resolve_constants(ns.constants(), overrides=config.constants)
      resolver.resolve_alias_types(config, ns.name())
      resolver.resolve_record_types(config, ns.name())
      for name in config.records {
        let qualified = ns.name() + "." + name
        if ns.parse_record(name) is Some(rec) {
          resolver.resolve_record(rec, qualified)
        }
      }
      for name in config.interfaces {
        match ns.parse_interface(name) {
          Some(iface) => {
            let qualified = ns.name() + "." + name
            resolver.resolve_interface(iface, qualified)
          }
          None => ()
        }
      }
      for name in config.classes {
        match ns.parse_class(name) {
          Some(cls) => {
            let qualified = ns.name() + "." + name
            resolver.resolve_class(cls, qualified)
          }
          None => ()
        }
      }
      for name in config.functions {
        match ns.get_function(name) {
          Some(function_) => resolver.resolve_function(function_, name)
          None => ()
        }
      }
      let (files, diagnostics) = resolver.finish()
      emit_files(files, output_dir)
      for d in diagnostics {
        @stdio.stderr.write("  " + d.to_string() + "\n")
      }
      @stdio.stdout.write("Done.\n")
    }
    None => {
      // Legacy CLI mode
      let gir_name = match matches.values.get("gir-name") {
        Some(vals) => vals[0]
        None => {
          @stdio.stderr.write(cmd.render_help())
          return
        }
      }
      let pkg_name = match matches.values.get("pkg") {
        Some(vals) => vals[0]
        None => "gtk"
      }
      let class_whitelist : Array[String]? = match
        matches.values.get("classes") {
        Some(vals) => {
          let result : Array[String] = []
          for v in vals {
            for part in v.split(",") {
              let s = part.to_owned()
              if !s.is_empty() {
                result.push(s)
              }
            }
          }
          Some(result)
        }
        None => None
      }
      let gir_path = @path.Path(gir_dir).join(@path.Path(gir_name + ".gir"))
      let gir_exists = @fs.exists(gir_path.0)
      if !gir_exists {
        @stdio.stderr.write("GIR file not found: " + gir_path.0 + "\n")
        return
      }
      // Read GIR file
      let data = @fs.read_file(gir_path.0)
      let content = data.text() catch {
        e => {
          @stdio.stderr.write(
            "Failed to read GIR file: " + e.to_string() + "\n",
          )
          return
        }
      }
      let ns = @parser.parse_gir(content) catch {
        e => {
          @stdio.stderr.write("Generation failed: " + e.to_string() + "\n")
          return
        }
      }
      let header = pkg_name + "/" + pkg_name + ".h"
      let resolver = @resolver.BindingResolver::new(pkg_name, header)
      // Register all enums and parent chain
      for name in ns.enum_names() {
        match ns.get_enum(name) {
          Some(e) => resolver.register_enum(e, e.name)
          None => ()
        }
      }
      for name in ns.class_names() {
        let qualified = ns.name() + "." + name
        match ns.parent_of(name) {
          Some(parent) => resolver.store_parent(qualified, parent)
          None => ()
        }
      }
      // Register and resolve classes
      let class_names : Array[String] = match class_whitelist {
        Some(classes) => classes
        None => ns.class_names().collect()
      }
      for name in class_names {
        match ns.parse_class(name) {
          Some(cls) => {
            let qualified = ns.name() + "." + name
            resolver.register_class(cls, qualified)
          }
          None => ()
        }
      }
      for name in ns.enum_names() {
        match ns.get_enum(name) {
          Some(e) => {
            let qualified = ns.name() + "." + name
            resolver.resolve_enum(e, qualified)
          }
          None => ()
        }
      }
      resolver.resolve_constants(ns.constants())
      for name in class_names {
        match ns.parse_class(name) {
          Some(cls) => {
            let qualified = ns.name() + "." + name
            resolver.resolve_class(cls, qualified)
          }
          None => ()
        }
      }
      let (files, diagnostics) = resolver.finish()
      emit_files(files, output_dir)
      for d in diagnostics {
        @stdio.stderr.write("  " + d.to_string() + "\n")
      }
      @stdio.stdout.write("Done.\n")
    }
  }
}