/* eslint-disable camelcase */
import { useEffect, useRef, useCallback, useMemo } from 'react';
import { ANALYTICS_CONTEXTS, ANALYTICS_EVENTS, ANALYTICS_INTERNAL_EVENTS, ANALYTICS_METHODS, ANALYTICS_SNOWPLOW_SCHEMA_VENDOR as SCHEMA_VENDOR } from './constants';
/**
 * Wrapper around analytics data
 * between AnalyticsService and AnalyticsProvider
 */
export class AnalyticsMediator {
  constructor(method, args = []) {
    this.method = method;
    this.args = args;
  }
}
const testUserEmailDomain = 'm.mablmail.com';
/**
 * Provides abstraction across analytics services
 */
export function useAnalytics({
  analyticsProviders = []
} = {}) {
  const providers = useRef(analyticsProviders);
  const queue = useRef([]);
  const page = useRef(null);
  const tab = useRef(null);
  const dialog = useRef(null);
  const contextProperty = useRef(null);
  useEffect(() => {
    // Update or remove view context based on active tab and dialog
    if (page.current || tab.current || dialog.current || contextProperty.current) {
      const data = {};
      if (page.current) {
        data.page = page.current;
      }
      if (dialog.current) {
        data.dialog = dialog.current;
      }
      if (tab.current) {
        data.tab = tab.current;
      }
      if (contextProperty.current) {
        data.property = contextProperty.current;
      }
      setContext(ANALYTICS_CONTEXTS.VIEW, data);
    } else {
      removeContext(ANALYTICS_CONTEXTS.VIEW);
    }
  }, [page.current, tab.current, dialog.current, contextProperty.current]);

  /**
   * Handle event
   * @param {AnalyticsMediator} event
   */
  function pushEvent(event) {
    if (providers.current.length === 0) {
      queue.current = [...queue.current, event];
    } else {
      providers.current.forEach(provider => {
        provider.handle(event);
      });
    }
  }

  /**
   * Track advanced events with set of properties
   *
   * @param event
   * @param properties
   */
  function trackInternal(event, properties) {
    if (!event) {
      throw Error('"event" argument is required');
    }
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.TRACK_INTERNAL, [event, properties]));
  }
  /**
   * Base method to track simple events
   * like click, form submit, input change
   *
   * @param event string
   * @param label string (optional)
   * @param property string (optional)
   * @param value number|boolean (optional)
   * @param isOptions boolean (optional)
   */
  function track({
    event,
    label = null,
    property = null,
    value = null,
    isOptions = false
  }) {
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.TRACK, [page.current, event, label, property, value, isOptions]));
  }

  /**
   * Track click at particular element
   *
   * Simplify wrapper around AnalyticsService.track
   *
   * @param label
   * @param property
   * @param value
   */
  function click({
    label,
    property = null,
    value = null
  }) {
    track({
      event: ANALYTICS_EVENTS.ITEM_CLICK,
      label,
      property,
      value
    });
  }

  /**
   * Track input/select/etc changes
   *
   * Simplify wrapper around AnalyticsService.track
   *
   * @param element
   * @param property
   * @param value
   * @param isOptions
   */
  function change({
    element,
    property = null,
    value = null,
    isOptions = false
  }) {
    track({
      event: ANALYTICS_EVENTS.ITEM_CHANGE,
      label: element,
      property,
      value,
      isOptions
    });
  }

  /**
   * Track page view and reset view context
   * before each new page view
   * If new page is the same as current it doesn't reset data without "force" flag
   *
   * @see client/js/routes/routes-map.js
   * @see client/js/routes/route-restriction.js
   *
   * @param pageName - name of page from routesMap
   * @param force - refresh page event if new is same as current
   */
  function pageView(pageName, force = false) {
    if (page.current && pageName === page.current && !force) {
      return;
    }
    page.current = pageName;
    dialog.current = null;
    tab.current = null;
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.VIEW, [pageName]));
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.TRACK, [pageName, ANALYTICS_EVENTS.PAGE_VIEW, pageName]));
  }

  /**
   * Track dialog (modal) view and
   * set appropriate view context
   *
   * @param name - unique identifier of dialog (modal) across app
   * @param additionalProperty - additional property (is optional)
   */
  function dialogView(name, additionalProperty = null) {
    dialog.current = name;
    contextProperty.current = additionalProperty;
    if (name) {
      track({
        event: ANALYTICS_EVENTS.DIALOG_VIEW,
        label: name,
        property: additionalProperty
      });
    }
  }

  /**
   * Track tab view and
   * set appropriate view context
   *
   * @param name - unique identifier of tab across app
   */
  function tabView(name) {
    tab.current = name;
    if (name) {
      track({
        event: ANALYTICS_EVENTS.TAB_VIEW,
        label: name
      });
    }
  }

  /**
   * Identify user
   *
   * @param userId
   * @param userData
   */
  function identify(userId, userData = {}) {
    setContext(ANALYTICS_CONTEXTS.USER, userData);
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.IDENTIFY, [userId, userData]));
    trackInternal(ANALYTICS_INTERNAL_EVENTS.SESSION_START, {
      user_id: userId
    });
  }

  /**
   * Reset user
   *
   */
  function reset() {
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.RESET));
  }

  /**
   * Add context by key
   *
   * @param type
   * @param value
   */
  function setContext(type, value) {
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.CONTEXT, [type, value]));
  }

  /**
   * Remove context by key
   *
   * @param type
   */
  function removeContext(type) {
    pushEvent(new AnalyticsMediator(ANALYTICS_METHODS.CONTEXT, [type, null]));
  }

  /**
   * Setup analytics providers
   *
   * @param {AnalyticsProvider} analyticsProvidersParam
   */
  function setAnalyticsProviders(analyticsProvidersParam) {
    if (analyticsProvidersParam.length > 0) {
      providers.current = [...providers.current, ...analyticsProvidersParam];
      analyticsProvidersParam.forEach(provider => {
        queue.current.forEach(e => provider.handle(e));
      });
    }
  }
  const returnValues = {
    setAnalyticsProviders: useCallback(setAnalyticsProviders, []),
    trackInternal: useCallback(trackInternal, []),
    dialogView: useCallback(dialogView, []),
    pageView: useCallback(pageView, []),
    identify: useCallback(identify, []),
    tabView: useCallback(tabView, []),
    change: useCallback(change, []),
    reset: useCallback(reset, []),
    click: useCallback(click, []),
    track: useCallback(track, [])
  };
  return useMemo(() => returnValues, []);
}

