///|
priv trait Storage {
  fn set_item(Self, String, String) -> Unit
  fn get_item(Self, String) -> String?
  fn remove_item(Self, String) -> Unit
}

///|
#external
priv type LocalStorage

///|
extern "js" fn LocalStorage::get_instance() -> LocalStorage =
  #|() => window.localStorage

///|
extern "js" fn LocalStorage::set_item_ffi(
  self : LocalStorage,
  id : String,
  val : String,
) -> Unit =
  #|(self, id, val) => self.setItem(id, val)

///|
impl Storage for LocalStorage with fn set_item(self, id, val) {
  self.set_item_ffi(id, val)
}

///|
extern "js" fn LocalStorage::get_item_ffi(
  self : LocalStorage,
  id : String,
) -> Nullable[String] =
  #|(self, id) => self.getItem(id)

///|
impl Storage for LocalStorage with fn get_item(self, id) {
  self.get_item_ffi(id).to_option()
}

///|
extern "js" fn LocalStorage::remove_item_ffi(
  self : LocalStorage,
  id : String,
) -> Unit =
  #|(self, id) => self.removeItem(id)

///|
impl Storage for LocalStorage with fn remove_item(self, id) {
  self.remove_item_ffi(id)
}

///|
priv struct FakeStorage {
  data : Map[String, String]
}

///|
fn FakeStorage::new() -> FakeStorage {
  { data: {}, }
}

///|
impl Storage for FakeStorage with fn set_item(self, id, val) {
  self.data[id] = val
}

///|
impl Storage for FakeStorage with fn get_item(self, id) {
  self.data.get(id)
}

///|
impl Storage for FakeStorage with fn remove_item(self, id) {
  self.data.remove(id)
}

///|
struct LocalStorageManager {
  best_score_key : String
  game_states_stack_key : String
  storage : &Storage
}

///|
extern "js" fn local_storage_supported() -> Bool =
  #|() => {
  #|  var testKey = "test";
  #|
  #|  try {
  #|    var storage = window.localStorage;
  #|    storage.setItem(testKey, "1");
  #|    storage.removeItem(testKey);
  #|    return true;
  #|  } catch (error) {
  #|    return false;
  #|  }
  #|}

///|
pub fn LocalStorageManager::new() -> LocalStorageManager {
  {
    best_score_key: "bestScore",
    game_states_stack_key: "gameStatesStack",
    storage: if local_storage_supported() {
      LocalStorage::get_instance() as &Storage
    } else {
      FakeStorage::new()
    },
  }
}

///|
pub fn LocalStorageManager::get_best_score(self : LocalStorageManager) -> Int {
  (self.storage.get_item(self.best_score_key).unwrap_or("0")
  |> @string.parse_int) catch {
    _ => 0
  }
}

///|
pub fn LocalStorageManager::set_best_score(
  self : LocalStorageManager,
  score : Int,
) -> Unit {
  self.storage.set_item(self.best_score_key, score.to_string())
}

///|
fn LocalStorageManager::get_game_states_stack(
  self : LocalStorageManager,
) -> Array[GameState] {
  let game_states_stack_json = match
    self.storage.get_item(self.game_states_stack_key) {
    Some(json) => json
    None => return []
  }
  (game_states_stack_json |> @json.parse |> @json.from_json) catch {
    _ => []
  }
}

///|
pub fn LocalStorageManager::get_game_state(
  self : LocalStorageManager,
) -> GameState? {
  self.get_game_states_stack().last()
}

///|
pub fn LocalStorageManager::set_game_state(
  self : LocalStorageManager,
  game_state : GameState,
) -> Unit {
  self.storage.set_item(
    self.game_states_stack_key,
    (self.get_game_states_stack() + [game_state]).to_json().stringify(),
  )
}

///|
pub fn LocalStorageManager::clear_game_state(
  self : LocalStorageManager,
) -> Unit {
  self.storage.remove_item(self.game_states_stack_key)
}

///|
pub fn LocalStorageManager::pop_game_state(
  self : LocalStorageManager,
) -> GameState? {
  match self.get_game_states_stack() {
    [] => None
    [state] => Some(state)
    [.. init, prev, _curr] => {
      self.storage.set_item(
        self.game_states_stack_key,
        init.to_json().stringify(),
      )
      Some(prev)
    }
  }
}

///|
pub fn LocalStorageManager::push_game_state(
  self : LocalStorageManager,
  state : GameState,
) -> Unit {
  let game_states_stack = self.get_game_states_stack()
  if game_states_stack.length() > 50 {
    game_states_stack.remove(0) |> ignore
  }
  game_states_stack.push(state)
  self.storage.set_item(
    self.game_states_stack_key,
    game_states_stack.to_json().stringify(),
  )
}

///|
pub fn LocalStorageManager::get_length_of_game_states_stack(
  self : LocalStorageManager,
) -> Int {
  self.get_game_states_stack().length()
}