///|
pub struct ResourceDescriptor {
  kind : String
  owner : String
  name : String
  metadata : String
} derive(Debug, Eq)

///|
pub fn ResourceDescriptor::new(
  kind~ : String,
  owner? : String = "",
  name? : String = "",
  metadata? : String = "",
) -> ResourceDescriptor {
  { kind, owner, name, metadata }
}

///|
pub fn ResourceDescriptor::kind(self : ResourceDescriptor) -> String {
  self.kind
}

///|
pub fn ResourceDescriptor::owner(self : ResourceDescriptor) -> String {
  self.owner
}

///|
pub fn ResourceDescriptor::name(self : ResourceDescriptor) -> String {
  self.name
}

///|
pub fn ResourceDescriptor::metadata(self : ResourceDescriptor) -> String {
  self.metadata
}

///|
pub fn ResourceDescriptor::validate(self : ResourceDescriptor) -> Array[String] {
  let problems : Array[String] = []
  if self.kind == "" {
    problems.push("resource kind is required")
  }
  problems
}

///|
pub fn ResourceDescriptor::to_json(self : ResourceDescriptor) -> String {
  [
    "{",
    "\"kind\":\{self.kind.json_string()},",
    "\"owner\":\{self.owner.json_string()},",
    "\"name\":\{self.name.json_string()},",
    "\"metadata\":\{self.metadata.json_string()}",
    "}",
  ].join("")
}

///|
pub struct ResourceHandle {
  id : String
  kind : String
  owner : String
  name : String
} derive(Debug, Eq)

///|
pub fn ResourceHandle::new(
  id~ : String,
  kind~ : String,
  owner? : String = "",
  name? : String = "",
) -> ResourceHandle {
  { id, kind, owner, name }
}

///|
pub fn ResourceHandle::from_entry(entry : ResourceEntry) -> ResourceHandle {
  ResourceHandle::new(
    id=entry.id(),
    kind=entry.kind(),
    owner=entry.owner(),
    name=entry.name(),
  )
}

///|
pub fn ResourceHandle::id(self : ResourceHandle) -> String {
  self.id
}

///|
pub fn ResourceHandle::kind(self : ResourceHandle) -> String {
  self.kind
}

///|
pub fn ResourceHandle::owner(self : ResourceHandle) -> String {
  self.owner
}

///|
pub fn ResourceHandle::name(self : ResourceHandle) -> String {
  self.name
}

///|
pub fn ResourceHandle::validate(self : ResourceHandle) -> Array[String] {
  let problems : Array[String] = []
  if self.id == "" {
    problems.push("resource handle id is required")
  }
  if self.kind == "" {
    problems.push("resource handle kind is required")
  }
  problems
}

///|
pub fn ResourceHandle::to_json(self : ResourceHandle) -> String {
  [
    "{",
    "\"id\":\{self.id.json_string()},",
    "\"kind\":\{self.kind.json_string()},",
    "\"owner\":\{self.owner.json_string()},",
    "\"name\":\{self.name.json_string()}",
    "}",
  ].join("")
}

///|
pub struct ResourceEntry {
  id : String
  sequence : Int
  kind : String
  owner : String
  name : String
  metadata : String
  closed : Bool
} derive(Debug, Eq)

///|
fn ResourceEntry::new(
  id~ : String,
  sequence~ : Int,
  descriptor : ResourceDescriptor,
) -> ResourceEntry {
  {
    id,
    sequence,
    kind: descriptor.kind(),
    owner: descriptor.owner(),
    name: descriptor.name(),
    metadata: descriptor.metadata(),
    closed: false,
  }
}

///|
pub fn ResourceEntry::id(self : ResourceEntry) -> String {
  self.id
}

///|
pub fn ResourceEntry::sequence(self : ResourceEntry) -> Int {
  self.sequence
}

///|
pub fn ResourceEntry::kind(self : ResourceEntry) -> String {
  self.kind
}

