///|
priv struct PendingPdf {
  window : Int64
  view : Int64?
  request : Int
  path : String
  mut result : Result[Unit, WindowSessionError]?
  changed : @async.CondVar
}

///|
priv struct PdfCompletionStore {
  pending : Array[PendingPdf]
}

///|
fn PdfCompletionStore::PdfCompletionStore() -> PdfCompletionStore {
  { pending: [], }
}

///|
fn PendingPdf::finish(
  self : PendingPdf,
  result : Result[Unit, WindowSessionError],
) -> Unit {
  guard self.result is None else { return }
  self.result = Some(result)
  self.changed.broadcast()
}

///|
fn PendingPdf::fail(self : PendingPdf, detail : String) -> Unit {
  self.finish(
    Err(
      window_operation_failed(
        "print PDF to " + self.path,
        @native.NativeError::Status(status=-6, message=detail),
      ),
    ),
  )
}

///|
// None selects all windows; a selected window with no view selects its children
// too. Renderer termination is routed separately to the exact page below.
fn PdfCompletionStore::fail(
  self : PdfCompletionStore,
  window : Int64?,
  view : Int64?,
  detail : String,
) -> Unit {
  for pending in self.pending {
    if (window is None || window == Some(pending.window)) &&
      (view is None || view == pending.view) {
      pending.fail(detail)
    }
  }
}

///|
fn PdfCompletionStore::route(
  self : PdfCompletionStore,
  event : @native.RuntimeEvent,
) -> Bool {
  if event.pdf_print_result() is Some(result) {
    if event.window_id() is Some(window) {
      self.complete(window, event.view_id(), result)
    }
    // A cancelled waiter has already unregistered; discard its late completion.
    return true
  }
  if event.is_window_closed() || event.is_view_closed() {
    if event.window_id() is Some(window) {
      self.fail(Some(window), event.view_id(), "page has closed")
    }
  } else if event.renderer_process_termination() is Some(_) {
    for pending in self.pending {
      if event.window_id() == Some(pending.window) &&
        event.view_id() == pending.view {
        pending.fail("renderer process has terminated")
      }
    }
  }
  false
}

///|
async fn PdfCompletionStore::wait(
  self : PdfCompletionStore,
  window : Int64,
  view : Int64?,
  request : Int,
  path : String,
) -> Unit {
  let pending : PendingPdf = {
    window,
    view,
    request,
    path,
    result: None,
    changed: @async.CondVar::Cond(),
  }
  self.pending.push(pending)
  defer (for index, candidate in self.pending {
    if physical_equal(candidate, pending) {
      ignore(self.pending.remove(index))
      break
    }
  })
  while pending.result is None {
    pending.changed.wait()
  }
  match pending.result {
    Some(Ok(_)) => ()
    Some(Err(error)) => raise error
    None => abort("PDF waiter resumed without completion")
  }
}

///|
fn PdfCompletionStore::complete(
  self : PdfCompletionStore,
  window : Int64,
  view : Int64?,
  result : @native.NativePdfPrintResult,
) -> Unit {
  for pending in self.pending {
    if window == pending.window &&
      view == pending.view &&
      result.request_id == pending.request {
      if result.success {
        pending.finish(Ok(()))
      } else {
        pending.fail("CEF could not write the PDF")
      }
      return
    }
  }
}