///|
pub struct AppStateEntry {
  key : String
  kind : String
  value : String
} derive(Debug, Eq)

///|
pub fn AppStateEntry::new(
  key~ : String,
  kind? : String = "",
  value? : String = "",
) -> AppStateEntry {
  { key, kind, value }
}

///|
pub fn AppStateEntry::key(self : AppStateEntry) -> String {
  self.key
}

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

///|
pub fn AppStateEntry::value(self : AppStateEntry) -> String {
  self.value
}

///|
pub fn AppStateEntry::validate(self : AppStateEntry) -> Array[String] {
  let problems : Array[String] = []
  if self.key == "" {
    problems.push("app state key is required")
  }
  problems
}

///|
pub fn AppStateEntry::to_json(self : AppStateEntry) -> String {
  [
    "{",
    "\"key\":\{self.key.json_string()},",
    "\"kind\":\{self.kind.json_string()},",
    "\"value\":\{self.value.json_string()}",
    "}",
  ].join("")
}

///|
pub struct AppStateStore {
  entries : Map[String, AppStateEntry]
  order : Array[String]
}

///|
pub fn AppStateStore::new() -> AppStateStore {
  { entries: {}, order: [] }
}

///|
pub fn AppStateStore::put(
  self : AppStateStore,
  entry : AppStateEntry,
) -> Result[AppStateEntry, Array[String]] {
  let problems = entry.validate()
  if !problems.is_empty() {
    return Err(problems)
  }
  let key = entry.key()
  if !self.entries.contains(key) {
    self.order.push(key)
  }
  self.entries[key] = entry
  Ok(entry)
}

///|
pub fn AppStateStore::set(
  self : AppStateStore,
  key : String,
  kind? : String = "",
  value? : String = "",
) -> Result[AppStateEntry, Array[String]] {
  self.put(AppStateEntry::new(key~, kind~, value~))
}

///|
pub fn AppStateStore::get(self : AppStateStore, key : String) -> AppStateEntry? {
  self.entries.get(key)
}

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

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

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

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

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

///|
pub fn AppStateStore::remove(
  self : AppStateStore,
  key : String,
) -> Result[AppStateEntry, String] {
  match self.entries.get(key) {
    Some(entry) => {
      self.entries.remove(key)
      self.remove_ordered_key(key)
      Ok(entry)
    }
    None => Err("app state not found: \{key}")
  }
}

///|
pub fn AppStateStore::clear(self : AppStateStore) -> Array[AppStateEntry] {
  let entries = self.entries()
  self.entries.clear()
  self.order.clear()
  entries
}

///|
pub fn AppStateStore::to_json(self : AppStateStore) -> String {
  [
    "{",
    "\"entries\":[\{self.entries().map(fn(entry) { entry.to_json() }).join(",")}]",
    "}",
  ].join("")
}

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

///|
pub struct AppManager {
  plan : RuntimePlan
  registry : CommandRegistry
  resources : ResourceTable
  channels : ChannelTable
  events : EventBus
  state : AppStateStore
}

///|
pub fn AppManager::new(
  plan : RuntimePlan,
  registry? : CommandRegistry = CommandRegistry::new(),
) -> AppManager {
  {
    plan,
    registry,
    resources: ResourceTable::new(),
    channels: ChannelTable::new(),
    events: EventBus::new(),
    state: AppStateStore::new(),
  }
}

///|
pub fn App::manager(
  self : App,
  config? : RuntimeConfig = RuntimeConfig::system_webview(),
  registry? : CommandRegistry = CommandRegistry::new(),
) -> Result[AppManager, Array[String]] {
  match self.runtime_plan(config~) {
    Ok(plan) => Ok(AppManager::new(plan, registry~))
    Err(problems) => Err(problems)
  }
}

///|
pub fn AppManager::plan(self : AppManager) -> RuntimePlan {
  self.plan
}

///|
pub fn AppManager::registry(self : AppManager) -> CommandRegistry {
  self.registry
}

