import * as Cookie from 'js-cookie';
import { isJson } from '../value/value';
import {
  ICookieOptions,
  CookieValue,
  CookieProperty,
  ICookieChangedEvent,
} from './types';
import { subject$ } from './events.rx';

/**
 * Creates a wrapper for a cookie property at a specific key.
 */
export function prop<T extends CookieValue>(
  key: string,
  defaultOptions: ICookieOptions = {},
): CookieProperty<T> {
  const func = <T extends CookieValue>(
    value?: T | null,
    options: ICookieOptions = {},
  ) => {
    const args = { ...defaultOptions, ...options };
    return value === undefined
      ? getValue<T>(key, args)
      : setValue<T>(key, value, args);
  };
  const result = func as CookieProperty<T>;
  result.key = key;
  result.isProp = true;
  result.options = defaultOptions;
  return result;
}

/**
 * INTERNAL
 */
function getValue<T extends CookieValue>(key: string, options: ICookieOptions) {
  // Retrieve the value from the client-side object,
  // or the given server-side cookies.
  const { ctx } = options;
  const cookies = (ctx && ctx.req && ctx.req.cookies) || Cookie.getJSON() || {};
  let value = cookies[key];

  // TODO:BUG
  //    If the key contains a `/` characther this will fail because the paths is encoded.
  //    Account for URL encoding in the cookie keys.

  if (key.includes('/')) {
    throw new Error(
      `Cookie keys cannot contain "/" character. Given key: "${key}".`,
    );
  }

  // Optionally parse as JSON.
  if (isJson(value)) {
    try {
      value = JSON.parse(value as string);
    } catch (error) {
      // Ignore parse error - just return the raw value.
    }
  }

  // Update with default value if no value was found.
  if (value === undefined && options.default !== undefined) {
    value = options.default;
  }

  // Finish up.
  return value as T;
}

function setValue<T extends CookieValue>(
  key: string,
  value: T | null,
  options: ICookieOptions,
) {
  const fireEvent = (
    action: ICookieChangedEvent['action'],
    value?: CookieValue,
  ) => {
    subject$.next({ key, value, action });
    return value;
  };

  if (value === null) {
    Cookie.remove(key);
    return fireEvent('DELETE');
  } else {
    const { expires, path, domain, isSecure: secure } = options;
    Cookie.set(key, value, { expires, path, domain, secure });
    return fireEvent('UPDATE', getValue<T>(key, options));
  }
}
