///|
struct SaveableCodec[T] {
  encode : (T) -> String
  decode : (String) -> T?
}

///|
struct SaveableStateStore {
  mut strings : Array[SaveableStringEntry]
}

///|
struct SaveableStateSnapshot {
  entries : Array[SaveableStringEntry]
} derive(Eq, Debug, ToJson)

///|
priv struct SaveableStringEntry {
  key : String
  mut value : String
} derive(Eq, Debug, ToJson)

///|
pub fn[T] SaveableCodec::new(
  encode~ : (T) -> String,
  decode~ : (String) -> T?,
) -> SaveableCodec[T] {
  { encode, decode }
}

///|
pub fn[T] SaveableCodec::encode_value(
  self : SaveableCodec[T],
  value : T,
) -> String {
  (self.encode)(value)
}

///|
pub fn[T] SaveableCodec::decode_value(
  self : SaveableCodec[T],
  payload : String,
) -> T? {
  (self.decode)(payload)
}

///|
pub fn SaveableCodec::string() -> SaveableCodec[String] {
  SaveableCodec::new(encode=value => value, decode=value => Some(value))
}

///|
pub fn SaveableCodec::bool() -> SaveableCodec[Bool] {
  SaveableCodec::new(encode=value => if value { "true" } else { "false" }, decode=value => {
    match value {
      "true" => Some(true)
      "false" => Some(false)
      _ => None
    }
  })
}

///|
pub fn SaveableCodec::int() -> SaveableCodec[Int] {
  SaveableCodec::new(
    encode=value => value.to_string(),
    decode=parse_saveable_int,
  )
}

///|
pub fn SaveableStateStore::new() -> SaveableStateStore {
  { strings: [] }
}

///|
fn SaveableStateSnapshot::new(
  entries? : Array[SaveableStringEntry] = [],
) -> SaveableStateSnapshot {
  { entries, }
}

///|
pub fn SaveableStateStore::snapshot(
  self : SaveableStateStore,
) -> SaveableStateSnapshot {
  SaveableStateSnapshot::new(
    entries=self.strings.map(entry => { key: entry.key, value: entry.value }),
  )
}

///|
pub fn SaveableStateStore::restore(
  self : SaveableStateStore,
  snapshot : SaveableStateSnapshot,
) -> Unit {
  self.strings = snapshot.entries.map(entry => {
    key: entry.key,
    value: entry.value,
  })
}

///|
pub fn[T] SaveableStateStore::get(
  self : SaveableStateStore,
  key : String,
  codec : SaveableCodec[T],
) -> T? {
  match self.get_string(key) {
    Some(value) => codec.decode_value(value)
    None => None
  }
}

///|
pub fn[T] SaveableStateStore::set(
  self : SaveableStateStore,
  key : String,
  value : T,
  codec : SaveableCodec[T],
) -> Unit {
  self.set_string(key, codec.encode_value(value))
}

///|
pub fn SaveableStateStore::get_string(
  self : SaveableStateStore,
  key : String,
) -> String? {
  for entry in self.strings {
    if entry.key == key {
      return Some(entry.value)
    }
  }
  None
}

///|
pub fn SaveableStateStore::set_string(
  self : SaveableStateStore,
  key : String,
  value : String,
) -> Unit {
  for entry in self.strings {
    if entry.key == key {
      entry.value = value
      return
    }
  }
  self.strings.push({ key, value })
}

///|
pub fn SaveableStateStore::get_bool(
  self : SaveableStateStore,
  key : String,
) -> Bool? {
  self.get(key, SaveableCodec::bool())
}

///|
pub fn SaveableStateStore::set_bool(
  self : SaveableStateStore,
  key : String,
  value : Bool,
) -> Unit {
  self.set(key, value, SaveableCodec::bool())
}

///|
pub fn SaveableStateStore::get_int(
  self : SaveableStateStore,
  key : String,
) -> Int? {
  self.get(key, SaveableCodec::int())
}