///|
pub fn AppManager::command_routes(self : AppManager) -> Array[String] {
  self.plan.command_routes()
}

///|
pub fn AppManager::registered_routes(self : AppManager) -> Array[String] {
  self.registry.routes()
}

///|
pub fn AppManager::audit(self : AppManager) -> RuntimeAudit {
  self.plan.audit_with_registered_routes(self.registered_routes())
}

///|
pub fn AppManager::window_count(self : AppManager) -> Int {
  self.plan.window_count()
}

///|
pub fn AppManager::plugin_count(self : AppManager) -> Int {
  self.plan.plugin_count()
}

///|
pub fn AppManager::capability_count(self : AppManager) -> Int {
  self.plan.capability_count()
}

///|
pub fn AppManager::resource_count(self : AppManager) -> Int {
  self.resources.count() + self.channels.count()
}

///|
pub fn AppManager::channel_count(self : AppManager) -> Int {
  self.channels.count()
}

///|
pub fn AppManager::listener_count(self : AppManager) -> Int {
  self.events.listener_count()
}

///|
pub fn AppManager::state_count(self : AppManager) -> Int {
  self.state.count()
}

///|
pub fn AppManager::resources(self : AppManager) -> Array[ResourceEntry] {
  let entries = self.resources.entries()
  for channel in self.channels.resource_entries() {
    entries.push(channel)
  }
  entries
}

///|
pub fn AppManager::resource_leak_report(
  self : AppManager,
) -> ResourceLeakReport {
  ResourceLeakReport::new(resources=self.resources())
}

///|
pub fn AppManager::close_all_resources(
  self : AppManager,
) -> ResourceCleanupReport {
  let closed = self.resources.close_all()
  closed.append(self.channels.close_all())
  ResourceCleanupReport::new(closed~, remaining=self.resources())
}

///|
pub fn AppManager::resource_handles(self : AppManager) -> Array[ResourceHandle] {
  self.resources().map(fn(entry) { entry.handle() })
}

///|
pub fn AppManager::resource_handles_by_kind(
  self : AppManager,
  kind : String,
) -> Array[ResourceHandle] {
  self
  .resources()
  .filter(fn(entry) { entry.kind() == kind })
  .map(fn(entry) { entry.handle() })
}

///|
pub fn AppManager::resource_handles_by_owner(
  self : AppManager,
  owner : String,
) -> Array[ResourceHandle] {
  self
  .resources()
  .filter(fn(entry) { entry.owner() == owner })
  .map(fn(entry) { entry.handle() })
}

///|
pub fn AppManager::resource(self : AppManager, id : String) -> ResourceEntry? {
  match self.resources.get(id) {
    Some(entry) => Some(entry)
    None =>
      match self.channels.get(id) {
        Some(channel) => Some(channel.resource())
        None => None
      }
  }
}

///|
pub fn AppManager::open_resource(
  self : AppManager,
  descriptor : ResourceDescriptor,
) -> Result[ResourceEntry, Array[String]] {
  self.resources.open(descriptor)
}

///|
pub fn AppManager::close_resource(
  self : AppManager,
  id : String,
) -> Result[ResourceEntry, String] {
  if self.channels.contains(id) {
    self.channels.close(id)
  } else {
    self.resources.close(id)
  }
}

///|
pub fn AppManager::close_resource_handle(
  self : AppManager,
  handle : ResourceHandle,
) -> Result[ResourceEntry, String] {
  let problems = handle.validate()
  if !problems.is_empty() {
    return Err(problems[0])
  }
  match self.resource(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_resource(handle.id())
      }
    None => Err("resource not found: \{handle.id()}")
  }
}

///|
pub fn AppManager::open_channel(
  self : AppManager,
  owner? : String = "",
  name? : String = "",
  metadata? : String = "",
) -> Result[Channel, Array[String]] {
  self.channels.open(owner~, name~, metadata~)
}

///|
pub fn AppManager::channel(self : AppManager, id : String) -> Channel? {
  self.channels.get(id)
}

