import { uppercase, camelToUnderscore } from './helper';

/**
|--------------------------------------------------
| Types
|--------------------------------------------------
*/

export interface ActionRequest<Payload> {
  type: string;
  payload: Payload;
}

export interface AsyncActionResult<Payload, Request> {
  type: string;
  status: string;
  payload: PayloadOut;
  requestData: PayloadIn;
}

export type StoreAction<Payload, Request> = ActionRequest<Payload> & AsyncActionResult<Payload, Request>;

export interface SyncRequestAction<Payload> {
  (payload?: Payload): ActionRequest<Payload>;
}

export interface SyncRequestStructure<Payload> {
  action: SyncRequestAction<Payload>;
  constant: string;
}

export interface AsyncResponseAction<Payload, Request> {
  (payload?: Payload, status?: string, requestData?: Request): AsyncActionResult<Payload>;
}
export interface AsyncResponseStructure<Payload, Request> {
  action: AsyncResponseAction<Payload, Request>;
  constant: string;
}

export interface AsyncActionStructure<In, Out> {
  request: SyncRequestStructure<In>;
  result: AsyncResponseStructure<Out, In>;
  set: SyncRequestStructure<Out>;
  reset?: SyncRequestStructure<void>;
}

export interface ResourceStructure<ListIn, ListOut, DetailIn, DetailOut> {
  list: AsyncActionStructure<ListIn, ListOut>;
  listMore: AsyncActionStructure<ListIn, ListOut>;
  listPage?: AsyncActionStructure<ListIn, ListOut>;
  search: AsyncActionStructure<ListIn, ListOut>;
  add: AsyncActionStructure<DetailIn, DetailOut>;
  detail: AsyncActionStructure<DetailIn, DetailOut>;
  update: AsyncActionStructure<DetailIn, DetailOut>;
  delete: AsyncActionStructure<DetailIn, DetailOut>;
}

/**
|--------------------------------------------------
| Methods
|--------------------------------------------------
*/

// Creation of an action structure
const createActionStructure = <Payload>(str: string): SyncRequestStructure<Payload> => {
  str = uppercase(str);

  return {
    action: (payload: Payload): SyncRequesTStructure<Payload> => ({
      type: str,
      payload,
    }),
    constant: str,
  };
};

// Creation of a response structure
const createResponseStructure = <Payload, Request>(str: string): AsyncResponseStructure<Payload, Request> => {
  str = uppercase(str);

  return {
    action: (payload: Payload, status: string, requestData?: Request): AsyncActionResult<Payload, Request> => ({
      type: str,
      payload,
      requestData,
      status,
    }),
    constant: str,
  };
};

// Creation of an async structure
const createAsyncStructure = <In, Out>(str: string, reset?: boolean): AsyncActionStructure<In, Out> => {
  if (str.indexOf('_') < 0) str = camelToUnderscore(str);
  const returnObj = {
    request: createActionStructure(`${str}_request`),
    result: createResponseStructure(`${str}_result`),
    set: createActionStructure(`${str}_set`),
  };
  if (reset) {
    returnObj.reset = createActionStructure(`${str}_reset`);
  }
  return returnObj;
};

// Creation of a resource structure
const createResourceStructure = <ListIn, ListOut, DetailIn, DetailOut>(str: string): ResourceStructure<ListIn, ListOut, DetailIn, DetailOut> => {
  if (str.indexOf('_') < 0) str = camelToUnderscore(str);
  return {
    list: createAsyncStructure(`${str}_list`, true),
    listMore: createAsyncStructure(`${str}_list_more`),
    listPage: createAsyncStructure(`${str}_list_page`),
    search: createAsyncStructure(`${str}_search`, true),
    detail: createAsyncStructure(`${str}_detail`, true),
    add: createAsyncStructure(`${str}_add`),
    update: createAsyncStructure(`${str}_update`),
    delete: createAsyncStructure(`${str}_delete`),
  };
};

const syncAction = (dispatch, action) => (data, status, requestData) => {
  if (typeof action === 'object') action = action?.request?.action || action?.action;
  if (typeof action !== 'function') {
    console.error('No action provided', action);
    return;
  }
  dispatch(action(data, status, requestData));
};

const asyncAction = (dispatch, action) => (data, status, requestData) => {
  return new Promise((resolve, reject) => {
    data = data || {};
    data.resolve = resolve;
    data.reject = reject;
    syncAction(dispatch, action)(data, status, requestData);
  });
};

export { createActionStructure, createAsyncStructure, createResourceStructure, syncAction, asyncAction };
