import {
  CHOICE_WIDGET_KIND,
  IRewardTransaction,
  IWidgetChoiceItem,
  IWidgetEarnableReward,
  IWidgetInteraction,
  IWidgetOptionItem,
  IWidgetPayload,
  MULTI_INTERACTION_WIDGET_KINDS,
  OPTION_WIDGET_KIND,
  SINGLE_INTERACTION_WIDGET_KINDS,
  WidgetKind,
} from '@livelike/javascript';
import { WidgetResultState, WidgetUIPhase } from '../types';
import { createStore } from './store';

export type WidgetState = {
  widgetPayload: IWidgetPayload;
  widgetUIPhase?: WidgetUIPhase;
  widgetInteractions?: IWidgetInteraction[];
  selectedOptionIndex?: number;
  widgetResultState?: WidgetResultState;
  widgetRewards?: IWidgetEarnableReward[];
  isTimelineWidget?: boolean;
};

export type WidgetStoreValue = Record<string, WidgetState>;

const initialWidgetStoreValue: WidgetStoreValue = {};

export const widgetStore = createStore(initialWidgetStoreValue);

export type BaseWidgetActionArgs = {
  widgetId: string;
};

export type UpdateWidgetPhaseActionArgs = BaseWidgetActionArgs & {
  widgetUIPhase: WidgetUIPhase;
};

export type UpdateWidgetChoicesActionArgs = BaseWidgetActionArgs & {
  widgetChoices: Pick<IWidgetChoiceItem, 'id' | 'answer_count'>[];
};

export type UpdateWidgetOptionsActionArgs = BaseWidgetActionArgs & {
  widgetOptions: Pick<IWidgetOptionItem, 'id' | 'vote_count'>[];
};

export type UpdateWidgetInteractionActionArgs = BaseWidgetActionArgs & {
  widgetInteractions: IWidgetInteraction[];
};

export type UpdateSelectedOptionIndexActionArgs = BaseWidgetActionArgs & {
  selectedOptionIndex: number;
};

export type UpdateWidgetResultStateActionArgs = BaseWidgetActionArgs & {
  widgetResultState: WidgetResultState;
};

export type UpdateWidgetRewardsActionArgs = BaseWidgetActionArgs & {
  widgetRewards: IWidgetEarnableReward[];
};

export type UpdateWidgetStateActionArgs = BaseWidgetActionArgs & {
  widgetState: Partial<WidgetState>;
};

export type UpdateWidgetAverageMagnitudeArgs = BaseWidgetActionArgs & {
  averageMagnitude: string;
};

