import { writable, type Writable, type Readable, derived } from "svelte/store";

export function localStore<T>(key: string, defaultValue: T, initialConverter: (value: string) => T, stringifier: (value: T) => string): Writable<T> {
  let baseWritable = writable(defaultValue);
  if (globalThis.localStorage) {
    let value = localStorage.getItem(key);
    if (value !== null) baseWritable.set(initialConverter(value));
    else baseWritable.set(defaultValue);
  }

  return {
    set(value) {
      if (globalThis.localStorage)
        localStorage.setItem(key, stringifier(value));
      return baseWritable.set(value);
    },
    subscribe(run, invalidate) {
      return baseWritable.subscribe(run, invalidate);
    },
    update(updater) {
      baseWritable.update((value) => {
        const newValue = updater(value);
        if (globalThis.localStorage)
          localStorage.setItem(key, stringifier(newValue));
        return newValue;
      })
    }
  }
}
export function identity<T>(value: T) { return value; }


/** One or more `Readable`s. */
type Stores = Readable<any> | [Readable<any>, ...Array<Readable<any>>] | Array<Readable<any>>;

/** One or more values from `Readable` stores. */
type StoresValues<T> =
  T extends Readable<infer U> ? U : { [K in keyof T]: T[K] extends Readable<infer U> ? U : never };
export type Cached<T> = Readable<T> & { v: T };

/**
 * Caches the value of a store for later access.
 */
export function cached<S extends Stores, T>(stores: S, updater: (values: StoresValues<S>) => T) {
  let store: Cached<T> = derived(stores, updater) as any;
  store.subscribe((value) => store.v = value);
  return store
}

export type CachedWritable<T> = Writable<T> & { get(): T };

export function cachedWritable<T>(initial: T): CachedWritable<T> {
  const store = writable(initial);
  let current = initial;
  return {
    get() {
      return current;
    },
    set(value) {
      current = value;
      store.set(value)
    },
    update(updater) {
      store.update((value) => {
        const newValue = updater(value);
        current = newValue;
        return newValue;
      });
    },
    subscribe: store.subscribe
  }
}
