///|
/// Entry point for local framework probes.
/// Run with `moon run cmd/main` from the project root.
fn main {
  let command = command_from_args(@env.args())
  if command == "help" {
    print_help()
  } else if command == "unknown" {
    println("unsupported command=\{unknown_command_from_args(@env.args())}")
    print_help()
    panic()
  } else if command == "init" {
    let name = scaffold_name_from_args(@env.args())
    let output_dir = scaffold_output_dir_from_args(@env.args(), name)
    let scaffold = scaffold_from_args(@env.args(), name)
    write_scaffold(output_dir, scaffold)
    println("command=init")
    println(scaffold.summary)
    println("wrote=\{scaffold_write_file_count(scaffold)} output=\{output_dir}")
  } else if command == "ci-plan" {
    let plan = ci_plan_from_args(@env.args())
    if ci_script_from_args(@env.args()) {
      println(@bunnia.ci_plan_shell_script(plan))
    } else {
      print_ci_plan(plan)
    }
  } else if command == "limits" {
    print_platform_limits(platform_limits_from_args(@env.args()))
  } else {
    run_build_or_watch(command, @env.args())
  }
}

///|
fn default_command() -> String {
  "build"
}

///|
fn default_target() -> String {
  "wechat"
}

///|
fn default_example() -> String {
  "agent_map"
}

///|
fn default_output_dir_for_build(target : String, example : String) -> String {
  "_build/bunnia/\{target}/\{example}"
}

