import SdkOptions from './options'
import SdkSettings, { EnhancedContentSettings } from './settings'
import EnhancedContentApi, { attachIframeContextListener } from './enhancedContent'
import EventsApi from './events'
import { getCookie, setCookie, deleteCookie } from './utils/cookies'
import { createLogger } from './utils/logger'
import TimeOnPageTracker from './utils/time-on-page-tracker'
import { SDK_VERSION } from './version'
import { inBrowser } from './utils/runtime'

const sessionIdKey = 'salsify_session_id'

const defaultOptions = {
  languageCode: 'en-US',
  tracking: true,
}

const defaultEcSettings = {
  idType: 'SDKID',
}

/** @internal */
export interface Context {
  /** URL of the website serving the enhanced content, if operating within the browser */
  url: string | undefined
  /** UUID for the user visiting the website if operating within the browser,
   *  stored in / retrieved from a session cookie unless tracking is set to false
   *
   * If tracking is false, this is set to "NOT_TRACKED"
   */
  sessionId: string | undefined
  /** UUID for the page visit, if operating within the browser
   *
   *  Not stored in a cookie; reset across page visits
   */
  pageSessionId: string | undefined
  /** Whether or not cookie-based user tracking is enabled. The host site is responsible
   *  for displaying any user consent dialogs and setting this option based on the user's
   *  preference.
   */
  tracking: boolean
  /** Client identifier (UUID) provided by Salsify that uniquely identifies the website */
  clientId: string
  /** Locale identifier that determines the language of the page content */
  languageCode: string
  /** Enhanced Content settings, including the product identifier type */
  enhancedContent: EnhancedContentSettings
  /** SDK version */
  version: string
  jsSource: 'bundle' | 'npm'
}

/**
 * The Salsify Experiences SDK.
 *
 * This class is responsible for initializing the SDK and exposing the public getter methods for each different available API.
 */
export default class SdkApi {
  #initialized = false
  #settings?: SdkSettings
  #ecApi?: EnhancedContentApi
  #eventsApi?: EventsApi
  #context?: Context
  #stalePageSessionId = false
  #timeOnPageTracker?: TimeOnPageTracker
  #jsSource: 'bundle' | 'npm'

  /** @internal */
  public constructor(jsSource: 'bundle' | 'npm') {
    this.#jsSource = jsSource
  }

  /**
   * Initializes the SDK.
   *
   * @example
   * ```javascript
   * window.salsifyExperiencesSdk.init({ clientId, enhancedContent: { idType }})
   * ```
   *
   * @param options The options to initialize the SDK with.
   */
  public init(options: SdkOptions): void {
    if (this.#initialized) {
      throw new Error('Salsify Experiences SDK has already been initialized.')
    }

    const ecSettings = {
      ...defaultEcSettings,
      ...options.enhancedContent,
    }

    this.#settings = {
      ...defaultOptions,
      ...options,
      enhancedContent: ecSettings,
    }

    this.#context = {
      url: inBrowser() ? window.location.href : undefined,
      sessionId: this.#updateSessionId(this.#settings.tracking),
      pageSessionId: inBrowser() ? crypto.randomUUID?.() : undefined,
      tracking: this.#settings.tracking,
      clientId: this.#settings.clientId,
      languageCode: this.#settings.languageCode,
      enhancedContent: this.#settings.enhancedContent,
      version: SDK_VERSION,
      jsSource: this.#jsSource,
    }

    if (inBrowser()) {
      attachIframeContextListener(this.#context)
    }

    const logger = createLogger(this.#context, this.#settings)
    logger.log('init', this.#settings)

    this.#ecApi = new EnhancedContentApi(this.#settings, this.#context, logger, {
      beforeRender: this.#beforeRender.bind(this),
      afterRender: this.#afterRender.bind(this),
    })
    if (inBrowser()) {
      this.#timeOnPageTracker = new TimeOnPageTracker(logger, this.#ecApi)
      this.#timeOnPageTracker.start()
    }
    this.#eventsApi = new EventsApi(logger, { beforeNavigation: this.#beforeNavigation.bind(this) }, this.#ecApi)

    this.#initialized = true
  }

  /**
   * Whether the SDK has been initialized.
   *
   * @example
   * ```javascript
   * const salsify = window.salsifyExperiencesSdk;
   * salsify.initialized; // false
   * salsify.init({ clientId, enhancedContent: { idType } });
   * salsify.initialized; // true
   * ```
   */
  public get initialized(): boolean {
    return this.#initialized
  }

  /**
   * The initialized Enhanced Content API.
   *
   * Throws an error if the SDK has not been initialized.
   *
   * @example
   * ```javascript
   * const salsify = window.salsifyExperiencesSdk;
   * const ec = salsify.enhancedContent;
   * ```
   */
  public get enhancedContent(): EnhancedContentApi {
    if (!this.#ecApi) {
      throw new Error('Salsify Experiences SDK has not been initialized.')
    }
    return this.#ecApi
  }

  /**
   * The initialized Events API.
   *
   * Throws an error if the SDK has not been initialized.
   *
   * @example
   * ```javascript
   * const salsify = window.salsifyExperiencesSdk;
   * const events = salsify.events;
   * ```
   */
  public get events(): EventsApi {
    if (!this.#eventsApi) {
      throw new Error('Salsify Experiences SDK has not been initialized.')
    }
    return this.#eventsApi
  }

  #getOrCreateSessionId(): string | undefined {
    let id = getCookie(sessionIdKey)
    if (!id) {
      id = crypto.randomUUID?.()
      if (id) {
        setCookie(sessionIdKey, id)
      }
    }
    return id
  }

  #removeSessionId(): void {
    deleteCookie(sessionIdKey)
  }

  #resetPageSessionId(): void {
    if (this.#context) {
      this.#context.pageSessionId = crypto.randomUUID?.()
      this.#stalePageSessionId = false
    }
  }

  #beforeRender(): void {
    if (!inBrowser()) return

    if (this.#context) {
      this.#context.url = window.location.href
    }

    if (this.#stalePageSessionId) {
      this.#resetPageSessionId()
    }
  }

  #afterRender(): void {
    if (inBrowser()) {
      this.#stalePageSessionId = true
    }
  }

  #beforeNavigation(): void {
    if (!inBrowser()) return

    if (this.#context) {
      this.#context.url = window.location.href
    }

    if (this.#stalePageSessionId) {
      this.#timeOnPageTracker?.sendEvent()
      this.#timeOnPageTracker?.restart()
      this.#resetPageSessionId()
    }
  }

  #updateSessionId(tracking: boolean): string | undefined {
    if (inBrowser()) {
      if (tracking) {
        return this.#getOrCreateSessionId()
      } else {
        this.#removeSessionId()
        return 'NOT_TRACKED'
      }
    }
  }
}