///|
pub fn ResourceEntry::owner(self : ResourceEntry) -> String {
  self.owner
}

///|
pub fn ResourceEntry::name(self : ResourceEntry) -> String {
  self.name
}

///|
pub fn ResourceEntry::metadata(self : ResourceEntry) -> String {
  self.metadata
}

///|
pub fn ResourceEntry::closed(self : ResourceEntry) -> Bool {
  self.closed
}

///|
pub fn ResourceEntry::descriptor(self : ResourceEntry) -> ResourceDescriptor {
  ResourceDescriptor::new(
    kind=self.kind,
    owner=self.owner,
    name=self.name,
    metadata=self.metadata,
  )
}

///|
pub fn ResourceEntry::handle(self : ResourceEntry) -> ResourceHandle {
  ResourceHandle::from_entry(self)
}

///|
pub fn ResourceEntry::to_json(self : ResourceEntry) -> String {
  [
    "{",
    "\"id\":\{self.id.json_string()},",
    "\"sequence\":\{self.sequence},",
    "\"kind\":\{self.kind.json_string()},",
    "\"owner\":\{self.owner.json_string()},",
    "\"name\":\{self.name.json_string()},",
    "\"metadata\":\{self.metadata.json_string()},",
    "\"closed\":\{resource_bool_json(self.closed)}",
    "}",
  ].join("")
}

///|
fn ResourceEntry::as_closed(self : ResourceEntry) -> ResourceEntry {
  { ..self, closed: true }
}

///|
pub struct ResourceTable {
  mut next_sequence : Int
  id_prefix : String
  entries : Map[String, ResourceEntry]
  order : Array[String]
}

///|
pub fn ResourceTable::new() -> ResourceTable {
  ResourceTable::with_prefix("resource")
}

///|
pub fn ResourceTable::with_prefix(id_prefix : String) -> ResourceTable {
  let id_prefix = if id_prefix == "" { "resource" } else { id_prefix }
  { next_sequence: 1, id_prefix, entries: {}, order: [] }
}

///|
pub fn ResourceTable::id_prefix(self : ResourceTable) -> String {
  self.id_prefix
}

///|
pub fn ResourceTable::open(
  self : ResourceTable,
  descriptor : ResourceDescriptor,
) -> Result[ResourceEntry, Array[String]] {
  let problems = descriptor.validate()
  if !problems.is_empty() {
    return Err(problems)
  }
  let sequence = self.next_sequence
  self.next_sequence += 1
  let id = "\{self.id_prefix}-\{sequence}"
  let entry = ResourceEntry::new(id~, sequence~, descriptor)
  self.entries[id] = entry
  self.order.push(id)
  Ok(entry)
}

///|
pub fn ResourceTable::get(self : ResourceTable, id : String) -> ResourceEntry? {
  self.entries.get(id)
}

///|
pub fn ResourceTable::handle(
  self : ResourceTable,
  id : String,
) -> ResourceHandle? {
  match self.get(id) {
    Some(entry) => Some(entry.handle())
    None => None
  }
}

///|
pub fn ResourceTable::handles(self : ResourceTable) -> Array[ResourceHandle] {
  self.entries().map(fn(entry) { entry.handle() })
}

///|
pub fn ResourceTable::handles_by_kind(
  self : ResourceTable,
  kind : String,
) -> Array[ResourceHandle] {
  self.entries_by_kind(kind).map(fn(entry) { entry.handle() })
}

///|
pub fn ResourceTable::handles_by_owner(
  self : ResourceTable,
  owner : String,
) -> Array[ResourceHandle] {
  self.entries_by_owner(owner).map(fn(entry) { entry.handle() })
}

///|
pub fn ResourceTable::contains(self : ResourceTable, id : String) -> Bool {
  self.entries.contains(id)
}

///|
pub fn ResourceTable::count(self : ResourceTable) -> Int {
  self.entries.length()
}

///|
pub fn ResourceTable::is_empty(self : ResourceTable) -> Bool {
  self.entries.is_empty()
}