///|
pub fn SaveableStateStore::set_int(
  self : SaveableStateStore,
  key : String,
  value : Int,
) -> Unit {
  self.set(key, value, SaveableCodec::int())
}

///|
pub fn SaveableStateStore::remove(
  self : SaveableStateStore,
  key : String,
) -> Unit {
  self.strings = self.strings.filter(entry => entry.key != key)
}

///|
fn parse_saveable_int(value : String) -> Int? {
  let chars = value.to_array()
  if chars.length() == 0 {
    return None
  }
  let mut index = 0
  let mut sign = 1
  if chars[index] == '-' {
    sign = -1
    index = index + 1
  }
  if index >= chars.length() {
    return None
  }
  let mut parsed = 0
  while index < chars.length() {
    let char = chars[index]
    if !char.is_ascii_digit() {
      return None
    }
    parsed = parsed * 10 + char.to_int() - '0'.to_int()
    index = index + 1
  }
  Some(parsed * sign)
}

///|
pub fn ComponentContext::save_string(
  self : ComponentContext,
  key~ : String,
  value : String,
) -> Unit {
  self.save(key~, value, SaveableCodec::string())
}

///|
pub fn ComponentContext::restore_string(
  self : ComponentContext,
  key~ : String,
) -> String? {
  self.restore(key~, SaveableCodec::string())
}

///|
pub fn ComponentContext::save_bool(
  self : ComponentContext,
  key~ : String,
  value : Bool,
) -> Unit {
  self.save(key~, value, SaveableCodec::bool())
}

///|
pub fn ComponentContext::restore_bool(
  self : ComponentContext,
  key~ : String,
) -> Bool? {
  self.restore(key~, SaveableCodec::bool())
}

///|
pub fn ComponentContext::save_int(
  self : ComponentContext,
  key~ : String,
  value : Int,
) -> Unit {
  self.save(key~, value, SaveableCodec::int())
}

///|
pub fn ComponentContext::restore_int(
  self : ComponentContext,
  key~ : String,
) -> Int? {
  self.restore(key~, SaveableCodec::int())
}

///|
pub fn[T] ComponentContext::save(
  self : ComponentContext,
  key~ : String,
  value : T,
  codec : SaveableCodec[T],
) -> Unit {
  self.saveable_state.set(self.scoped_saveable_key(key), value, codec)
}

///|
pub fn[T] ComponentContext::restore(
  self : ComponentContext,
  key~ : String,
  codec : SaveableCodec[T],
) -> T? {
  self.saveable_state.get(self.scoped_saveable_key(key), codec)
}

///|
pub fn[T] ComponentContext::saveable(
  self : ComponentContext,
  key~ : String,
  initial : T,
  codec : SaveableCodec[T],
) -> State[T] {
  let scoped = self.scoped_saveable_key(key)
  let restored = self.saveable_state.get(scoped, codec).unwrap_or(initial)
  let state = State::new(restored)
  let store = self.saveable_state
  let request_rebuild = self.request_component_rebuild
  let component_id = self.component_id
  self.subscriptions.push(
    state.subscribe(() => {
      store.set(scoped, state.get(), codec)
      request_rebuild(component_id)
    }),
  )
  store.set(scoped, state.get(), codec)
  state
}

///|
pub fn ComponentContext::saveable_string(
  self : ComponentContext,
  key~ : String,
  initial : String,
) -> State[String] {
  self.saveable(key~, initial, SaveableCodec::string())
}

///|
pub fn ComponentContext::saveable_bool(
  self : ComponentContext,
  key~ : String,
  initial : Bool,
) -> State[Bool] {
  self.saveable(key~, initial, SaveableCodec::bool())
}

///|
pub fn ComponentContext::saveable_int(
  self : ComponentContext,
  key~ : String,
  initial : Int,
) -> State[Int] {
  self.saveable(key~, initial, SaveableCodec::int())
}

///|
fn ComponentContext::scoped_saveable_key(
  self : ComponentContext,
  key : String,
) -> String {
  "\{self.component_id.value}:\{key}"
}