import { __ } from '@wordpress/i18n'
import { h, onMounted, onUnmounted, defineAsyncComponent } from 'vue'
import * as he from "he"
import axios, { AxiosRequestConfig } from 'axios'
import purify from 'dompurify'
import { getSavetoWishlistObj } from '@/config'
import LoadingSpinner from '@/apps/settings/components/LoadingSpinner.vue'

/**
 * Load components.
 *
 * @param {string} control
 *
 * @since 2.1.9
 * @return {string}
 */
export const loadComponent = (control: string) => {
  switch (control) {
    case 'heading':
      return getControlSettingsAsyncLayout('HeadingControl');
    case 'text':
      return getControlSettingsAsyncLayout('TextControl');
    case 'textarea':
      return getControlSettingsAsyncLayout('TextAreaControl');
    case 'number':
      return getControlSettingsAsyncLayout('NumberControl');
    case 'select':
      return getControlSettingsAsyncLayout('SelectControl');
    case 'radio':
      return getControlSettingsAsyncLayout('RadioControl');
    case 'checkbox':
      return getControlSettingsAsyncLayout('CheckboxControl');
    case 'button':
      return getControlSettingsAsyncLayout('ButtonControl');
    case 'html':
      return getControlSettingsAsyncLayout('HtmlControl');
    case 'image':
      return getControlSettingsAsyncLayout('ImageControl');
    case 'paragraph':
      return getControlSettingsAsyncLayout('ParagraphControl');
    case 'table':
      return getControlSettingsAsyncLayout('TableControl');
    case 'group':
      return getControlSettingsAsyncLayout('GroupControl');
    case 'group_conditional':
      return getControlSettingsAsyncLayout('GroupConditionalControl');
    case 'switch':
      return getControlSettingsAsyncLayout('SwitchControl');
    case 'money':
      return getControlSettingsAsyncLayout('MoneyControl');
    case 'percent':
      return getControlSettingsAsyncLayout('PercentControl');
    case 'copy':
      return getControlSettingsAsyncLayout('CopyControl');
    case 'link':
      return getControlSettingsAsyncLayout('LinkControl');
    case 'options':
      return getControlSettingsAsyncLayout('OptionsControl');
    case 'upgrade_suffix':
      return getUpgradeSettingsAsyncLayout('SuffixOverride');
    case 'upgrade_advance_tax':
      return getUpgradeSettingsAsyncLayout('AdvanceTax');
    case 'upgrade_tax_exemption':
      return getUpgradeSettingsAsyncLayout('TaxExemption');
    case 'upgrade_prices_display':
      return getUpgradeSettingsAsyncLayout('PricesDisplay');
    case 'upgrade_explicitly_use_product':
      return getUpgradeSettingsAsyncLayout('DefinePricesGlobally');
    case 'license_drm':
      return getUpgradeSettingsAsyncLayout('LicenseDRM');
    case 'color_picker':
      return getControlSettingsAsyncLayout('ColorPickerControl');
    case 'preset':
      return getControlSettingsAsyncLayout('PresetControl');
    default:
      return getControlSettingsAsyncLayout('HeadingControl');
  }
};

/**
 * Get async layout component relative to `/src/apps/settings/` directory.
 *
 * @param {string} layout The path to the component.
 *
 * @return {any} The async component.
 */
export const getControlSettingsAsyncLayout = (layout: string) => defineAsyncComponent({
  loader: () => {
    const controlsLayouts: Record<string, any> = import.meta.glob(
      `@/apps/settings/components/controls/*.vue`,
      { import: 'default' }
    );

    return controlsLayouts[`/src/apps/settings/components/controls/${layout}.vue`]()
  },
  loadingComponent: () => h(LoadingSpinner),
  delay: 200,
  errorComponent: () => null,
  timeout: 3000
})

/**
 * Get async layout component relative to `/src/apps/settings/` directory.
 *
 * @param {string} layout The path to the component.
 *
 * @return {any} The async component.
 */
export const getUpgradeSettingsAsyncLayout = (layout: string) => defineAsyncComponent({
  loader: () => {
    const upgradeLayouts: Record<string, any> = import.meta.glob(
      `@/apps/settings/upgrade/*.vue`,
      { import: 'default' }
    );

    return upgradeLayouts[`/src/apps/settings/upgrade/${layout}.vue`]()
  },
  loadingComponent: () => h(LoadingSpinner),
  delay: 200,
  errorComponent: () => null,
  timeout: 3000
})

/**
 * Get async component relative to `/src/apps/settings/` directory.
 * @param path
 *
 * @return {any} The async component.
 */
export const getSettingsAsyncComponent = (path: string) => defineAsyncComponent({
  loader: () => {
    const components: Record<string, any> = import.meta.glob([
      '@/apps/settings/**/*.vue',
    ], { import: 'default' })

    return components[`/src/apps/settings/${path}`]()
  },
  loadingComponent: () => null,
  delay: 200,
  errorComponent: () => null,
  timeout: 3000
})