///|
pub fn ResourceTable::ids(self : ResourceTable) -> Array[String] {
  self.order.copy()
}

///|
pub fn ResourceTable::entries(self : ResourceTable) -> Array[ResourceEntry] {
  let entries : Array[ResourceEntry] = []
  for id in self.order {
    match self.entries.get(id) {
      Some(entry) => entries.push(entry)
      None => ()
    }
  }
  entries
}

///|
pub fn ResourceTable::entries_by_kind(
  self : ResourceTable,
  kind : String,
) -> Array[ResourceEntry] {
  self.entries().filter(fn(entry) { entry.kind() == kind })
}

///|
pub fn ResourceTable::entries_by_owner(
  self : ResourceTable,
  owner : String,
) -> Array[ResourceEntry] {
  self.entries().filter(fn(entry) { entry.owner() == owner })
}

///|
pub fn ResourceTable::close(
  self : ResourceTable,
  id : String,
) -> Result[ResourceEntry, String] {
  match self.entries.get(id) {
    Some(entry) => {
      self.entries.remove(id)
      self.remove_ordered_id(id)
      Ok(entry.as_closed())
    }
    None => Err("resource not found: \{id}")
  }
}

///|
pub fn ResourceTable::close_handle(
  self : ResourceTable,
  handle : ResourceHandle,
) -> Result[ResourceEntry, String] {
  let problems = handle.validate()
  if !problems.is_empty() {
    return Err(problems[0])
  }
  match self.get(handle.id()) {
    Some(entry) =>
      if entry.kind() != handle.kind() {
        Err(
          "resource kind mismatch for \{handle.id()}: expected \{handle.kind()} got \{entry.kind()}",
        )
      } else {
        self.close(handle.id())
      }
    None => Err("resource not found: \{handle.id()}")
  }
}

///|
pub fn ResourceTable::close_kind(
  self : ResourceTable,
  kind : String,
) -> Array[ResourceEntry] {
  let entries = self.entries_by_kind(kind)
  self.close_entries(entries)
}

///|
pub fn ResourceTable::close_owner(
  self : ResourceTable,
  owner : String,
) -> Array[ResourceEntry] {
  let entries = self.entries_by_owner(owner)
  self.close_entries(entries)
}

///|
pub fn ResourceTable::close_all(self : ResourceTable) -> Array[ResourceEntry] {
  let entries = self.entries()
  self.close_entries(entries)
}

///|
pub fn ResourceTable::leak_report(self : ResourceTable) -> ResourceLeakReport {
  ResourceLeakReport::new(resources=self.entries())
}

///|
pub fn ResourceTable::cleanup_report(
  self : ResourceTable,
) -> ResourceCleanupReport {
  let closed = self.close_all()
  ResourceCleanupReport::new(closed~, remaining=self.entries())
}

///|
pub fn ResourceTable::to_json(self : ResourceTable) -> String {
  [
    "{",
    "\"idPrefix\":\{self.id_prefix.json_string()},",
    "\"nextSequence\":\{self.next_sequence},",
    "\"resources\":[\{self.entries().map(fn(entry) { entry.to_json() }).join(",")}]",
    "}",
  ].join("")
}

///|
fn ResourceTable::close_entries(
  self : ResourceTable,
  entries : Array[ResourceEntry],
) -> Array[ResourceEntry] {
  let closed : Array[ResourceEntry] = []
  for entry in entries {
    self.entries.remove(entry.id())
    self.remove_ordered_id(entry.id())
    closed.push(entry.as_closed())
  }
  closed
}

///|
fn ResourceTable::remove_ordered_id(self : ResourceTable, id : String) -> Unit {
  match self.order.search(id) {
    Some(index) => ignore(self.order.remove(index))
    None => ()
  }
}

///|
fn resource_bool_json(value : Bool) -> String {
  if value {
    "true"
  } else {
    "false"
  }
}