///|
fn command_from_args(args : Array[String]) -> String {
  if args.contains("help") || args.contains("--help") || args.contains("-h") {
    return "help"
  }
  for i in 0.. String {
  for i in 0.. Bool {
  item == "build" ||
  item == "init" ||
  item == "watch" ||
  item == "snapshot" ||
  item == "inspect" ||
  item == "limits" ||
  item == "ci-plan"
}

///|
fn command_arg_is_ignored(args : Array[String], index : Int) -> Bool {
  let item = args[index]
  command_arg_is_runner(item) ||
  item.has_prefix("-") ||
  (index > 0 && command_option_consumes_value(args[index - 1]))
}

///|
fn command_arg_is_runner(item : String) -> Bool {
  item == "node" || item.has_suffix("/node") || item.has_suffix(".js")
}

///|
fn command_option_consumes_value(item : String) -> Bool {
  item == "--target" ||
  item == "--example" ||
  item == "--budget" ||
  item == "--render-budget" ||
  item == "--out" ||
  item == "--snapshot-out" ||
  item == "--snapshot-dir" ||
  item == "--name" ||
  item == "--module"
}

///|
fn command_writes_project_output(command : String) -> Bool {
  command == "build" || command == "watch"
}

///|
fn print_help() -> Unit {
  println("Bunnia CLI")
  println("usage=moon run cmd/main --  [options]")
  println("command=build target=wechat writes=_build/bunnia/wechat/")
  println(
    "command=watch target=wechat writes=_build/bunnia/wechat/ options=--once",
  )
  println("command=inspect writes=false")
  println("command=snapshot writes=snapshot-file")
  println("command=ci-plan writes=false options=--script")
  println("command=limits writes=false options=--target wechat|alipay|tiktok")
  println("command=init writes=scaffold options=--name --module --out")
  println("options=--target --example --budget --render-budget --strict --out")
}

///|
fn target_from_args(args : Array[String]) -> String {
  for i in 0.. String {
  for i in 0.. Bool {
  args.contains("--strict") || args.contains("--fail-on-diagnostics")
}

///|
fn budget_profile_from_args(args : Array[String]) -> String {
  for i in 0.. String {
  for i in 0.. @bunnia.RenderBudget {
  let profile = render_budget_profile_from_args(args)
  if !@bunnia.is_named_budget_profile(profile) {
    println("unsupported render_budget=\{profile}")
    panic()
  }
  @bunnia.render_budget_for_profile(profile)
}

///|
fn wechat_build_budget_from_args(
  args : Array[String],
) -> @bunnia.WechatBuildBudget {
  let profile = budget_profile_from_args(args)
  if !@bunnia.is_named_budget_profile(profile) {
    println("unsupported budget=\{profile}")
    panic()
  }
  @bunnia.wechat_build_budget_for_profile(profile)
}

///|
fn watch_once_from_args(args : Array[String]) -> Bool {
  args.contains("--once")
}

///|
fn default_scaffold_name() -> String {
  "bunnia_starter"
}

///|
fn scaffold_name_from_args(args : Array[String]) -> String {
  for i in 0.. String {
  for i in 0.. String {
  for i in 0.. @bunnia.ScaffoldPlan {
  @bunnia.scaffold_project(
    name~,
    module_name=scaffold_module_from_args(args, name),
  )
}

///|
fn output_dir_from_args(
  args : Array[String],
  target : String,
  example : String,
) -> String {
  for i in 0.. String {
  for i in 0.. String {
  for i in 0.. Array[String] {
  for i in 0.. String {
  for i in 0.. String {
  for i in 0.. Bool {
  args.contains("--script")
}

///|
fn ci_plan_from_args(args : Array[String]) -> @bunnia.CiPlan {
  let target = target_from_args(args)
  let examples = ci_examples_from_args(args)
  @bunnia.ci_plan(
    target~,
    budget_profile=ci_budget_profile_from_args(args),
    render_budget_profile=ci_render_budget_profile_from_args(args),
    examples~,
    snapshot_dir=ci_snapshot_dir_from_args(args),
    post_build_steps=ci_post_build_steps(target, examples),
  )
}

///|
fn ci_post_build_steps(
  target : String,
  examples : Array[String],
) -> Array[@bunnia.CiPlanStep] {
  let steps : Array[@bunnia.CiPlanStep] = []
  if target == "wechat" &&
    examples.length() == 1 &&
    examples[0] == "moontown_miniapp" {
    steps.push(
      @bunnia.ci_plan_step(
        "ordinary-copy-moontown_miniapp", "sh scripts/validate_moontown_ordinary_copy.sh _build/bunnia/wechat/moontown_miniapp",
        "reject technical copy leaks in ordinary Moontown surfaces",
      ),
    )
  }
  steps
}

///|
fn platform_limits_from_args(
  args : Array[String],
) -> @bunnia.PlatformLimitsReport {
  @bunnia.platform_limits(target=target_from_args(args))
}

///|
fn target_support_from_args(
  args : Array[String],
) -> @bunnia.PlatformTargetSupport {
  @bunnia.platform_target_support(target=target_from_args(args))
}

///|
fn ensure_target_generator_available(
  support : @bunnia.PlatformTargetSupport,
) -> Unit {
  if !support.generator_available {
    println(support.summary)
    print_diagnostics(support.diagnostics)
    panic()
  }
}

///|
fn project_for_build(
  target : String,
  example : String,
  render_budget : @bunnia.RenderBudget,
) -> @bunnia.WechatProject {
  ensure_target_generator_available(@bunnia.platform_target_support(target~))
  project_for_example(example, render_budget)
}

///|
fn project_for_example(
  example : String,
  render_budget : @bunnia.RenderBudget,
) -> @bunnia.WechatProject {
  if example == "wenyu_overview" {
    @wenyu.demo_wechat_project_with_render_budget(render_budget)
  } else if example == "moontown_miniapp" {
    @moontown.demo_wechat_project_with_render_budget(render_budget)
  } else if example == "agent_map" {
    @demo.demo_wechat_project_with_render_budget(render_budget)
  } else {
    println("unsupported example=\{example}")
    panic()
  }
}

///|
fn patches_for_build(target : String, example : String) -> @bunnia.PatchPlan {
  ensure_target_generator_available(@bunnia.platform_target_support(target~))
  patches_for_example(example)
}

///|
fn patches_for_example(example : String) -> @bunnia.PatchPlan {
  if example == "wenyu_overview" {
    @wenyu.demo_patch_plan()
  } else if example == "moontown_miniapp" {
    @moontown.demo_patch_plan()
  } else if example == "agent_map" {
    @demo.demo_patch_plan()
  } else {
    println("unsupported example=\{example}")
    panic()
  }
}

///|
fn profile_for_build(
  target : String,
  example : String,
  project : @bunnia.WechatProject,
  patches : @bunnia.PatchPlan,
  report : @bunnia.WechatBuildReport,
) -> @bunnia.BuildProfile {
  ensure_target_generator_available(@bunnia.platform_target_support(target~))
  profile_for_example(
    example,
    @bunnia.wechat_project_with_report(project, report),
    patches,
  )
}

///|
fn profile_for_example(
  example : String,
  project : @bunnia.WechatProject,
  patches : @bunnia.PatchPlan,
) -> @bunnia.BuildProfile {
  if example == "wenyu_overview" {
    @bunnia.profile_wechat_project(
      project,
      patches,
      effect_plans=[@wenyu.demo_effect_plan()],
      communication_plans=[@wenyu.demo_communication_plan()],
      backend_plans=[
        @bunnia.plan_backend_contract(@wenyu.demo_backend_contract()),
      ],
      snapshot_plans=[@wenyu.demo_snapshot_delta_plan()],
      asset_plans=[@wenyu.demo_asset_plan()],
      thread_plans=[@wenyu.demo_scene_thread_plan()],
      scene_plans=[@wenyu.demo_scene_render_plan()],
      surface_plans=[@wenyu.demo_scene_surface_plan()],
      quality_plans=[@wenyu.demo_scene_visual_quality_plan()],
    )
  } else if example == "moontown_miniapp" {
    @bunnia.profile_wechat_project(
      project,
      patches,
      effect_plans=[@moontown.demo_effect_plan()],
      communication_plans=[@moontown.demo_communication_plan()],
      tool_result_plans=[@moontown.demo_tool_result_plan()],
      agent_delta_plans=[@moontown.demo_agent_delta_plan()],
      backend_plans=[@moontown.demo_backend_plan()],
      snapshot_plans=[@moontown.demo_snapshot_delta_plan()],
      asset_plans=[@moontown.demo_asset_plan()],
      thread_plans=[@moontown.demo_scene_thread_plan()],
      camera_plans=[
        @bunnia.plan_scene_camera(
          @moontown.demo_scene(),
          @moontown.demo_camera(),
        ),
      ],
      scene_plans=[@moontown.demo_scene_render_plan()],
      surface_plans=[@moontown.demo_scene_surface_plan()],
      quality_plans=[@moontown.demo_scene_visual_quality_plan()],
    )
  } else if example == "agent_map" {
    @bunnia.profile_wechat_project(
      project,
      patches,
      effect_plans=[@demo.demo_effect_plan()],
      communication_plans=[@demo.demo_communication_plan()],
      tool_result_plans=[@demo.demo_tool_result_plan()],
      agent_delta_plans=[@demo.demo_agent_delta_plan()],
      backend_plans=[
        @bunnia.plan_backend_contract(@demo.demo_backend_contract()),
      ],
      snapshot_plans=[@demo.demo_snapshot_delta_plan()],
      asset_plans=[@demo.demo_asset_plan()],
      thread_plans=[@demo.demo_scene_thread_plan()],
      camera_plans=[@demo.demo_scene_camera_plan()],
      scene_plans=[@demo.demo_scene_render_plan()],
      surface_plans=[@demo.demo_scene_surface_plan()],
      quality_plans=[@demo.demo_scene_visual_quality_plan()],
    )
  } else {
    println("unsupported example=\{example}")
    panic()
  }
}

///|
fn run_build_or_watch(command : String, args : Array[String]) -> Unit {
  let target = target_from_args(args)
  let target_support = target_support_from_args(args)
  ensure_target_generator_available(target_support)
  if target != "wechat" {
    run_generic_miniapp_build(command, args, target_support)
    return
  }
  let example = example_from_args(args)
  let output_dir = output_dir_from_args(args, target, example)
  let render_budget = render_budget_from_args(args)
  let render_budget_profile = render_budget_profile_from_args(args)
  let project = project_for_build(target, example, render_budget)
  let patches = patches_for_build(target, example)
  let budget_profile = budget_profile_from_args(args)
  let report = @bunnia.report_wechat_project_with_budget(
    project,
    @bunnia.default_wechat_runtime(),
    wechat_build_budget_from_args(args),
  )
  let profile = profile_for_build(target, example, project, patches, report)
  let inspection = @bunnia.inspect_wechat_project(
    target~,
    example~,
    project,
    report,
  )
  let inspection_gate = @bunnia.gate_project_inspection(
    inspection,
    @bunnia.inspection_budget_for_profile(budget_profile),
    budget_profile~,
  )
  let profile_gate = @bunnia.gate_build_profile(
    profile,
    @bunnia.build_profile_budget_for_profile(budget_profile),
    budget_profile~,
  )
  let release = @bunnia.release_readiness_report(
    target~,
    example~,
    budget_profile~,
    project,
    profile,
    profile_gate,
    inspection,
    inspection_gate,
  )
  let strict = strict_from_args(args)
  if command == "inspect" {
    let snapshot = snapshot_for_build(
      target~,
      example~,
      project,
      report,
      profile,
      budget_profile~,
      render_budget_profile~,
    )
    print_inspect_result(
      target, example, budget_profile, render_budget_profile, project, report, patches,
      profile, profile_gate, snapshot, inspection, inspection_gate, release,
    )
  } else if command == "snapshot" {
    let snapshot = snapshot_for_build(
      target~,
      example~,
      project,
      report,
      profile,
      budget_profile~,
      render_budget_profile~,
    )
    print_snapshot_result(
      target, example, budget_profile, render_budget_profile, project, report, patches,
      profile, profile_gate, inspection_gate, release,
    )
    let snapshot_path = snapshot_output_path_from_args(args, target, example)
    write_text_file(snapshot_path, snapshot.content)
    println(snapshot.summary)
    println("snapshot=\{snapshot_path}")
  } else if command_writes_project_output(command) {
    write_project(output_dir, project)
    print_build_result(
      command, target, example, budget_profile, render_budget_profile, output_dir,
      project, report, patches, profile, profile_gate, inspection_gate, release,
    )
  } else {
    println("unsupported command=\{command}")
    panic()
  }
  if should_fail_build(
      strict, project, report, patches, profile, profile_gate, inspection_gate, release,
    ) {
    println("build_failed=diagnostics")
    panic()
  }
  if command == "watch" {
    if watch_once_from_args(args) {
      println("watch=once")
    } else {
      watch_generated_output(target, example, output_dir, strict)
    }
  }
}

///|
fn run_generic_miniapp_build(
  command : String,
  args : Array[String],
  target_support : @bunnia.PlatformTargetSupport,
) -> Unit {
  let target = target_from_args(args)
  let example = example_from_args(args)
  let output_dir = output_dir_from_args(args, target, example)
  let render_budget = render_budget_from_args(args)
  let render_budget_profile = render_budget_profile_from_args(args)
  let budget_profile = budget_profile_from_args(args)
  let strict = strict_from_args(args)
  if command != "build" &&
    command != "inspect" &&
    command != "snapshot" &&
    command != "watch" {
    println(
      "unsupported command=\{command} target=\{target} reason=generic-miniapp-build-inspect-snapshot-watch-only",
    )
    panic()
  }
  let project = generic_miniapp_project_for_build(
    target_support, example, render_budget,
  )
  let inspection = @bunnia.inspect_miniapp_project(target~, example~, project)
  let inspection_gate = @bunnia.gate_miniapp_inspection(
    inspection,
    @bunnia.miniapp_inspection_budget_for_profile(budget_profile),
    budget_profile~,
  )
  let release = @bunnia.miniapp_release_readiness_report(
    target~,
    example~,
    budget_profile~,
    project,
    inspection,
    inspection_gate,
  )
  if command == "inspect" {
    print_generic_miniapp_inspect_result(
      target, example, budget_profile, render_budget_profile, project, inspection,
      inspection_gate, release,
    )
  } else if command == "snapshot" {
    let snapshot = @bunnia.snapshot_miniapp_project(
      target~,
      example~,
      project,
      inspection,
      inspection_gate,
      release,
      budget_profile~,
      render_budget_profile~,
    )
    print_generic_miniapp_snapshot_result(
      target, example, budget_profile, render_budget_profile, project, inspection,
      inspection_gate, release, snapshot,
    )
    let snapshot_path = snapshot_output_path_from_args(args, target, example)
    write_text_file(snapshot_path, snapshot.content)
    println(snapshot.summary)
    println("snapshot=\{snapshot_path}")
  } else {
    write_miniapp_project(output_dir, project)
    print_generic_miniapp_build_result(
      command, target, example, budget_profile, render_budget_profile, output_dir,
      project, inspection, inspection_gate, release,
    )
  }
  if strict &&
    (
      project.page.plan.diagnostics.length() > 0 ||
      project.manifest.diagnostic_count > 0 ||
      inspection_gate.diagnostics.length() > 0 ||
      release.diagnostics.length() > 0
    ) {
    println("build_failed=diagnostics")
    panic()
  }
  if command == "watch" {
    if watch_once_from_args(args) {
      println("watch=once")
    } else {
      watch_generated_output(target, example, output_dir, strict)
    }
  }
}

///|
fn generic_miniapp_project_for_build(
  target_support : @bunnia.PlatformTargetSupport,
  example : String,
  render_budget : @bunnia.RenderBudget,
) -> @bunnia.MiniappProject {
  if example == "wenyu_overview" {
    @bunnia.generate_miniapp_project_with_budget(
      name="Wenyu Overview",
      route="pages/wenyu/index",
      root=@wenyu.demo_view(),
      platform=target_support.adapter.platform,
      render_budget~,
    )
  } else if example == "moontown_miniapp" {
    @bunnia.generate_miniapp_project_with_budget(
      name="Moontown Miniapp",
      route="pages/moontown/index",
      root=@moontown.demo_view(),
      platform=target_support.adapter.platform,
      render_budget~,
    )
  } else if example == "agent_map" {
    @bunnia.generate_miniapp_project_with_budget(
      name="Agent Map",
      root=@demo.demo_view(),
      platform=target_support.adapter.platform,
      render_budget~,
    )
  } else {
    println("unsupported example=\{example}")
    panic()
  }
}

///|
fn snapshot_for_build(
  target~ : String,
  example~ : String,
  project : @bunnia.WechatProject,
  report : @bunnia.WechatBuildReport,
  profile : @bunnia.BuildProfile,
  budget_profile~ : String,
  render_budget_profile~ : String,
) -> @bunnia.GeneratedSnapshot {
  @bunnia.snapshot_wechat_project_with_report(
    target~,
    example~,
    project,
    report,
    profile,
    budget_profile~,
    render_budget_profile~,
  )
}

///|
fn print_snapshot_result(
  target : String,
  example : String,
  budget_profile : String,
  render_budget_profile : String,
  project : @bunnia.WechatProject,
  report : @bunnia.WechatBuildReport,
  patches : @bunnia.PatchPlan,
  profile : @bunnia.BuildProfile,
  profile_gate : @bunnia.BuildProfileGate,
  inspection_gate : @bunnia.ProjectInspectionGate,
  release : @bunnia.ReleaseReadinessReport,
) -> Unit {
  println("command=snapshot")
  println("target=\{target}")
  println("example=\{example}")
  println("budget=\{budget_profile}")
  println("render_budget=\{render_budget_profile}")
  println(project.page.plan.summary)
  println(project.summary)
  println(project.manifest.summary)
  println(report.summary)
  print_diagnostics(report.diagnostics)
  println(patches.summary)
  println(profile.summary)
  print_profile_diagnostics(profile.diagnostics)
  println(profile_gate.summary)
  print_profile_gate_diagnostics(profile_gate.diagnostics)
  println(inspection_gate.summary)
  print_inspection_gate_diagnostics(inspection_gate.diagnostics)
  println(release.summary)
  print_release_diagnostics(release.diagnostics)
}

///|
fn print_build_result(
  command : String,
  target : String,
  example : String,
  budget_profile : String,
  render_budget_profile : String,
  output_dir : String,
  project : @bunnia.WechatProject,
  report : @bunnia.WechatBuildReport,
  patches : @bunnia.PatchPlan,
  profile : @bunnia.BuildProfile,
  profile_gate : @bunnia.BuildProfileGate,
  inspection_gate : @bunnia.ProjectInspectionGate,
  release : @bunnia.ReleaseReadinessReport,
) -> Unit {
  println("command=\{command}")
  println("target=\{target}")
  println("example=\{example}")
  println("budget=\{budget_profile}")
  println("render_budget=\{render_budget_profile}")
  println(project.page.plan.summary)
  println(project.summary)
  println(project.manifest.summary)
  println(report.summary)
  print_diagnostics(report.diagnostics)
  println(patches.summary)
  println(profile.summary)
  print_profile_diagnostics(profile.diagnostics)
  println(profile_gate.summary)
  print_profile_gate_diagnostics(profile_gate.diagnostics)
  println(inspection_gate.summary)
  print_inspection_gate_diagnostics(inspection_gate.diagnostics)
  println(release.summary)
  print_release_diagnostics(release.diagnostics)
  println("wrote=\{project.files.length()} output=\{output_dir}")
}

///|
fn print_generic_miniapp_snapshot_result(
  target : String,
  example : String,
  budget_profile : String,
  render_budget_profile : String,
  project : @bunnia.MiniappProject,
  inspection : @bunnia.MiniappInspection,
  inspection_gate : @bunnia.MiniappInspectionGate,
  release : @bunnia.MiniappReleaseReadinessReport,
  snapshot : @bunnia.GeneratedSnapshot,
) -> Unit {
  println("command=snapshot")
  println("target=\{target}")
  println("example=\{example}")
  println("budget=\{budget_profile}")
  println("render_budget=\{render_budget_profile}")
  println(project.page.plan.summary)
  println(project.summary)
  println(project.manifest.summary)
  println(inspection.summary)
  println(inspection_gate.summary)
  print_miniapp_inspection_gate_diagnostics(inspection_gate.diagnostics)
  println(release.summary)
  print_release_diagnostics(release.diagnostics)
  print_diagnostics(project.page.plan.diagnostics)
  println(
    "snapshot_preview=target=\{target} example=\{example} pages=\{snapshot.page_count} files=\{snapshot.file_count} bytes=\{snapshot.total_bytes} risk_route=\{snapshot.top_route} risk_score=\{snapshot.route_risk_score} route_diagnostic_routes=\{snapshot.route_diagnostic_route_count} route_diagnostics=\{snapshot.route_diagnostic_count} gate_diagnostics=\{snapshot.inspection_gate_diagnostic_count} release_status=\{release.status} diagnostics=\{snapshot.diagnostic_count}",
  )
}

///|
fn print_generic_miniapp_build_result(
  command : String,
  target : String,
  example : String,
  budget_profile : String,
  render_budget_profile : String,
  output_dir : String,
  project : @bunnia.MiniappProject,
  inspection : @bunnia.MiniappInspection,
  inspection_gate : @bunnia.MiniappInspectionGate,
  release : @bunnia.MiniappReleaseReadinessReport,
) -> Unit {
  println("command=\{command}")
  println("target=\{target}")
  println("example=\{example}")
  println("budget=\{budget_profile}")
  println("render_budget=\{render_budget_profile}")
  println(project.page.plan.summary)
  println(project.summary)
  println(project.manifest.summary)
  println(inspection.summary)
  println(inspection_gate.summary)
  print_miniapp_inspection_gate_diagnostics(inspection_gate.diagnostics)
  println(release.summary)
  print_release_diagnostics(release.diagnostics)
  print_diagnostics(project.page.plan.diagnostics)
  println(
    "Bunnia generic miniapp build: platform=\{@bunnia.platform_id(project.platform)} files=\{project.files.length()} bytes=\{project.total_bytes} markup=\{project.page.markup_extension} style=\{project.page.style_extension} route_diagnostic_routes=\{project.manifest.diagnostic_route_count} diagnostics=\{project.manifest.diagnostic_count} gate_diagnostics=\{inspection_gate.diagnostics.length()} release_status=\{release.status}",
  )
  println("wrote=\{project.files.length()} output=\{output_dir}")
}

///|
fn print_generic_miniapp_inspect_result(
  target : String,
  example : String,
  budget_profile : String,
  render_budget_profile : String,
  project : @bunnia.MiniappProject,
  inspection : @bunnia.MiniappInspection,
  inspection_gate : @bunnia.MiniappInspectionGate,
  release : @bunnia.MiniappReleaseReadinessReport,
) -> Unit {
  println("command=inspect")
  println("target=\{target}")
  println("example=\{example}")
  println("budget=\{budget_profile}")
  println("render_budget=\{render_budget_profile}")
  println(project.page.plan.summary)
  println(project.summary)
  println(project.manifest.summary)
  println(inspection.summary)
  println(inspection_gate.summary)
  print_miniapp_inspection_gate_diagnostics(inspection_gate.diagnostics)
  println(release.summary)
  print_release_diagnostics(release.diagnostics)
  print_diagnostics(project.page.plan.diagnostics)
  println(
    "inspect=target=\{target} example=\{example} pages=\{inspection.page_count} files=\{inspection.file_count} bytes=\{inspection.total_bytes} max_nodes=\{inspection.max_node_count} max_depth=\{inspection.max_depth} max_events=\{inspection.max_event_count} max_scene_markers=\{inspection.max_scene_marker_count} max_scene_regions=\{inspection.max_scene_region_count} max_scene_assets=\{inspection.max_scene_asset_count} route_diagnostic_routes=\{inspection.diagnostic_route_count} route_diagnostics=\{inspection.diagnostic_count} gate_diagnostics=\{inspection_gate.diagnostics.length()} release_status=\{release.status} status=\{inspection.status}",
  )
  for page in project.manifest.pages {
    println(
      "route=\{page.route} status=\{page.status} nodes=\{page.node_count} depth=\{page.depth} components=\{page.component_count} events=\{page.event_count} windowed_lists=\{page.windowed_list_count} list_items=\{page.list_visible_count}/\{page.list_total_count} scene_markers=\{page.scene_visible_marker_count}/\{page.scene_marker_count} scene_regions=\{page.scene_visible_region_count}/\{page.scene_region_count} scene_assets=\{page.scene_asset_count} file_bytes=\{page.file_bytes} diagnostics=\{page.diagnostic_count}",
    )
    for item in page.diagnostics {
      println("route_diagnostic=\{page.route}:\{item}")
    }
  }
  for file in project.manifest.files {
    println(
      "file=\{file.path} kind=\{file.kind} route=\{file.route} bytes=\{file.bytes}",
    )
  }
}

///|
fn print_inspect_result(
  target : String,
  example : String,
  budget_profile : String,
  render_budget_profile : String,
  project : @bunnia.WechatProject,
  report : @bunnia.WechatBuildReport,
  patches : @bunnia.PatchPlan,
  profile : @bunnia.BuildProfile,
  profile_gate : @bunnia.BuildProfileGate,
  snapshot : @bunnia.GeneratedSnapshot,
  inspection : @bunnia.ProjectInspection,
  inspection_gate : @bunnia.ProjectInspectionGate,
  release : @bunnia.ReleaseReadinessReport,
) -> Unit {
  println("command=inspect")
  println("target=\{target}")
  println("example=\{example}")
  println("budget=\{budget_profile}")
  println("render_budget=\{render_budget_profile}")
  println(project.page.plan.summary)
  println(project.summary)
  println(project.manifest.summary)
  println(report.summary)
  print_diagnostics(report.diagnostics)
  println(patches.summary)
  println(profile.summary)
  print_profile_diagnostics(profile.diagnostics)
  println(profile_gate.summary)
  print_profile_gate_diagnostics(profile_gate.diagnostics)
  println(inspection_gate.summary)
  print_inspection_gate_diagnostics(inspection_gate.diagnostics)
  println(release.summary)
  print_release_diagnostics(release.diagnostics)
  println(snapshot.summary)
  println(inspection.summary)
  println(
    "inspect=route=\{snapshot.route} pages=\{snapshot.page_count} files=\{snapshot.file_count} bytes=\{snapshot.total_bytes} max_first_screen=\{snapshot.max_first_screen_bytes} max_update_payload=\{snapshot.max_update_payload_bytes} max_update_ops=\{snapshot.max_update_operation_count} route_diagnostic_routes=\{snapshot.route_diagnostic_route_count} route_diagnostics=\{snapshot.route_diagnostic_count} diagnostics=\{snapshot.diagnostic_count}",
  )
  println(
    "risk_route=\{inspection.top_route} risk_score=\{inspection.risk_score} routes=\{inspection.route_count} diagnostics=\{inspection.total_diagnostics} max_unwindowed_list_children=\{inspection.max_unwindowed_list_child_count} max_unkeyed_list_children=\{inspection.max_unkeyed_list_child_count} max_duplicate_list_keys=\{inspection.max_duplicate_list_key_count} max_backend_events=\{inspection.max_backend_event_count} max_backend_streams=\{inspection.max_backend_stream_count} max_backend_reviews=\{inspection.max_backend_review_count} max_backend_cancels=\{inspection.max_backend_cancel_count} max_backend_retries=\{inspection.max_backend_retry_count} max_scene_markers=\{inspection.max_scene_marker_count} max_scene_regions=\{inspection.max_scene_region_count} max_scene_assets=\{inspection.max_scene_asset_count} max_scene_remote_assets=\{inspection.max_scene_remote_asset_count} max_scene_package_bytes=\{inspection.max_scene_package_bytes}",
  )
  for route in inspection.routes {
    println(route.summary)
  }
  for page in project.manifest.pages {
    println(
      "route=\{page.route} status=\{page.status} nodes=\{page.node_count} depth=\{page.depth} components=\{page.component_count} events=\{page.event_count} unwindowed_list_children=\{page.unwindowed_list_child_count} unkeyed_list_children=\{page.unkeyed_list_child_count} duplicate_list_keys=\{page.duplicate_list_key_count} backend_events=\{page.backend_event_count} backend_streams=\{page.backend_stream_count} backend_reviews=\{page.backend_review_count} backend_cancels=\{page.backend_cancel_count} backend_retries=\{page.backend_retry_count} scene_markers=\{page.scene_visible_marker_count}/\{page.scene_marker_count} scene_regions=\{page.scene_visible_region_count}/\{page.scene_region_count} scene_assets=\{page.scene_assets.length()} scene_remote_assets=\{page.scene_remote_asset_count} scene_package_bytes=\{page.scene_package_bytes} first_screen=\{page.first_screen_bytes} update_payload=\{page.update_payload_bytes} update_ops=\{page.update_operation_count} file_bytes=\{page.file_bytes} diagnostics=\{page.diagnostic_count}",
    )
    for asset in page.scene_assets {
      println(
        "scene_asset=\{asset.route}:\{asset.id} kind=\{asset.kind} src=\{asset.src} remote=\{asset.remote} estimated_bytes=\{asset.estimated_bytes}",
      )
    }
    for item in page.diagnostics {
      println("route_diagnostic=\{page.route}:\{item}")
    }
  }
  for file in snapshot.files {
    println(
      "file=\{file.path} kind=\{file.kind} bytes=\{file.bytes} checksum=\{file.checksum}",
    )
  }
}

///|
fn print_inspection_gate_diagnostics(items : Array[String]) -> Unit {
  for item in items {
    println("inspection_diagnostic=\{item}")
  }
}

///|
fn print_profile_gate_diagnostics(items : Array[String]) -> Unit {
  for item in items {
    println("profile_gate_diagnostic=\{item}")
  }
}

///|
fn print_release_diagnostics(items : Array[String]) -> Unit {
  for item in items {
    println("release_diagnostic=\{item}")
  }
}

///|
fn print_miniapp_inspection_gate_diagnostics(items : Array[String]) -> Unit {
  for item in items {
    println("miniapp_inspection_diagnostic=\{item}")
  }
}

///|
fn print_ci_plan(plan : @bunnia.CiPlan) -> Unit {
  println("command=ci-plan")
  println("target=\{plan.target}")
  println("budget=\{plan.budget_profile}")
  println("render_budget=\{plan.render_budget_profile}")
  println(plan.summary)
  println(plan.inspection_gate_summary)
  println(plan.profile_gate_summary)
  print_diagnostics(plan.diagnostics)
  for step in plan.steps {
    println("step=\{step.name} command=\{step.command}")
  }
}

///|
fn print_platform_limits(report : @bunnia.PlatformLimitsReport) -> Unit {
  println("command=limits")
  println("target=\{report.target}")
  println(report.summary)
  print_diagnostics(report.diagnostics)
  println("generator_status=\{report.generator_status_id}")
  println(
    "capability=canvas:\{report.supports_canvas} cloud:\{report.supports_cloud} stream:\{report.supports_stream} generator:\{report.generator_available}",
  )
  for item in report.component_mappings {
    println(
      "component=\{item.bunnia_kind} platform_component=\{item.platform_component}",
    )
  }
  for item in report.event_mappings {
    println("event=\{item.bunnia_event} platform_event=\{item.platform_event}")
  }
  for item in report.lifecycle_mappings {
    println(
      "lifecycle=\{item.bunnia_lifecycle} platform_lifecycle=\{item.platform_lifecycle}",
    )
  }
  for item in report.request_mappings {
    println("request=\{item.bunnia_request} platform_api=\{item.platform_api}")
  }
}

///|
fn should_fail_build(
  strict : Bool,
  project : @bunnia.WechatProject,
  report : @bunnia.WechatBuildReport,
  patches : @bunnia.PatchPlan,
  profile : @bunnia.BuildProfile,
  profile_gate : @bunnia.BuildProfileGate,
  inspection_gate : @bunnia.ProjectInspectionGate,
  release : @bunnia.ReleaseReadinessReport,
) -> Bool {
  strict &&
  (
    project_has_render_diagnostics(project) ||
    report.diagnostics.length() > 0 ||
    patches.diagnostics.length() > 0 ||
    profile.diagnostics.length() > 0 ||
    profile_gate.diagnostics.length() > 0 ||
    inspection_gate.diagnostics.length() > 0 ||
    release.diagnostics.length() > 0
  )
}

///|
fn project_has_render_diagnostics(project : @bunnia.WechatProject) -> Bool {
  for page in project.pages {
    if page.page.plan.diagnostics.length() > 0 {
      return true
    }
  }
  false
}

///|
fn write_project(output_dir : String, project : @bunnia.WechatProject) -> Unit {
  reset_project_output_dir(output_dir)
  for file in project.files {
    write_generated_file(join_path(output_dir, file.path), file.content)
  }
}

///|
fn write_generated_file(path : String, content : String) -> Unit {
  if content.has_prefix("base64:") {
    write_base64_file(
      path,
      content.sub(start=7, end=content.length()).to_owned(),
    )
  } else if content.has_prefix("copy-file:") {
    copy_binary_file(
      content.sub(start=10, end=content.length()).to_owned(),
      path,
    )
  } else {
    write_text_file(path, content)
  }
}

///|
fn write_miniapp_project(
  output_dir : String,
  project : @bunnia.MiniappProject,
) -> Unit {
  reset_project_output_dir(output_dir)
  for file in project.files {
    write_text_file(join_path(output_dir, file.path), file.content)
  }
}

///|
fn write_scaffold(output_dir : String, scaffold : @bunnia.ScaffoldPlan) -> Unit {
  for file in scaffold.files {
    let path = join_path(output_dir, file.path)
    if scaffold_file_is_executable(file.path) {
      write_executable_text_file(path, file.content)
    } else {
      write_text_file(path, file.content)
    }
  }
  write_text_file(
    join_path(output_dir, "moon.work"),
    scaffold_workspace_content(current_working_dir()),
  )
}

///|
fn scaffold_write_file_count(scaffold : @bunnia.ScaffoldPlan) -> Int {
  scaffold.files.length() + 1
}

///|
fn scaffold_file_is_executable(path : String) -> Bool {
  path.has_prefix("scripts/")
}

///|
fn scaffold_workspace_content(framework_path : String) -> String {
  "members = [\n" +
  "  \".\",\n" +
  "  \"\{escape_toml_string(framework_path)}\",\n" +
  "]\n"
}

///|
fn join_path(left : String, right : String) -> String {
  if left == "" {
    right
  } else if left.has_suffix("/") {
    "\{left}\{right}"
  } else {
    "\{left}/\{right}"
  }
}

///|
fn reset_project_output_dir(output_dir : String) -> Unit {
  if !generated_output_dir_is_safe(output_dir) {
    println("unsafe_output_dir=\{output_dir}")
    panic()
  }
  clean_generated_output_dir(output_dir)
}

///|
fn generated_output_dir_is_safe(output_dir : String) -> Bool {
  let path = trim_trailing_slashes(output_dir)
  if path == "" || path == "." || path == "/" {
    false
  } else if output_dir_has_parent_traversal(path) {
    false
  } else if path == "_build" ||
    path == "_build/bunnia" ||
    path == "/tmp" ||
    path == "/private/tmp" {
    false
  } else {
    path.has_prefix("_build/") ||
    path.has_prefix("/tmp/bunnia-") ||
    path.has_prefix("/tmp/bunnia_") ||
    path.has_prefix("/private/tmp/bunnia-") ||
    path.has_prefix("/private/tmp/bunnia_")
  }
}

///|
fn trim_trailing_slashes(path : String) -> String {
  if path.length() > 1 && path.has_suffix("/") {
    trim_trailing_slashes(path.sub(start=0, end=path.length() - 1).to_owned())
  } else {
    path
  }
}

///|
fn output_dir_has_parent_traversal(path : String) -> Bool {
  path == ".." ||
  path.has_prefix("../") ||
  path.has_suffix("/..") ||
  path.contains("/../")
}

///|
fn print_diagnostics(items : Array[String]) -> Unit {
  for item in items {
    println("diagnostic=\{item}")
  }
}

///|
fn print_profile_diagnostics(items : Array[String]) -> Unit {
  for item in items {
    println("profile_diagnostic=\{item}")
  }
}

///|
fn escape_toml_string(value : String) -> String {
  value.replace(old="\\", new="\\\\").replace(old="\"", new="\\\"").to_string()
}

///|
#cfg(target="js")
extern "js" fn current_working_dir() -> String =
  #| function() {
  #|   return process.cwd()
  #| }

///|
#cfg(not(target="js"))
fn current_working_dir() -> String {
  "."
}

///|
#cfg(target="js")
extern "js" fn watch_generated_output(
  target : String,
  example : String,
  output_dir : String,
  strict : Bool,
) -> Unit =
  #| function(target, example, outputDir, strict) {
  #|   const fs = require("fs")
  #|   const childProcess = require("child_process")
  #|   const watchRoots = [
  #|     "core",
  #|     "agent",
  #|     "scene",
  #|     "effects",
  #|     "program",
  #|     "adapters",
  #|     "tooling",
  #|     "examples",
  #|     "cmd",
  #|     "docs",
  #|   ]
  #|   const rootFiles = ["moon.mod", "moon.pkg", "README.mbt.md"]
  #|   const buildArgs = ["run", "cmd/main", "--", "build", "--target", target, "--example", example, "--out", outputDir]
  #|   if (strict) buildArgs.push("--strict")
  #|   function shouldRebuild(filename) {
  #|     if (!filename) return true
  #|     const text = String(filename)
  #|     if (text.includes("_build") || text.includes(".git")) return false
  #|     return text.endsWith(".mbt") ||
  #|       text.endsWith(".mbt.md") ||
  #|       text.endsWith(".md") ||
  #|       text.endsWith(".mbti") ||
  #|       text.endsWith("moon.pkg") ||
  #|       text.endsWith("moon.mod")
  #|   }
  #|   let timer = null
  #|   function rebuild(reason) {
  #|     if (!shouldRebuild(reason)) return
  #|     clearTimeout(timer)
  #|     timer = setTimeout(() => {
  #|       console.log("watch=rebuild reason=" + (reason || "change"))
  #|       const result = childProcess.spawnSync("moon", buildArgs, { stdio: "inherit" })
  #|       if (result.error) {
  #|         console.error("watch_error=" + result.error.message)
  #|       } else if (result.status !== 0) {
  #|         console.error("watch_exit=" + result.status)
  #|       }
  #|     }, 150)
  #|   }
  #|   for (const file of rootFiles) {
  #|     if (fs.existsSync(file)) {
  #|       fs.watch(file, () => rebuild(file))
  #|     }
  #|   }
  #|   for (const root of watchRoots) {
  #|     if (fs.existsSync(root)) {
  #|       fs.watch(root, { recursive: true }, (_event, filename) => {
  #|         rebuild(filename ? root + "/" + filename : root)
  #|       })
  #|     }
  #|   }
  #|   console.log("watch=active target=" + target + " example=" + example + " output=" + outputDir)
  #|   console.log("watching=" + watchRoots.join(","))
  #|   process.stdin.resume()
  #| }

///|
#cfg(not(target="js"))
fn watch_generated_output(
  _target : String,
  _example : String,
  _output_dir : String,
  _strict : Bool,
) -> Unit {
  panic()
}

///|
#cfg(target="js")
extern "js" fn clean_generated_output_dir(path : String) -> Unit =
  #| function(path) {
  #|   const fs = require("fs")
  #|   fs.rmSync(path, { recursive: true, force: true })
  #|   fs.mkdirSync(path, { recursive: true })
  #| }

///|
#cfg(not(target="js"))
fn clean_generated_output_dir(_path : String) -> Unit {
  panic()
}

///|
#cfg(target="js")
extern "js" fn write_text_file(path : String, content : String) -> Unit =
  #| function(path, content) {
  #|   const fs = require("fs")
  #|   const nodePath = require("path")
  #|   fs.mkdirSync(nodePath.dirname(path), { recursive: true })
  #|   fs.writeFileSync(path, content, "utf8")
  #| }

///|
#cfg(not(target="js"))
fn write_text_file(_path : String, _content : String) -> Unit {
  panic()
}

///|
#cfg(target="js")
extern "js" fn write_base64_file(path : String, content : String) -> Unit =
  #| function(path, content) {
  #|   const fs = require("fs")
  #|   const nodePath = require("path")
  #|   fs.mkdirSync(nodePath.dirname(path), { recursive: true })
  #|   fs.writeFileSync(path, Buffer.from(content, "base64"))
  #| }

///|
#cfg(not(target="js"))
fn write_base64_file(_path : String, _content : String) -> Unit {
  panic()
}

///|
#cfg(target="js")
extern "js" fn copy_binary_file(
  source_path : String,
  target_path : String,
) -> Unit =
  #| function(sourcePath, targetPath) {
  #|   const fs = require("fs")
  #|   const nodePath = require("path")
  #|   fs.mkdirSync(nodePath.dirname(targetPath), { recursive: true })
  #|   fs.copyFileSync(sourcePath, targetPath)
  #| }

///|
#cfg(not(target="js"))
fn copy_binary_file(_source_path : String, _target_path : String) -> Unit {
  panic()
}

///|
#cfg(target="js")
extern "js" fn write_executable_text_file(
  path : String,
  content : String,
) -> Unit =
  #| function(path, content) {
  #|   const fs = require("fs")
  #|   const nodePath = require("path")
  #|   fs.mkdirSync(nodePath.dirname(path), { recursive: true })
  #|   fs.writeFileSync(path, content, { encoding: "utf8", mode: 0o755 })
  #|   fs.chmodSync(path, 0o755)
  #| }

///|
#cfg(not(target="js"))
fn write_executable_text_file(_path : String, _content : String) -> Unit {
  panic()
}