///|
pub fn AppManager::send_channel(
  self : AppManager,
  id : String,
  payload : String,
) -> Result[ChannelMessage, String] {
  self.channels.send(id, payload)
}

///|
pub fn AppManager::fail_channel(
  self : AppManager,
  id : String,
  message : String,
) -> Result[ChannelMessage, String] {
  self.channels.fail(id, message)
}

///|
pub fn AppManager::end_channel(
  self : AppManager,
  id : String,
) -> Result[ChannelMessage, String] {
  self.channels.end(id)
}

///|
pub fn AppManager::cancel_channel(
  self : AppManager,
  id : String,
) -> Result[ChannelMessage, String] {
  self.channels.cancel(id)
}

///|
pub fn AppManager::drain_channel(
  self : AppManager,
  id : String,
) -> Result[Array[ChannelMessage], String] {
  self.channels.drain(id)
}

///|
pub fn AppManager::listen(
  self : AppManager,
  name : String,
  target? : EventTarget = AppTarget,
  once? : Bool = false,
) -> Result[EventListener, Array[String]] {
  self.events.listen(name, target~, once~)
}

///|
pub fn AppManager::once(
  self : AppManager,
  name : String,
  target? : EventTarget = AppTarget,
) -> Result[EventListener, Array[String]] {
  self.events.once(name, target~)
}

///|
pub fn AppManager::emit(
  self : AppManager,
  event : Event,
  target? : EventTarget = AppTarget,
) -> Result[Array[EventDelivery], Array[String]] {
  self.events.emit(event, target~)
}

///|
pub fn AppManager::unlisten(
  self : AppManager,
  id : String,
) -> Result[EventListener, String] {
  self.events.unlisten(id)
}

///|
pub fn AppManager::manage_state(
  self : AppManager,
  key : String,
  kind? : String = "",
  value? : String = "",
) -> Result[AppStateEntry, Array[String]] {
  self.state.set(key, kind~, value~)
}

///|
pub fn AppManager::state(self : AppManager, key : String) -> AppStateEntry? {
  self.state.get(key)
}

///|
pub fn AppManager::remove_state(
  self : AppManager,
  key : String,
) -> Result[AppStateEntry, String] {
  self.state.remove(key)
}

///|
pub fn AppManager::dispatch(
  self : AppManager,
  request : InvokeRequest,
) -> InvokeResponse {
  match self.dispatch_runtime_control(request) {
    Some(response) => response
    None =>
      self.registry.dispatch_with_profile_channels(
        self.security_profile(),
        request,
        self.channels,
      )
  }
}

///|
pub async fn AppManager::dispatch_async(
  self : AppManager,
  request : InvokeRequest,
) -> InvokeResponse {
  match self.dispatch_runtime_control(request) {
    Some(response) => response
    None =>
      self.registry.dispatch_with_profile_channels_async(
        self.security_profile(),
        request,
        self.channels,
      )
  }
}

///|
pub fn AppManager::to_json(self : AppManager) -> String {
  [
    "{",
    "\"backend\":\{self.plan.backend().name().json_string()},",
    "\"assetProtocol\":\{self.plan.asset_protocol().json_string()},",
    "\"windows\":\{self.window_count()},",
    "\"plugins\":\{self.plugin_count()},",
    "\"capabilities\":\{self.capability_count()},",
    "\"commands\":[\{self.command_routes().map(fn(route) { route.json_string() }).join(",")}],",
    "\"registeredCommands\":[\{self.registered_routes().map(fn(route) { route.json_string() }).join(",")}],",
    "\"invokeContractReport\":\{self.invoke_contract_report().to_json()},",
    "\"resources\":[\{self.resources().map(fn(entry) { entry.to_json() }).join(",")}],",
    "\"resourceLeaks\":\{self.resource_leak_report().to_json()},",
    "\"channels\":\{self.channels.to_json()},",
    "\"events\":\{self.events.to_json()},",
    "\"state\":\{self.state.to_json()}",
    "}",
  ].join("")
}