import { R } from '../../libs';
import {
  ILocalStorageOptions,
  ILocalStorageValue,
  StorageValueType,
  LocalStorageProp,
} from './types';
import { init as initStore } from './store.web';
import { subject$ } from './events.rx';

const store = initStore();

function asTypeName(value: any): StorageValueType {
  if (value === null) {
    return 'null';
  }
  if (R.is(Boolean, value)) {
    return 'bool';
  }
  if (R.is(String, value)) {
    return 'string';
  }
  if (R.is(Number, value)) {
    return 'number';
  }
  if (R.is(Boolean, value)) {
    return 'bool';
  }
  if (R.is(Date, value)) {
    return 'date';
  }
  return 'object';
}

function toValue(key: string, json?: string | null): any {
  try {
    return parse(json);
  } catch (error) {
    throw new Error(
      `Failed to read '${key}' from storage. The value '${json}' cannot be parsed.`,
    );
  }
}

export function parse(json?: string | null) {
  if (!json) {
    return json;
  }

  // Convert the JSON string to an object.
  const item = JSON.parse(json) as ILocalStorageValue;

  // Perform type conversions.
  switch (item.type) {
    case 'null':
    case 'bool':
    case 'string':
      return item.value;
    case 'number':
      return parseFloat(item.value);
    case 'date':
      return new Date(item.value);
    case 'object':
      return item.value;
    default: // Ignore.
  }
}

/**
 * Asynchronous read/write interface to the store.
 *
 *    READ:  Pass nothing (undefined) to read the value for the `key` parameter.
 *    WRITE: Pass a `value` to write.
 *    CLEAR: Pass null to `value` to clear.
 */
export function prop<T>(
  key: string,
  defaultOptions: ILocalStorageOptions = {},
): LocalStorageProp<T> {
  async function func<T>(value?: T | null, options: ILocalStorageOptions = {}) {
    const args = { ...defaultOptions, ...options };
    let result: any = value;

    const fireEvent = () => {
      subject$.next({
        key,
        value,
        type: asTypeName(value),
      });
    };

    if (value === null) {
      // CLEAR.
      await store.removeItem(key);
      fireEvent();
    } else if (value !== undefined) {
      // WRITE.
      const storageValue: ILocalStorageValue = {
        value,
        type: asTypeName(value),
      };
      await store.setItem(key, JSON.stringify(storageValue));
      fireEvent();
    } else {
      // READ.
      const json = await store.getItem(key);
      result = json ? toValue(key, json) : undefined;
    }

    // Finish up.
    return result === undefined ? args.default : (result as T);
  }

  // Finish up.
  const result = func as LocalStorageProp<T>;
  result.key = key;
  result.isProp = true;
  return result;
}
