///|
pub struct WindowRegistry {
  mut next_value : Int
  mut records : Array[@backend_host.WindowRecord]
}

///|
pub fn WindowRegistry::new() -> WindowRegistry {
  { next_value: 1, records: [], }
}

///|
pub fn WindowRegistry::open(
  self : WindowRegistry,
  title~ : String,
  scene? : @backend_host.WindowScene = @backend_host.WindowScene::main(),
  metrics~ : @core.SurfaceMetrics,
  primary? : Bool = false,
) -> @backend_host.WindowId {
  let id = @backend_host.WindowId::new(self.next_value)
  self.next_value = self.next_value + 1
  let is_primary = primary || self.open_window_count() == 0
  if is_primary {
    self.clear_primary()
  }
  self.records.push(
    @backend_host.WindowRecord::new(
      id~,
      title~,
      scene~,
      metrics~,
      phase=WindowVisible,
      primary=is_primary,
    ),
  )
  id
}

///|
pub fn WindowRegistry::lookup(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> @backend_host.WindowRecord? {
  for record in self.records {
    if record.id == id {
      return Some(record)
    }
  }
  None
}

///|
pub fn WindowRegistry::open_windows(
  self : WindowRegistry,
) -> Array[@backend_host.WindowRecord] {
  self.records.filter(record => record.phase != WindowClosed)
}

///|
pub fn WindowRegistry::open_window_count(self : WindowRegistry) -> Int {
  self.open_windows().length()
}

///|
pub fn WindowRegistry::primary_window(
  self : WindowRegistry,
) -> @backend_host.WindowRecord? {
  for record in self.records {
    if record.primary && record.phase != WindowClosed {
      return Some(record)
    }
  }
  None
}

///|
pub fn WindowRegistry::focused_window(
  self : WindowRegistry,
) -> @backend_host.WindowRecord? {
  for record in self.records {
    if record.phase == WindowFocused {
      return Some(record)
    }
  }
  None
}

///|
pub fn WindowRegistry::focus(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  if !self.has_open_window(id) {
    return false
  }
  self.records = self.records.map(record => {
    if record.id == id {
      { ..record, phase: WindowFocused, }
    } else if record.phase == WindowFocused {
      { ..record, phase: WindowVisible, }
    } else {
      record
    }
  })
  true
}

///|
pub fn WindowRegistry::resize(
  self : WindowRegistry,
  id : @backend_host.WindowId,
  metrics : @core.SurfaceMetrics,
) -> Bool {
  self.update_open_window(id, record => { ..record, metrics, })
}

///|
pub fn WindowRegistry::minimize(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  self.update_open_window(id, record => { ..record, phase: WindowMinimized, })
}

///|
pub fn WindowRegistry::show(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  self.update_open_window(id, record => { ..record, phase: WindowVisible, })
}

///|
pub fn WindowRegistry::request_close(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  self.update_open_window(id, record => {
    ..record,
    phase: WindowCloseRequested,
  })
}

///|
pub fn WindowRegistry::close(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  let changed = self.update_open_window(id, record => {
    ..record,
    phase: WindowClosed,
    primary: false,
  })
  if changed {
    self.ensure_primary_window()
  }
  changed
}

///|
pub fn WindowRegistry::remove_closed(self : WindowRegistry) -> Int {
  let before = self.records.length()
  self.records = self.records.filter(record => record.phase != WindowClosed)
  before - self.records.length()
}

///|
pub fn WindowRegistry::set_primary(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  if !self.has_open_window(id) {
    return false
  }
  self.records = self.records.map(record => {
    ..record,
    primary: record.id == id && record.phase != WindowClosed,
  })
  true
}

///|
pub fn WindowRegistry::apply_host_event(
  self : WindowRegistry,
  id : @backend_host.WindowId,
  event : @backend_host.Event,
) -> Bool {
  match event {
    Resized(metrics) => self.resize(id, metrics)
    Focus({ focused: true, .. }) => self.focus(id)
    Focus({ focused: false, .. }) =>
      self.update_open_window(id, record => {
        if record.phase == WindowFocused {
          { ..record, phase: WindowVisible, }
        } else {
          record
        }
      })
    CloseRequested => self.request_close(id)
    _ => false
  }
}

///|
pub fn WindowRegistry::apply_request(
  self : WindowRegistry,
  request : @backend_host.WindowRequest,
) -> @backend_host.WindowRequestResult {
  match request {
    OpenWindow(request) =>
      WindowOpened(
        self.open(
          title=request.title,
          scene=request.scene,
          metrics=request.metrics,
          primary=request.primary,
        ),
      )
    FocusWindow(id) =>
      window_request_update_result(id, self.focus(id), action="focus")
    CloseWindow(id) =>
      window_request_update_result(id, self.request_close(id), action="close")
    ResizeWindow(id, metrics) =>
      window_request_update_result(
        id,
        self.resize(id, metrics),
        action="resize",
      )
    MinimizeWindow(id) =>
      window_request_update_result(id, self.minimize(id), action="minimize")
    ShowWindow(id) =>
      window_request_update_result(id, self.show(id), action="show")
    SetPrimaryWindow(id) =>
      window_request_update_result(
        id,
        self.set_primary(id),
        action="set primary",
      )
  }
}

///|
fn WindowRegistry::has_open_window(
  self : WindowRegistry,
  id : @backend_host.WindowId,
) -> Bool {
  match self.lookup(id) {
    Some(record) => record.phase != WindowClosed
    None => false
  }
}

///|
fn WindowRegistry::clear_primary(self : WindowRegistry) -> Unit {
  self.records = self.records.map(record => { ..record, primary: false, })
}

///|
fn WindowRegistry::ensure_primary_window(self : WindowRegistry) -> Unit {
  match self.primary_window() {
    Some(_) => ()
    None => {
      let mut assigned = false
      self.records = self.records.map(record => {
        if !assigned && record.phase != WindowClosed {
          assigned = true
          { ..record, primary: true, }
        } else {
          record
        }
      })
    }
  }
}

///|
fn WindowRegistry::update_open_window(
  self : WindowRegistry,
  id : @backend_host.WindowId,
  update : (@backend_host.WindowRecord) -> @backend_host.WindowRecord,
) -> Bool {
  let mut changed = false
  self.records = self.records.map(record => {
    if record.id == id && record.phase != WindowClosed {
      changed = true
      update(record)
    } else {
      record
    }
  })
  changed
}

///|
fn window_request_update_result(
  id : @backend_host.WindowId,
  changed : Bool,
  action~ : String,
) -> @backend_host.WindowRequestResult {
  if changed {
    WindowUpdated(id)
  } else {
    WindowRejected("cannot " + action + " window: \{id.value}")
  }
}