import { IStorage } from "./IStorage";
import { AsyncReturnType } from "./Types";

type TPersistLayerParams = {
  storage?: IStorage;
  noCache?: boolean;
}

function wrapValue(val: any) {
  if (val) {
    return JSON.stringify(val);
  }

  return '__null';
}

function unwrapValue(val: any) {
  if (val === '__null') {
    return null;
  }

  try {
    return JSON.parse(val);
  } catch (e) {
    return null;
  }
}

export class PersistLayer {
  private readonly storage?: IStorage;
  private readonly noCache: boolean;
  private inMemory?: Record<string, string> = {};

  constructor(params: TPersistLayerParams) {
    this.storage = params.storage;
    this.noCache = params.noCache ?? false;
    this.sync = this.sync.bind(this);
    this.wrap = this.wrap.bind(this);
  }

  sync<T>(cacheKey: string): T {
    const inMemory = this.inMemory[cacheKey];
    if (!this.noCache && inMemory) {
      return unwrapValue(inMemory);
    }

    const inStorage = this.storage?.getItem(cacheKey)
    if (!this.noCache && inStorage) {
      this.inMemory[cacheKey] = inStorage;
      return unwrapValue(inStorage);
    }

    return null;
  }

  async wrap<T extends (...any: any) => Promise<any>>(cacheKey: string, call: T): Promise<AsyncReturnType<T>> {
    const value = this.sync<AsyncReturnType<T>>(cacheKey);
    if (value) {
      return value;
    }

    const fetched = await call();
    const valueToSave = wrapValue(fetched);
    this.inMemory[cacheKey] = valueToSave;
    this.storage?.setItem(cacheKey, valueToSave);
    return unwrapValue(valueToSave);
  }
}
