///|
pub(all) enum GpuRenderWorkerState {
  Idle
  Lost
  Recovering
  Recovered
  FallbackToRaster
  Shutdown
} derive(Eq, Debug)

///|
pub(all) enum GpuRenderWorkerControl {
  Attach(UInt64)
  AttachSurface(UInt64, UInt64)
  Resize(Int, Int)
  Detach
  ContextLoss(String)
  Shutdown
} derive(Eq, Debug)

///|
pub(all) enum GpuRenderCompletionStatus {
  PictureRecorded
  Presented
  Dropped
  FallbackToRaster
} derive(Eq, Debug)

///|
pub(all) struct GpuRenderCompletion {
  sequence : Int
  surface_generation : Int
  image_resource_revision : Int64
  submitted_at_ms : Double
  gpu_completed_at_ms : Double
  presented_at_ms : Double
  status : GpuRenderCompletionStatus
  context_generation : Int
} derive(Eq, Debug)

///|
pub(all) struct GpuRenderWorkerDiagnostics {
  state : GpuRenderWorkerState
  submitted : Int
  started : Int
  completed : Int
  dropped : Int
  replaced_pending : Int
  rejected_generation : Int
  surface_generation : Int
  context_generation : Int
  recovery_count : Int
  recovery_failures : Int
  readback_count : Int
  resource_cache_bytes : Int64
  control_pending : Int
  in_flight : Bool
  pending : Bool
  worker_thread_id : Int64
} derive(Eq, Debug)

///|
struct GpuRenderPictureFrame {
  sequence : Int
  surface_generation : Int
  image_resource_revision : Int64
  submitted_at_ms : Double
  picture : Picture?
}

///|
/// Capacity-two native-picture queue. Platform integrations drive `poll` only
/// from the renderer worker thread; the queue never invokes MoonBit callbacks.
pub struct GpuRenderWorker {
  mut state : GpuRenderWorkerState
  mut next_sequence : Int
  mut surface_generation : Int
  mut context_generation : Int
  mut in_flight : GpuRenderPictureFrame?
  mut pending : GpuRenderPictureFrame?
  controls : Array[GpuRenderWorkerControl]
  completions : Array[GpuRenderCompletion]
  mut submitted : Int
  mut started : Int
  mut completed : Int
  mut dropped : Int
  mut replaced_pending : Int
  mut rejected_generation : Int
  mut recovery_count : Int
  mut recovery_failures : Int
  mut fail_next_recovery : Bool
  resource_cache_bytes : Int64
  worker_thread_id : Int64
}

///|
pub fn GpuRenderWorker::new(
  surface_generation? : Int = 1,
  worker_thread_id? : Int64 = 0L,
) -> GpuRenderWorker {
  {
    state: GpuRenderWorkerState::Idle,
    next_sequence: 1,
    surface_generation: surface_generation.max(1),
    context_generation: 1,
    in_flight: None,
    pending: None,
    controls: [],
    completions: [],
    submitted: 0,
    started: 0,
    completed: 0,
    dropped: 0,
    replaced_pending: 0,
    rejected_generation: 0,
    recovery_count: 0,
    recovery_failures: 0,
    fail_next_recovery: false,
    resource_cache_bytes: 0L,
    worker_thread_id,
  }
}

///|
pub fn GpuRenderWorker::submit(
  self : GpuRenderWorker,
  picture : Picture?,
  image_resource_revision? : Int64 = 0L,
  submitted_at_ms? : Double = 0.0,
) -> Int {
  if self.state is GpuRenderWorkerState::Shutdown ||
    self.state is GpuRenderWorkerState::FallbackToRaster {
    self.dropped = self.dropped + 1
    return 0
  }
  let sequence = self.next_sequence
  self.next_sequence = self.next_sequence + 1
  self.submitted = self.submitted + 1
  if self.pending is Some(_) {
    self.replaced_pending = self.replaced_pending + 1
    self.dropped = self.dropped + 1
  }
  self.pending = Some({
    sequence,
    surface_generation: self.surface_generation,
    image_resource_revision,
    submitted_at_ms,
    picture,
  })
  sequence
}

///|
pub fn GpuRenderWorker::submit_control(
  self : GpuRenderWorker,
  control : GpuRenderWorkerControl,
) -> Int {
  if self.state is GpuRenderWorkerState::Shutdown {
    return self.controls.length()
  }
  self.controls.push(control)
  self.controls.length()
}

///|
pub fn GpuRenderWorker::fail_next_context_recovery(
  self : GpuRenderWorker,
) -> Unit {
  self.fail_next_recovery = true
}