/**
 * Get async component relative to `/src/apps/wishlist/` directory.
 * @param path
 *
 * @return {any} The async component.
 */
export const getWishlistAsyncComponent = (path: string) => defineAsyncComponent({
  loader: () => {
    const components: Record<string, any> = import.meta.glob([
      '@/apps/wishlist/**/*.vue',
    ], { import: 'default' })

    return components[`/src/apps/wishlist/${path}`]()
  },
  loadingComponent: () => null,
  delay: 200,
  errorComponent: () => null,
  timeout: 3000
})

/**
 * Get async component relative to `/src/apps/pages/` directory.
 * @param path
 *
 * @return {any} The async component.
 */
export const getPagesAsyncComponent = (path: string) => defineAsyncComponent({
  loader: () => {
    const components: Record<string, any> = import.meta.glob([
      '@/apps/pages/**/*.vue',
    ], { import: 'default' })

    return components[`/src/apps/pages/${path}`]()
  },
  loadingComponent: () => null,
  delay: 200,
  errorComponent: () => null,
  timeout: 3000
})

/**
 * Purify HTML string.
 *
 * @param {string} html
 *
 * @since 2.1.9
 * @return {string}
 */
export const purifyHtml = (html: string) => {
  return purify.sanitize(html, { ALLOWED_TAGS: getSavetoWishlistObj.allowedTags, ALLOWED_ATTR: getSavetoWishlistObj.allowedAttrs })
}

/**
 * Sanitize HTML for subtitle/description rendering with stricter rules.
 * This function uses a more restrictive whitelist to prevent XSS attacks
 * in subtitle and description fields.
 *
 * @param {string} html The HTML string to sanitize
 * @since 2.2.0
 * @return {string} The sanitized HTML string
 */
export const sanitizeSubtitleHtml = (html: string) => {
  // Strict whitelist for subtitle/description content
  // Only allow basic formatting tags, no scripts, iframes, or event handlers
  const SUBTITLE_ALLOWED_TAGS = ['b', 'i', 'em', 'strong', 'u', 'br', 'p', 'span', 'a'];
  const SUBTITLE_ALLOWED_ATTR = ['href', 'title', 'rel', 'target'];

  return purify.sanitize(html, {
    ALLOWED_TAGS: SUBTITLE_ALLOWED_TAGS,
    ALLOWED_ATTR: SUBTITLE_ALLOWED_ATTR,
    ALLOW_DATA_ATTR: false,
    KEEP_CONTENT: true,
    // Enforce stricter parsing rules
    PARSER_MEDIA_TYPE: 'text/html',
  })
}

/**
 * Initialize WP Editor.
 *
 * @param {string} id
 * @param {Record<string, any>} settings
 */
export const initWpEditor = (id: string, settings: any = {}) => {
  onMounted(() => {
    // Check if editor is already initialized to prevent duplicates using TinyMCE API
    if (window.tinymce && window.tinymce.get(id)) {
      return
    }

    window.wp.editor.initialize(
      id,
      {
        ...window.wp.editor.getDefaultSettings(),
        ...settings,
      }
    )
  })

  onUnmounted(() => {
    // Safely remove editor if it exists
    if (window.wp.editor && window.wp.editor.remove) {
      window.wp.editor.remove(id)
    }
  })
}

/**
 * Filter the value format.
 * 
 * @return {string}
 */
export const filterFieldValue = (type: string, value: any) => {
  if (type === 'number' || type === 'percent') {
    return parseFloat(value)
  }

  return value
}

/**
 * Sets up axios headers.
 */
export const setupAxiosHeaders = () => {
  const headers: AxiosRequestConfig['headers'] = {
    'Content-Type': 'application/json;charset=UTF-8',
    'X-WP-Nonce': getSavetoWishlistObj.wpNonce,
  };

  return headers;
}

/**
 * Send request to the server.
 */
export const sendRequest = async (data: any = {}, showList: boolean = false) => {
  const headers = setupAxiosHeaders();

  const response = await axios.post(getSavetoWishlistObj.rest_save_url, data, { headers })
  let typeMessage = 'success'
  let title = 'Success'
  if (response.data.status === 'error') {
    typeMessage = 'error'
    title = 'Failed'
  }

  if (showList) {
    return response.data.list
  }

  return {
    type: typeMessage,
    title: __(title, 'saveto-wishlist-lite-for-woocommerce'),
    message: response.data.message
  }
}

/**
 * Send action button request to the server.
 */
export const triggerActionRequest = async (data: any = {}) => {
  const headers = setupAxiosHeaders();

  const response = await axios.post(getSavetoWishlistObj.rest_action_url, data, { headers });
  let typeMessage = 'success';
  let title = 'Success';
  if (response.data.status === 'error') {
    typeMessage = 'error';
    title = 'Failed';
  }

  if (response.data?.redirect) {
    window.location.href = response.data.redirect
  }

  return {
    type: typeMessage,
    title: __(title, 'saveto-wishlist-lite-for-woocommerce'),
    message: response.data.message,
    extra: response.data?.extra
  }
}

