'use client';

import { runInAction } from 'mobx';
import storageAvailable from 'storage-available';

import { PageEventType } from '@/utils/event-logger';

import { RootStore, Substore } from './rootStore';
import { makeAutoObservableSubstore } from './utils';

interface Promo {
  id: string;
  impression?: number;
  dismissed?: number;
}

type WebPageEventParams = Parameters<
  NonNullable<Substore['logger']>['logWebPageEvent']
>[0];

const LOCAL_STORAGE_KEY = 'promo_serialized';

const SERIALIZED_SCHEMA_VERSION = '0';

export const HIDE_SIDEBAR_LIMIT = 2;
export const HONOR_DISMISSAL_DURATION = 48 * 3600000; // 48 hours

export function serializePromo(promo: Promo) {
  return [
    promo.id,
    SERIALIZED_SCHEMA_VERSION,
    promo.impression || 0,
    promo.dismissed || 0,
  ] as const;
}

export function deserializePromo(serializedPromo: string): Promo | null {
  const [id, , impression = 0, dismissed = 0] = serializedPromo;
  /**
   * If we needed any special migration logic for an old `schemaVersion`, that could go here
   */
  return {
    id,
    impression,
    dismissed,
  } as Promo;
}

export class PromoStore implements Substore {
  dismissedModal = false;
  dismissedSidebar = false;
  hideCount = 0;
  hideTimestamp?: number;
  sessionHideCount = 0;
  sessionTimestamp?: number;

  promo: Record<string, Promo> = {};
  slideId?: string | null;
  previouslyDismissed?: boolean;

  readonly root: RootStore;
  get apiClient() {
    return this.root.apiClient;
  }
  get logger() {
    return this.root.logger;
  }
  // DO NOT ADD INIT LOGIC IN constructor. this will run on every page! considering adding any it in the component init logic instead
  constructor(root: RootStore) {
    this.root = root;
    makeAutoObservableSubstore(this);
  }

  // this will run on every page! considering adding any it in the component init logic instead
  initialize() {
    this.loadStorage();
  }

  get showSidebar() {
    return !this.dismissedSidebar;
  }

  set showSidebar(visible: boolean) {
    this.dismissedSidebar = !visible;
    // Increment or decrement the hide counter
    if (visible) {
      this.sessionHideCount--;
    } else {
      this.sessionHideCount++;
      this.hideTimestamp = Date.now();
    }
    this.persistStorage();
  }

  /**
   * Creates an object to store the promo data if it does not already exist
   */
  getPromo(id: string) {
    if (!this.promo[id]) {
      this.promo[id] = { id };
    }
    return this.promo[id];
  }

  /**
   * Determines whether a promo was already dismissed
   */
  isDismissed(id: string) {
    return !!this.getPromo(id).dismissed;
  }

  impression(id: string, params?: Partial<WebPageEventParams>) {
    const promo = this.getPromo(id);

    if (!promo.impression) {
      promo.impression = 1;

      this.logger.logWebPageEvent({
        userId: this.root.session.userId!,
        eventType: PageEventType.OPEN,
        element: 'popup',
        method: 'auto',
        entityType: 'promo',
        entityId: id,
        pageUrl: location.pathname,
        ...params,
      });
    }
  }

  dismiss(id: string, params?: Partial<WebPageEventParams>) {
    const promo = this.getPromo(id);

    if (!promo.dismissed) {
      promo.dismissed = 1;

      this.logger.logWebPageEvent({
        userId: this.root.session.userId!,
        eventType: PageEventType.CLOSE,
        element: 'popup',
        method: 'auto',
        entityType: 'promo',
        entityId: id,
        pageUrl: location.pathname,
        ...params,
      });

      this.persistStorage();
    }
  }

  /**
   * Sets the slide and keeps track to persist between session
   */
  setSlideId(id: string) {
    this.slideId = id;
    this.sessionTimestamp = Date.now();
    this.persistStorage();
  }

  /**
   * Loads persisted promo data from localStorage
   */
  loadStorage(reset = true) {
    if (this.root.isLocalStorageAvailable) {
      try {
        const serialized = localStorage.getItem(LOCAL_STORAGE_KEY);
        if (serialized) {
          runInAction(() => {
            let parsed = JSON.parse(serialized);
            if (Array.isArray(parsed)) {
              // Support migrating from promo array serialization
              parsed = { promo: parsed };
            }
            if (typeof parsed !== 'object') {
              throw new Error('Expected an object');
            }

            // Timestamp of the last session activity
            if (typeof parsed.ts === 'number') {
              this.sessionTimestamp = parsed.ts;
            }
            // Restore hide counter within the timeframe
            if (
              typeof parsed.hts === 'number' &&
              typeof parsed.hc === 'number'
            ) {
              if (Date.now() < parsed.hts + HONOR_DISMISSAL_DURATION) {
                this.hideCount = parsed.hc;
                this.sessionHideCount = 0;
              }
            }
            // Should we hide the sidebar?
            this.dismissedSidebar = this.hideCount >= HIDE_SIDEBAR_LIMIT;
            // Restore previous slide
            if (parsed.slide) {
              this.slideId = parsed.slide;
            }
            if (typeof parsed.diss === 'boolean') {
              this.previouslyDismissed = parsed.diss;
            }
            // Restore promos, including impression/dismissal states
            if (reset) {
              this.promo = {};
            }
            if (Array.isArray(parsed.promo)) {
              parsed.promo.forEach((parsedPromo: string) => {
                const promo = deserializePromo(parsedPromo);
                if (promo) {
                  this.promo[promo.id] = promo;
                }
              });
            }
          });
        }
      } catch (e) {
        console.error('Failed to deserialize localStorage');
      }
    }
  }

  /**
   * Serializes the currnet promo data and puts it in localStorage
   */
  persistStorage() {
    if (storageAvailable('localStorage')) {
      const serialized = JSON.stringify({
        // If we hid the slide during the session, make sure we rotate on the next load
        ts: this.sessionTimestamp,
        hc: this.hideCount + this.sessionHideCount,
        hts: this.hideTimestamp,
        diss: this.sessionHideCount > 0,
        slide: this.slideId,
        promo: Object.values(this.promo).map((promo) => serializePromo(promo)),
      });
      localStorage.setItem(LOCAL_STORAGE_KEY, serialized);
    }
  }
}