export const widgetStoreActions = {
  updateWidgetStateAction({
    widgetId,
    widgetState,
  }: UpdateWidgetStateActionArgs) {
    const prevWidgetState = widgetStore.get()[widgetId];
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...prevWidgetState,
        ...widgetState,
      },
    });
  },
  updateWidgetChoicesAction({
    widgetId,
    widgetChoices,
  }: UpdateWidgetChoicesActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget choices, widget not found for widgetId=${widgetId}`
      );
    }
    // check if there's any choice change
    const changedChoices = widgetState.widgetPayload.choices.filter(
      (choice, index) => {
        return Object.entries(widgetChoices[index]).some(
          ([key, value]) => choice[key] !== widgetChoices[key]
        );
      }
    );
    if (!changedChoices.length) {
      return;
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetPayload: {
          ...widgetState.widgetPayload,
          choices: widgetChoices.map((choice, index) => ({
            ...widgetState.widgetPayload?.choices?.[index],
            ...choice,
          })),
        },
      },
    });
  },
  updateWidgetOptionsAction({
    widgetId,
    widgetOptions,
  }: UpdateWidgetOptionsActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget options, widget not found for widgetId=${widgetId}`
      );
    }
    // check if there's any option vote change
    const changedOptions = widgetState.widgetPayload.options.filter(
      (option, index) => {
        return Object.entries(widgetOptions[index]).some(
          ([key, value]) => option[key] !== widgetOptions[index][key]
        );
      }
    );
    if (!changedOptions.length) {
      return;
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetPayload: {
          ...widgetState.widgetPayload,
          options: widgetOptions.map((option, index) => ({
            ...widgetState.widgetPayload?.options?.[index],
            ...option,
          })),
        },
      },
    });
  },
  updateWidgetUIPhaseAction({
    widgetId,
    widgetUIPhase,
  }: UpdateWidgetPhaseActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error('Error while updating widget phase, Widget Id not found');
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetUIPhase,
      },
    });
  },
  updateWidgetInteractionAction({
    widgetId,
    widgetInteractions,
  }: UpdateWidgetInteractionActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget interaction, widget state not found for widgetId=${widgetId}`
      );
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetInteractions,
        widgetResultState: getWidgetResultState({
          ...widgetState,
          widgetInteractions,
        }),
        widgetUIPhase: getWidgetUIPhaseOnInteractionUpdate(widgetState),
      },
    });
  },
  updateSelectedOptionIndexAction({
    widgetId,
    selectedOptionIndex,
  }: UpdateSelectedOptionIndexActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget selected option, widget state not found for widgetId=${widgetId}`
      );
    }
    const { widgetPayload, selectedOptionIndex: prevSelectedOptionIndex } =
      widgetState;
    const { options, choices, kind } = widgetPayload;
    const isChoiceWidgetKind = CHOICE_WIDGET_KIND.includes(kind);
    const updatedChoicesOrOptions = isChoiceWidgetKind
      ? getUpdatedChoices({
          choices,
          selectedOptionIndex,
          prevSelectedOptionIndex,
        })
      : getUpdatedOptions({
          options,
          selectedOptionIndex,
          prevSelectedOptionIndex,
        });
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetPayload: {
          ...widgetState.widgetPayload,
          [isChoiceWidgetKind ? 'choices' : 'options']: updatedChoicesOrOptions,
        },
        selectedOptionIndex,
      },
    });
  },
  updateWidgetResultStateAction({
    widgetId,
    widgetResultState,
  }: UpdateWidgetResultStateActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget selected option, widget state not found for widgetId=${widgetId}`
      );
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetResultState,
      },
    });
  },
  updateWidgetRewardsAction({
    widgetId,
    widgetRewards,
  }: UpdateWidgetRewardsActionArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget selected option, widget state not found for widgetId=${widgetId}`
      );
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetRewards,
      },
    });
  },
  updateWidgetAverageMagnitude({
    widgetId,
    averageMagnitude,
  }: UpdateWidgetAverageMagnitudeArgs) {
    const widgetState = widgetStore.get()[widgetId];
    if (!widgetState) {
      throw new Error(
        `Error while updating widget selected option, widget state not found for widgetId=${widgetId}`
      );
    }
    widgetStore.set({
      ...widgetStore.get(),
      [widgetId]: {
        ...widgetState,
        widgetPayload: {
          ...widgetState.widgetPayload,
          average_magnitude: averageMagnitude,
        },
      },
    });
  },
};

function getUpdatedOptions({
  options,
  prevSelectedOptionIndex,
  selectedOptionIndex,
}: {
  options: IWidgetOptionItem[];
  prevSelectedOptionIndex: number;
  selectedOptionIndex: number;
}) {
  let validOptionUpdate = true;
  const updatedOptions = options.map((option, index) => {
    let { vote_count } = option;
    if (index === selectedOptionIndex) {
      vote_count += 1;
    } else if (index === prevSelectedOptionIndex) {
      vote_count -= 1;
    }
    if (vote_count < 0) {
      validOptionUpdate = false;
    }
    return {
      ...option,
      vote_count,
    };
  });
  return validOptionUpdate ? updatedOptions : options;
}

function getUpdatedChoices({
  choices,
  prevSelectedOptionIndex,
  selectedOptionIndex,
}: {
  choices: IWidgetChoiceItem[];
  prevSelectedOptionIndex: number;
  selectedOptionIndex: number;
}) {
  return choices.map((choice, index) => {
    let { answer_count } = choice;
    if (index === selectedOptionIndex) {
      answer_count += 1;
    } else if (index === prevSelectedOptionIndex) {
      answer_count -= 1;
    }
    return {
      ...choice,
      answer_count,
    };
  });
}