///|
fn GpuRenderWorker::apply_control(
  self : GpuRenderWorker,
  control : GpuRenderWorkerControl,
) -> Bool {
  match control {
    Attach(_) | AttachSurface(_, _) | Resize(_, _) => {
      self.surface_generation = self.surface_generation + 1
      self.in_flight = None
      true
    }
    Detach => {
      self.surface_generation = self.surface_generation + 1
      self.in_flight = None
      self.pending = None
      true
    }
    ContextLoss(_) => {
      if self.state is GpuRenderWorkerState::FallbackToRaster {
        ()
      } else {
        self.state = GpuRenderWorkerState::Lost
        self.in_flight = None
        self.pending = None
      }
      true
    }
    Shutdown => {
      self.state = GpuRenderWorkerState::Shutdown
      self.in_flight = None
      self.pending = None
      false
    }
  }
}

///|
fn GpuRenderWorker::recover_if_needed(self : GpuRenderWorker) -> Unit {
  match self.state {
    Lost => self.state = GpuRenderWorkerState::Recovering
    Recovering =>
      if self.fail_next_recovery {
        self.fail_next_recovery = false
        self.recovery_failures = self.recovery_failures + 1
        if self.recovery_failures >= 2 {
          self.state = GpuRenderWorkerState::FallbackToRaster
          self.completions.push({
            sequence: 0,
            surface_generation: self.surface_generation,
            image_resource_revision: 0L,
            submitted_at_ms: 0.0,
            gpu_completed_at_ms: 0.0,
            presented_at_ms: 0.0,
            status: GpuRenderCompletionStatus::FallbackToRaster,
            context_generation: self.context_generation,
          })
        } else {
          self.state = GpuRenderWorkerState::Lost
        }
      } else {
        self.context_generation = self.context_generation + 1
        self.recovery_count = self.recovery_count + 1
        self.recovery_failures = 0
        self.state = GpuRenderWorkerState::Recovered
      }
    Recovered => self.state = GpuRenderWorkerState::Idle
    _ => ()
  }
}

///|
pub fn GpuRenderWorker::poll(
  self : GpuRenderWorker,
  gpu_completed_at_ms? : Double = 0.0,
  presented_at_ms? : Double = 0.0,
) -> Unit {
  let _ = presented_at_ms
  while !self.controls.is_empty() {
    if !self.apply_control(self.controls.remove(0)) {
      return ()
    }
  }
  self.recover_if_needed()
  if self.state is GpuRenderWorkerState::Lost ||
    self.state is GpuRenderWorkerState::Recovering ||
    self.state is GpuRenderWorkerState::FallbackToRaster ||
    self.state is GpuRenderWorkerState::Shutdown {
    return ()
  }
  if self.in_flight is None {
    match self.pending {
      Some(frame) => {
        self.pending = None
        if frame.surface_generation != self.surface_generation {
          self.rejected_generation = self.rejected_generation + 1
          self.dropped = self.dropped + 1
        } else {
          self.in_flight = Some(frame)
          self.started = self.started + 1
        }
      }
      None => ()
    }
  }
  match self.in_flight {
    Some(frame) => {
      match frame.picture {
        Some(picture) => ignore(picture.is_available())
        None => ()
      }
      self.in_flight = None
      self.completed = self.completed + 1
      self.completions.push({
        sequence: frame.sequence,
        surface_generation: frame.surface_generation,
        image_resource_revision: frame.image_resource_revision,
        submitted_at_ms: frame.submitted_at_ms,
        gpu_completed_at_ms,
        presented_at_ms: 0.0,
        status: GpuRenderCompletionStatus::PictureRecorded,
        context_generation: self.context_generation,
      })
      self.state = GpuRenderWorkerState::Idle
    }
    None => ()
  }
}

///|
pub fn GpuRenderWorker::drain_completions(
  self : GpuRenderWorker,
) -> Array[GpuRenderCompletion] {
  let result = self.completions.copy()
  self.completions.clear()
  result
}

///|
pub fn GpuRenderWorker::diagnostics(
  self : GpuRenderWorker,
) -> GpuRenderWorkerDiagnostics {
  {
    state: self.state,
    submitted: self.submitted,
    started: self.started,
    completed: self.completed,
    dropped: self.dropped,
    replaced_pending: self.replaced_pending,
    rejected_generation: self.rejected_generation,
    surface_generation: self.surface_generation,
    context_generation: self.context_generation,
    recovery_count: self.recovery_count,
    recovery_failures: self.recovery_failures,
    readback_count: 0,
    resource_cache_bytes: self.resource_cache_bytes,
    control_pending: self.controls.length(),
    in_flight: self.in_flight is Some(_),
    pending: self.pending is Some(_),
    worker_thread_id: self.worker_thread_id,
  }
}