import {
  CarouselScrollEvent,
  EventCallback,
  EventCallbacks,
  EventMap,
  EventMapKeys,
  IWidgetEvents,
  MediaPosition,
  PostEvent,
  PostNavigationEvent,
  ProductEvent,
} from '../types';
import { Debug } from '../debug';
import { AnalyticsEventData, Analytics } from './analytics';

const createWidgetEvents = (): IWidgetEvents => {
  let destroyed = false;
  const eventCallbacks: { [key in EventMapKeys]?: EventCallbacks<EventMap[key]> } = {};

  // Event types that are not sent to AdAlong Analytics
  const ignoredEventsAnalytics: Set<EventMapKeys> = new Set(['minContentReached']);

  const getCallbacks = <K extends EventMapKeys>(eventName: K): EventCallbacks<EventMap[K]> => {
    eventCallbacks[eventName] = eventCallbacks[eventName] || [];
    return eventCallbacks[eventName] as EventCallbacks<EventMap[K]>;
  };

  // eslint-disable-next-line max-len
  const shouldIgnoreEvent = (eventName: EventMapKeys): boolean =>
    ignoredEventsAnalytics.has(eventName);

  const formatEventForAnalytics = <K extends EventMapKeys>(
    eventName: K,
    event: EventMap[K],
    widgetId: string,
  ): AnalyticsEventData => {
    switch (eventName) {
      case 'thumbnailUnavailable':
      case 'thumbnailHover':
      case 'thumbnailLoaded':
      case 'originalPostOpened':
      case 'socialProfileOpened':
      case 'postOpened':
      case 'postNavigation':
      case 'postClosed':
      case 'stlOpened':
      case 'stlNavigation':
      case 'stlClosed':
      case 'mobilePlayerOpened':
      case 'mobilePlayerNavigation':
      case 'mobilePlayerClosed':
      case 'videoPlayed':
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostEvent).post.id,
        };
      case 'thumbnailViewed':
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostEvent & MediaPosition).post.id,
          event_position: (event as PostEvent & MediaPosition).position,
        };
      case 'thumbnailClicked':
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostEvent & MediaPosition).post.id,
          event_position: (event as PostEvent & MediaPosition).position,
        };
      case 'productViewed':
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostEvent).post.id,
          event_product_id: (event as ProductEvent).product,
        };
      case 'productClicked':
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostEvent).post.id,
          event_product_id: (event as ProductEvent).product,
        };
      case 'packshotClicked':
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostEvent).post.id,
          event_product_id: (event as ProductEvent).product,
        };
        return {
          event_widget_id: widgetId,
          event_post_id: (event as PostNavigationEvent).post.id,
          event_direction: (event as PostNavigationEvent).direction,
        };
      case 'carouselArrowClicked':
      case 'carouselNativeScroll':
      default:
        return {
          event_widget_id: widgetId,
        };
    }
  };

  const onEvent = <K extends EventMapKeys>(
    eventName: K,
    callback: EventCallback<EventMap[K]>,
  ): void => {
    if (destroyed) return;

    Debug.try(() => {
      const callbacks = getCallbacks(eventName);
      if (!callbacks.includes(callback)) {
        callbacks.push(callback);
      }
    }).catch((error) => {
      console.error('Failed to add event callback:', error);
    });
  };

  const offEvent = <K extends EventMapKeys>(
    eventName: K,
    callback?: EventCallback<EventMap[K]>,
  ): void => {
    Debug.try(() => {
      if (callback) {
        const callbacks = getCallbacks(eventName);
        const index = callbacks.indexOf(callback);
        if (index > -1) {
          callbacks.splice(index, 1);
        }
      } else {
        eventCallbacks[eventName] = [];
      }
    }).catch((error) => {
      console.error('Failed to remove event callback:', error);
    });
  };

  const hasSubscribed = (eventName: EventMapKeys): boolean => !!eventCallbacks[eventName]?.length;

  const triggerEvent = <K extends EventMapKeys>(
    eventName: K,
    event: EventMap[K],
    widgetId?: string,
  ): void => {
    if (destroyed) return;

    const callbacks = getCallbacks(eventName);
    callbacks.forEach((callback) => callback(event));

    if (widgetId && !shouldIgnoreEvent(eventName)) {
      const data = formatEventForAnalytics(eventName, event, widgetId);
      Analytics.trackEvent({ eventName, data });
    }
  };

  const destroy = (): void => {
    Object.keys(eventCallbacks).forEach((key) => {
      eventCallbacks[key as EventMapKeys] = [];
    });
    destroyed = true;
  };

  return {
    onEvent,
    offEvent,
    hasSubscribed,
    triggerEvent,
    destroy,
  };
};

export default createWidgetEvents;