/**
 * Decode string/HTML entities.
 */
export const decodeEntities = (str: string) => {
  return he.decode(str);
}

/**
 * Get the value from url params.
 */
export const getParamValue = (name: string) => {
  const urlParams = new URLSearchParams(window.location.search);
  return urlParams.get(name);
}

/**
 * Set the value to url params.
 */
export const setParamValue = (name: string, value: any) => {
  const currentUrl = new URL(window.location.href);
  const searchParams = currentUrl.searchParams;
  if (value !== '') {
    searchParams.set(name, value);
  } else {
    searchParams.delete(name);
  }
  currentUrl.search = searchParams.toString();

  // Navigate to the updated URL.
  window.history.replaceState({}, '', currentUrl.href);
}

/**
 * Check if tab is exists.
 */
export const isTabExists = (tab: string | null, tabs: object) => {
  if (tab !== null && Object.prototype.hasOwnProperty.call(tabs, tab)) {
    return tab;
  } else {
    const keys = Object.keys(tabs);
    return keys[0];
  }
}

/***************************************************************************
 * Get translation from wwofObj.
 ***************************************************************************
 *
 * Get translation from wwofObj or return default text. In the event that
 * `wwofObj.i18n` or given `key` in it is not available, the default text
 * will be returned.
 *
 * This also allows us to remove the `i18n` object from the window object
 * and use the direct translation function instead if ever WPML fully
 * supports translation of JS files.
 *
 * @param {string} key The key of the translation from the
 * window.wwofObj.i18n property.
 *
 * @param {string} defaultText The default text to return if the key is not
 * found.
 *
 * @since 3.0
 */
export const getI18n = (key: string, defaultText: string = '') => {
  const keys = key.split('.')
  let value: any = getSavetoWishlistObj?.i18n
  for (const k of keys) {
    if (value == null || typeof value !== 'object') return defaultText
    value = value[k]
  }
  return value || defaultText
}

/**
 * Helper function to get wpayObj from window as it seems, `<template>` doesn't like access to window object.
 */
export const l10nObj = () => window.savetowishlistObj

/**
 * Remove items from other array.
 * 
 * @param {any[]} array1 Array first set.
 * @param {any[]} array2 Array second set.
 * 
 * @return {any[]} The new array.
 */
export const removeItemsFromArray = (array1: any[], array2: any[]) => {
  for (let key in array2) {
    if (typeof array1[key] !== 'undefined' && array1[key] !== null) {
      delete array1[key];
    }
  }
  return array1
}

/**
 * Normalize quantity.
 *
 * @param {number | string} qty
 *
 * @return {number}
 */
export const normalizeQuantity = (qty: number | string): number => {
  const num = typeof qty === 'string' ? parseInt(qty, 10) : qty
  return isNaN(num) || num < 1 ? 1 : Math.floor(num)
}

/**
 * Compute the next quantity after stepping by `delta`.
 * Returns the new quantity, or `null` if the quantity would not change
 * (e.g., decrementing when already at the minimum of 1).
 *
 * @param {{ quantity: number | string }} item
 * @param {number} delta
 *
 * @return {number | null}
 */
export const applyQuantityStep = (
  item: { quantity: number | string },
  delta: number,
): number | null => {
  const current = normalizeQuantity(item.quantity)
  const next = Math.max(1, current + delta)
  return next === current ? null : next
}

/**
 * Whether an item's quantity is at the minimum (1).
 *
 * @param {{ quantity: number | string }} item
 *
 * @return {boolean}
 */
export const isMinQuantity = (item: { quantity: number | string }): boolean => {
  return normalizeQuantity(item.quantity) <= 1
}

/**
 * Resolve a condition value using a 3-tier fallback:
 *   1. conditionalValues (user interactions in current session) — optional
 *   2. inputValues (saved/loaded settings)
 *   3. The control's own default value
 *
 * @param {{ key: string }} cItem          The condition descriptor containing the key to look up.
 * @param {Record<string, any>} inputValues Saved setting values keyed by control id.
 * @param {any[]} controls                  The full list of control definitions.
 * @param {Record<string, any>} [conditionalValues] Runtime interaction values; omit to skip tier 1.
 *
 * @return {any} The resolved value.
 */
export const resolveConditionValue = (
  cItem: { key: string },
  inputValues: Record<string, any>,
  controls: any[],
  conditionalValues?: Record<string, any>
): any => {
  if (conditionalValues !== undefined && typeof conditionalValues[cItem.key] !== 'undefined') {
    return conditionalValues[cItem.key]
  }
  if (typeof inputValues[cItem.key] !== 'undefined' && inputValues[cItem.key] !== null) {
    return inputValues[cItem.key]
  }
  return controls.find((c: any) => c.id === cItem.key)?.['default']
}