export function getWidgetResultState(widgetState: WidgetState) {
  const { widgetPayload, widgetInteractions } = widgetState;
  switch (widgetPayload.kind) {
    case WidgetKind.TEXT_PREDICTION:
    case WidgetKind.IMAGE_PREDICTION:
    case WidgetKind.TEXT_NUMBER_PREDICTION:
    case WidgetKind.IMAGE_NUMBER_PREDICTION: {
      const followUp =
        widgetPayload?.follow_ups?.[widgetPayload?.follow_ups?.length - 1];
      return widgetInteractions?.length &&
        followUp &&
        followUp.status === 'published'
        ? WidgetResultState.SHOWN
        : WidgetResultState.HIDDEN;
    }

    case WidgetKind.TEXT_PREDICTION_FOLLOW_UP:
    case WidgetKind.IMAGE_PREDICTION_FOLLOW_UP:
    case WidgetKind.IMAGE_NUMBER_PREDICTION_FOLLOW_UP:
    case WidgetKind.TEXT_NUMBER_PREDICTION_FOLLOW_UP: {
      return WidgetResultState.SHOWN;
    }

    default: {
      return widgetInteractions?.length
        ? WidgetResultState.SHOWN
        : WidgetResultState.HIDDEN;
    }
  }
}

export function getWidgetUIPhaseOnInteractionUpdate(widgetState: WidgetState) {
  const { widgetPayload } = widgetState;
  if (SINGLE_INTERACTION_WIDGET_KINDS.includes(widgetPayload.kind)) {
    return WidgetUIPhase.SUBMITTED;
  } else if (MULTI_INTERACTION_WIDGET_KINDS.includes(widgetPayload.kind)) {
    return WidgetUIPhase.INTERACTIVE;
  }
  return undefined;
}

export function getWidgeUIPhase(widgetState: WidgetState) {
  const { widgetPayload, widgetInteractions } = widgetState;
  if (
    widgetPayload?.interactive_until &&
    isTimeExpired(widgetPayload?.interactive_until)
  ) {
    return WidgetUIPhase.EXPIRED;
  }
  const followUp =
    widgetPayload?.follow_ups?.[widgetPayload?.follow_ups?.length - 1];
  switch (widgetPayload.kind) {
    case WidgetKind.TEXT_PREDICTION:
    case WidgetKind.IMAGE_PREDICTION: {
      return followUp && followUp.status === 'published'
        ? WidgetUIPhase.FOLLOW_UP_PUBLISHED
        : WidgetUIPhase.INTERACTIVE;
    }

    case WidgetKind.TEXT_NUMBER_PREDICTION:
    case WidgetKind.IMAGE_NUMBER_PREDICTION: {
      if (followUp && followUp.status === 'published') {
        return WidgetUIPhase.FOLLOW_UP_PUBLISHED;
      }
      return widgetInteractions?.length
        ? WidgetUIPhase.SUBMITTED
        : WidgetUIPhase.INTERACTIVE;
    }

    case WidgetKind.TEXT_PREDICTION_FOLLOW_UP:
    case WidgetKind.IMAGE_PREDICTION_FOLLOW_UP:
    case WidgetKind.IMAGE_NUMBER_PREDICTION_FOLLOW_UP:
    case WidgetKind.TEXT_NUMBER_PREDICTION_FOLLOW_UP: {
      return WidgetUIPhase.FOLLOW_UP_PUBLISHED;
    }

    default: {
      return widgetInteractions?.length
        ? WidgetUIPhase.SUBMITTED
        : WidgetUIPhase.INTERACTIVE;
    }
  }
}

export function getSelectedOptionIndex({
  widgetPayload,
  widgetInteractions,
}: WidgetState) {
  const { kind, choices, options } = widgetPayload;
  if (CHOICE_WIDGET_KIND.includes(kind)) {
    return choices.findIndex(({ id }) =>
      widgetInteractions?.find(({ choice_id }) => choice_id === id)
    );
  } else if (OPTION_WIDGET_KIND.includes(kind)) {
    return options.findIndex(({ id }) =>
      widgetInteractions?.find(({ option_id }) => option_id === id)
    );
  }
  return -1;
}

export function getWidgetRewardsFromRewardTransactions(
  rewardTransactions: IRewardTransaction[]
): IWidgetEarnableReward[] {
  return rewardTransactions.map(
    ({
      reward_item_amount,
      reward_item_name,
      reward_item_id,
      reward_action_key,
    }) => ({
      reward_item_amount,
      reward_item_name,
      reward_item_id,
      reward_action_key,
    })
  );
}

export function isTimeExpired(expiryTimestamp: string) {
  const expiredTime = new Date(expiryTimestamp);
  const timeout = expiredTime.getTime() - Date.now();
  return timeout <= 0;
}
