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

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

///|
pub(all) 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 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: [] }
}

///|
pub fn[T] SaveableStateStore::get(
  self : SaveableStateStore,
  key : String,
  codec : SaveableCodec[T],
) -> T? {
  match self.get_string(key) {
    Some(value) => (codec.decode)(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))
}

///|
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 BuildContext::save_string(
  self : BuildContext,
  key~ : String,
  value : String,
) -> Unit {
  self.runtime.saveable_state.set_string(self.scoped_saveable_key(key), value)
}

///|
pub fn BuildContext::restore_string(
  self : BuildContext,
  key~ : String,
) -> String? {
  self.runtime.saveable_state.get_string(self.scoped_saveable_key(key))
}

///|
pub fn BuildContext::save_bool(
  self : BuildContext,
  key~ : String,
  value : Bool,
) -> Unit {
  self.runtime.saveable_state.set_bool(self.scoped_saveable_key(key), value)
}

///|
pub fn BuildContext::restore_bool(self : BuildContext, key~ : String) -> Bool? {
  self.runtime.saveable_state.get_bool(self.scoped_saveable_key(key))
}

///|
pub fn BuildContext::save_int(
  self : BuildContext,
  key~ : String,
  value : Int,
) -> Unit {
  self.runtime.saveable_state.set_int(self.scoped_saveable_key(key), value)
}

///|
pub fn BuildContext::restore_int(self : BuildContext, key~ : String) -> Int? {
  self.runtime.saveable_state.get_int(self.scoped_saveable_key(key))
}

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

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

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

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

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

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

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