/**
 * Base class for analytics provider
 * Real providers should implement method `handle`
 */
export class AnalyticsProvider {
  constructor() {
    if (typeof this.handle !== 'function') {
      throw new TypeError('Method "handle" must be implemented');
    }
  }
}
/**
 * Implements analytics logic for snowplow js tracker
 *
 * @see client/template/main.html
 * @see https://github.com/snowplow/snowplow-javascript-tracker
 */
export class SnowplowAnalyticsProvider extends AnalyticsProvider {
  /**
   * Snowplow tracker function
   * @type {function}
   */
  fn = null;
  contexts = {};
  snowplowSchemaInternalEvents = SCHEMA_VENDOR.INTERNAL_EVENTS;
  constructor(snowplowSchemaInternalEvents) {
    super();
    if (!window.snowplow) {
      throw new Error("Can't find snowplow Tracker function");
    }
    if (!!snowplowSchemaInternalEvents) {
      this.snowplowSchemaInternalEvents = snowplowSchemaInternalEvents;
    }
    this.fn = window.snowplow;
    // Configure page pings
    this.fn('enableActivityTracking', 1, 15);
  }
  handle(event) {
    if (this.fn) {
      switch (event.method) {
        case ANALYTICS_METHODS.IDENTIFY:
          this.fn('setUserId', event.args[0]);
          break;
        case ANALYTICS_METHODS.VIEW:
          this.fn('trackPageView');
          break;
        case ANALYTICS_METHODS.CONTEXT:
          if (event.args[1]) {
            this.removeContext(event.args[0]);
            this.addContext(event.args[0], event.args[1]);
          } else {
            // Remove context when we pass non-true context value
            this.removeContext(event.args[0]);
          }
          break;
        case ANALYTICS_METHODS.TRACK:
          // @see https://github.com/snowplow/snowplow/wiki/2-Specific-event-tracking-with-the-Javascript-tracker#trackStructEvent
          this.fn('trackStructEvent', event.args[0], event.args[1], event.args[2] || '', event.args[3] || '', this.convertValue(event.args[4] || 0));
          break;
        case ANALYTICS_METHODS.TRACK_INTERNAL:
          // @see https://github.com/snowplow/snowplow/wiki/Custom-events#self-describing-events
          this.fn('trackUnstructEvent', {
            schema: `${this.snowplowSchemaInternalEvents}/${event.args[0]}/jsonschema/1-0-0`,
            data: event.args[1]
          });
          break;
        default:
          this.fn('trackUnstructEvent', {
            schema: `${this.snowplowSchemaInternalEvents}/default/jsonschema/1-0-0`,
            data: event.args[0]
          });
      }
    }
  }

  /**
   * Return numeric value at any case
   * @param value
   * @returns {number}
   */
  convertValue(value) {
    // eslint-disable-next-line no-param-reassign
    value = Number(value);
    if (isNaN(value)) {
      return 0;
    }
    return value;
  }

  /**
   * Create context object based on name and value
   *
   * @param name
   * @param value
   * @returns {{schema: string, data: *}}
   */
  buildContext(name, value) {
    return {
      schema: `${SCHEMA_VENDOR.CONTEXTS}/${name}/jsonschema/1-0-0`,
      data: value
    };
  }

