///|
fn target_metrics_tick(enabled : Bool) -> @perf.PerfTick? {
  if enabled {
    Some(@perf.tick_now())
  } else {
    None
  }
}

///|
fn detail_metrics_tick(enabled : Bool) -> @perf.PerfTick? {
  if enabled && @perf.detail_enabled() {
    Some(@perf.tick_now())
  } else {
    None
  }
}

///|
fn record_compile_subphase(
  name : String,
  tick : @perf.PerfTick?,
  parent? : String = "function",
) -> Unit {
  if tick is Some(start) {
    @perf.record_compile_subphase_us(name, @perf.elapsed_us(start), parent~)
  }
}

///|
fn record_target_stage(
  stage : @perf.FunctionCompileStage,
  tick : @perf.PerfTick?,
) -> Int64? {
  match tick {
    Some(start) => {
      let duration_us = @perf.elapsed_us(start)
      @perf.record_stage_us(stage, duration_us)
      Some(duration_us)
    }
    None => None
  }
}

///|
fn record_target_allocation_stats(
  statistics : @vcode.AllocationStatistics,
) -> Unit {
  @perf.record_regalloc_stats(
    statistics.spill_slots,
    statistics.spills,
    statistics.reloads,
    statistics.reg_moves,
    statistics.spill_to_spill,
  )
}

///|
fn native_lower_metrics_observer() -> (@milkir_native.NativeLowerEvent) -> Unit {
  let phase : Ref[(String, @perf.PerfTick)?] = { val: None, }
  fn close_phase() -> Unit {
    if phase.val is Some((name, tick)) {
      @perf.record_compile_subphase_us(
        name,
        @perf.elapsed_us(tick),
        parent="native_lowering",
      )
      phase.val = None
    }
  }

  fn(event) {
    match event {
      InputValidationStarted => {
        close_phase()
        phase.val = Some(("native_input_validation", @perf.tick_now()))
      }
      TargetConstructionStarted => {
        close_phase()
        phase.val = Some(("target_construction", @perf.tick_now()))
      }
      NativeLoweringFinished => close_phase()
    }
  }
}

///|
fn target_compile_metrics_observer(
  target_lower_tick : Ref[@perf.PerfTick?],
) -> (@vcode.TargetCompileEvent) -> Unit {
  let detail = @perf.detail_enabled()
  let phase_tick : Ref[@perf.PerfTick?] = { val: None, }
  let target_phase : Ref[(String, @perf.PerfTick)?] = { val: None, }
  let validation_phase : Ref[(String, @perf.PerfTick)?] = { val: None, }
  // The allocator reports each internal phase as it begins; the previous one
  // ends at that instant, and RegallocFinished closes the last (ISS-371).
  let regalloc_phase : Ref[(String, @perf.PerfTick)?] = { val: None, }
  fn close_regalloc_phase() -> Unit {
    if regalloc_phase.val is Some((name, tick)) {
      @perf.record_regalloc_phase_us(name, @perf.elapsed_us(tick))
      regalloc_phase.val = None
    }
  }

  fn close_target_phase() -> Unit {
    if target_phase.val is Some((name, tick)) {
      @perf.record_compile_subphase_us(
        name,
        @perf.elapsed_us(tick),
        parent="target_lowering",
      )
      target_phase.val = None
    }
  }

  fn close_validation_phase() -> Unit {
    if validation_phase.val is Some((name, tick)) {
      @perf.record_compile_subphase_us(
        name,
        @perf.elapsed_us(tick),
        parent="target_vcode_validation",
      )
      validation_phase.val = None
    }
  }

  fn(event) {
    match event {
      TargetAnalysisStarted =>
        if detail {
          close_target_phase()
          target_phase.val = Some(("target_analysis", @perf.tick_now()))
        }
      // Direct target construction is streamed inside NativeLowering and is
      // already attributed by native_lower_metrics_observer.
      TargetConstructionStarted => ()
      TargetValidationStarted =>
        if detail {
          close_target_phase()
          target_phase.val = Some(("target_vcode_validation", @perf.tick_now()))
        }
      TargetCommonValidationStarted =>
        if detail {
          close_validation_phase()
          validation_phase.val = Some(
            ("target_common_validation", @perf.tick_now()),
          )
        }
      TargetIsaValidationStarted =>
        if detail {
          close_validation_phase()
          validation_phase.val = Some(
            ("target_isa_validation", @perf.tick_now()),
          )
        }
      TargetSealingStarted =>
        if detail {
          close_target_phase()
          target_phase.val = Some(("target_vcode_sealing", @perf.tick_now()))
        }
      TargetSelectionFinished =>
        if detail {
          close_validation_phase()
          close_target_phase()
        }
      RegallocStarted => {
        close_target_phase()
        record_target_stage(TargetLowering, target_lower_tick.val) |> ignore
        phase_tick.val = Some(@perf.tick_now())
      }
      RegallocPhaseStarted(name) =>
        if detail {
          close_regalloc_phase()
          regalloc_phase.val = Some((name, @perf.tick_now()))
        }
      RegallocPhasesFinished =>
        if detail {
          close_regalloc_phase()
          // Everything from here to RegallocFinished is plan translation and
          // target-level checking, not allocator work. Naming it keeps the
          // phases summing to the measured regalloc stage.
          regalloc_phase.val = Some(("plan_translation", @perf.tick_now()))
        }
      RegallocLoopMeasured(statistics) =>
        @perf.record_regalloc_loop(
          statistics.queue_pops,
          statistics.register_probes,
          statistics.occupied_segments_scanned,
          statistics.conflicts,
          statistics.evictions,
          statistics.bundle_splits,
          statistics.second_chance_attempts,
          statistics.max_queue_length,
        )
      RegallocFinished(statistics) => {
        close_regalloc_phase()
        if record_target_stage(RegisterAllocation, phase_tick.val)
          is Some(duration_us) {
          @perf.record_regalloc_phase_us("allocate_vcode", duration_us)
        }
        record_target_allocation_stats(statistics)
      }
      FramePlanningStarted => phase_tick.val = Some(@perf.tick_now())
      FramePlanningFinished =>
        record_target_stage(FramePlanning, phase_tick.val) |> ignore
      EmissionStarted => phase_tick.val = Some(@perf.tick_now())
      EmissionFinished =>
        record_target_stage(Emission, phase_tick.val) |> ignore
    }
  }
}