///|
async fn main {
  let args_spec : Map[String, @cli.Arg] = {
    "scene": @cli.opt_string(short='s', help="TOML scene file to render"),
    "output": @cli.opt_string(short='o', help="Output filename (PPM or PNG)"),
    "width": @cli.opt_int(0, short='w', help="Override output width"),
    "height": @cli.opt_int(0, short='H', help="Override output height"),
    "samples": @cli.opt_int(0, help="Override samples per pixel"),
    "area_steps": @cli.opt_int(
      0,
      help="Cap area light sampling steps per axis (e.g. 4 => at most 4x4 shadow samples)",
    ),
    "area-steps": @cli.opt_int(0, help="Alias for --area_steps"),
    "draft": @cli.flag(
      help="Quick preview preset (lower resolution/samples/bounces unless overridden)",
    ),
    "stats": @cli.flag(
      help="Print render statistics (ray/shadow/intersection counts)",
    ),
    "debug": @cli.flag(help="Print parsed scene summary (materials/patterns)"),
    "divide": @cli.opt_int(
      0,
      help="Subdivide groups with at least this many children",
    ),
  }
  let args = match
    @cli.parse(args_spec, prog="render", description="MoonBit Ray Tracer") {
    Some(a) => a
    None => return
  }
  let scene_path = match args.string_opt("scene") {
    Some(path) => path
    None => {
      @cli.eprintln("error: --scene/-s is required")
      abort("")
    }
  }

  // Load scene
  let parsed_scene = @lib.parse_scene_file(scene_path) catch {
    e => {
      @cli.eprintln("error loading scene: \{e}")
      abort("")
    }
  }
  let world = parsed_scene.world
  let mut camera = parsed_scene.camera
  let settings = parsed_scene.render_settings
  world.background = settings.background

  // Apply overrides
  let draft = args.flag("draft")
  let w_override = args.int("width")
  let h_override = args.int("height")
  let s_override = args.int("samples")
  let area_steps_override = {
    let a = args.int("area_steps")
    if a > 0 {
      a
    } else {
      args.int("area-steps")
    }
  }
  let mut target_w = settings.width
  let mut target_h = settings.height
  if draft {
    // Prefer fast iteration defaults.
    // Only auto-adjust resolution when the user hasn't explicitly overridden it.
    if w_override <= 0 && h_override <= 0 {
      let max_w = 640
      let max_h = 360
      let sw = settings.width.to_double()
      let sh = settings.height.to_double()
      let mut scale = max_w.to_double() / sw
      let scale_h = max_h.to_double() / sh
      if scale_h < scale {
        scale = scale_h
      }
      if scale < 1.0 {
        let nw = (sw * scale).to_int()
        let nh = (sh * scale).to_int()
        target_w = if nw < 1 { 1 } else { nw }
        target_h = if nh < 1 { 1 } else { nh }
      }
    }

    // Only force low samples when the user hasn't explicitly overridden it.
    if s_override <= 0 {
      settings.samples = 1
    }

    // Cap bounces for speed (never increases the scene's requested bounces).
    if settings.max_bounces > 2 {
      settings.max_bounces = 2
    }
  }
  if w_override > 0 {
    target_w = w_override
  }
  if h_override > 0 {
    target_h = h_override
  }
  if target_w != settings.width || target_h != settings.height {
    settings.width = target_w
    settings.height = target_h
    camera = @lib.camera(settings.width, settings.height, camera.field_of_view)
    camera.transform = parsed_scene.camera.transform
  }
  if s_override > 0 {
    settings.samples = s_override
  }
  if area_steps_override > 0 {
    world.lights = world.lights.map(fn(l) {
      l.cap_area_steps(area_steps_override)
    })
  }
  let d = args.int("divide")
  if d > 0 {
    settings.divide = d
    let g = @lib.group()
    for shape in world.shapes {
      g.add_child(shape)
    }
    g.divide(d)
    world.shapes = [g]
  }
  if args.flag("debug") {
    println("--- debug: parsed scene summary ---")
    fn dump_shape(s : @lib.Shape, indent : String) -> Unit {
      println("\{indent}id=\{s.id} kind=\{s.object}")
      println(
        "\{indent}  material: mode=\{s.material.lighting_mode} color=\{s.material.color}",
      )
      println(
        "\{indent}  material: ambient=\{s.material.ambient} diffuse=\{s.material.diffuse} specular=\{s.material.specular} shininess=\{s.material.shininess}",
      )
      println(
        "\{indent}  material: metallic=\{s.material.metallic} metallic_pattern=\{Repr(s.material.metallic_pattern)}",
      )
      println(
        "\{indent}  material: roughness=\{s.material.roughness} roughness_pattern=\{Repr(s.material.roughness_pattern)}",
      )
      println("\{indent}  material: pattern=\{Repr(s.material.pattern)}")
      println(
        "\{indent}  material: normal_pattern=\{Repr(s.material.normal_pattern)} normal_strength=\{s.material.normal_strength}",
      )
      println(
        "\{indent}  material: displacement_pattern=\{Repr(s.material.displacement_pattern)} displacement_strength=\{s.material.displacement_strength}",
      )
      println(
        "\{indent}  material: normal_texture=\{Repr(s.material.normal_texture)} normal_texture_scale=\{s.material.normal_texture_scale} normal_texture_texcoord=\{s.material.normal_texture_texcoord}",
      )
      match s.object {
        Group(g) => {
          println("\{indent}  children: \{g.children.length()}")
          let next = indent + "    "
          for child in g.children {
            dump_shape(child, next)
          }
        }
        _ => ()
      }
    }

    println("name: \"\{parsed_scene.metadata.name}\"")
    println("scene: \"\{scene_path}\"")
    println("resolution: \{settings.width}x\{settings.height}")
    println("samples: \{settings.samples}")
    println("lights: \{world.lights.length()}")
    println("shapes: \{world.shapes.length()}")
    for i in 0.. 1
  println("Rendering \{parsed_scene.metadata.name}...")
  println("Resolution: \{settings.width}x\{settings.height}")
  println("Samples: \{settings.samples}")
  let stats_enabled = args.flag("stats")
  if stats_enabled {
    world.stats_enabled = true
    world.stats = @lib.render_stats()
    @lib.stats_enable_and_reset()
    world.stats_est_primitives_per_intersect_world = world.estimated_primitive_count()
  }
  async fn write_progress(msg : String) -> Unit {
    // Best-effort terminal output; ignore failures.
    @stdio.stdout.write(msg) catch {
      _ => ()
    }
  }

  let canvas = camera.render(
    world,
    samples=settings.samples,
    max_bounces=settings.max_bounces,
    progress=async fn(y) {
      if y % 10 == 0 || y == settings.height {
        let pct = y.to_double() * 100.0 / settings.height.to_double()
        let msg = "\rProgress: \{pct.to_int()}% (\{y}/\{settings.height})"
        write_progress(msg)
        if y == settings.height {
          write_progress("\n")
        }
      }
    },
  )
  canvas.use_tone_mapping = true

  // Determine output path
  let output_path = match args.string_opt("output") {
    Some(path) => path
    None => {
      // Default to scene name + .png
      let base = if scene_path.has_suffix(".toml") {
        scene_path[0:scene_path.length() - 5].to_owned()
      } else {
        scene_path
      }
      base + ".png"
    }
  }

  // Save output
  println("Saving to \{output_path}...")
  try {
    if output_path.has_suffix(".ppm") {
      @fs.write_file(output_path, canvas.to_ppm(), permission=0o644)
    } else {
      @fs.write_file(output_path, canvas.to_png(), permission=0o644)
    }
  } catch {
    e => {
      @cli.eprintln("error saving output: \{e}")
      abort("")
    }
  }
  if stats_enabled {
    println(
      world.stats.summary(settings.width, settings.height, settings.samples),
    )
  }
  println("Done.")
}