  /**
   * @see https://github.com/snowplow/snowplow/wiki/2-Specific-event-tracking-with-the-Javascript-tracker#31851-addglobalcontexts
   * @param name
   * @param value
   */
  addContext(name, value) {
    if (this.fn) {
      this.fn('addGlobalContexts', [this.buildContext(name, value)]);
      // Save context at the provider to be able to remove it by value then
      this.contexts[name] = value;
    }
  }

  /**
   * @see https://github.com/snowplow/snowplow/wiki/2-Specific-event-tracking-with-the-Javascript-tracker#31852-removeglobalcontexts
   * @param name
   */
  removeContext(name) {
    if (this.contexts[name] && this.fn) {
      this.fn('removeGlobalContexts', [this.buildContext(name, this.contexts[name])]);
      delete this.contexts[name];
    }
  }
}

/**
 * Implements analytics logic for pendo js tracker
 *
 * @see client/template/main.html
 */
export class PendoAnalyticsProvider extends AnalyticsProvider {
  constructor() {
    super();
    if (!window.pendo) {
      throw new Error("Can't find pendo Tracker function");
    }
  }
  handle(event) {
    const {
      method
    } = event;
    if (method === ANALYTICS_METHODS.IDENTIFY && window.pendo) {
      const {
        // eslint-disable-next-line @typescript-eslint/naming-convention
        org_id,
        // eslint-disable-next-line @typescript-eslint/naming-convention
        org_name,
        username,
        // eslint-disable-next-line @typescript-eslint/naming-convention
        full_name,
        // eslint-disable-next-line @typescript-eslint/naming-convention
        user_id,
        // eslint-disable-next-line @typescript-eslint/naming-convention
        user_role,
        ...rest
      } = event.args[1] || {};
      window.pendo.initialize({
        account: {
          id: org_id,
          org_name
        },
        visitor: {
          id: user_id,
          email: username,
          full_name,
          role: user_role,
          ...rest
        }
      });
    } else if (method === ANALYTICS_METHODS.TRACK) {
      const [page, eventType, label, property, value, isOptions] = event.args;
      const pendoPayload = {
        page
      };
      if (property) {
        pendoPayload.property = property;
      }
      if (value) {
        pendoPayload.value = value;
      }
      if (Object.values(ANALYTICS_EVENTS).includes(eventType)) {
        // some standard tracked event
        this.pendoTrack(`${eventType} > ${label}`, pendoPayload);
      } else {
        // some custom tracked event
        if (label) {
          pendoPayload.label = label;
        }
        this.pendoTrack(eventType, pendoPayload);
      }
      if (isOptions && window.pendo) {
        window.pendo.updateOptions({
          visitor: {
            [property]: value
          }
        });
      }
    }
  }
  pendoTrack(name, data) {
    if (window.pendo && window.pendo.isReady && window.pendo.isReady()) {
      return window.pendo.track(name, data);
    }
    setTimeout(() => {
      this.pendoTrack(name, data);
    }, 500);
  }
}

/**
 * Implements analytics logic for Hotjar tracker
 *
 * @see client/template/main.html
 */
export class HotjarAnalyticsProvider extends AnalyticsProvider {
  constructor() {
    super();
    if (!window.hj) {
      throw new Error("Can't find hotjar Tracker function");
    }
  }
  handle(event) {
    if (window.hj) {
      switch (event.method) {
        case ANALYTICS_METHODS.IDENTIFY:
          window.hj('identify', event.args[0], event.args[1]);
          break;
        default:
          break;
      }
    }
  }
}
export class AppcusesAnalyticsProvider extends AnalyticsProvider {
  constructor() {
    super();
    if (!window.Appcues) {
      throw new Error("Can't find Apcusses Tracker function");
    }
  }
  handle(event) {
    if (window.Appcues) {
      switch (event.method) {
        case ANALYTICS_METHODS.IDENTIFY:
          if (testUserEmailDomain === event.args[1].user_email_domain) {
            return;
          }
          window.Appcues.identify(event.args[0], {
            user_id: event.args[1].user_id,
            registered_on: event.args[1].signup_date,
            tenant_phase: event.args[1].tenant_phase,
            tenant_id: event.args[1].tenant_id,
            is_trial_organization: event.args[1].is_trial_organization,
            org_id: event.args[1].org_id,
            org_name: event.args[1].org_name,
            language: event.args[1].datarobot_language,
            theme: event.args[1].theme,
            max_workers: event.args[1].max_workers,
            logins_number: event.args[1].logins_number,
            gpu_enabled: event.args[1].gpu_enabled,
            is_admin_user: event.args[1].is_admin_user,
            industry: event.args[1].industry,
            tenant_name: event.args[1].account_tenant_name,
            user_email_domain: event.args[1].user_email_domain,
            is_nim_models_enabled: event.args[1].is_nim_models_enabled
          });
          break;
        case ANALYTICS_METHODS.VIEW:
          window.Appcues.page();
          break;
        default:
          break;
      }
    }
  }
}