// Type definitions
import type {
  ComponentObject,
  ImageObject,
  TimerHandle,
  MutationObserver as MutationObserverType,
  TailwindColors,
  TailwindOpacities,
  TailwindFontSizes,
  TailwindFontStyles,
  TailwindPaddingAndMargin,
  TailwindBorderRadius,
  TailwindBorderStyleWidthColor,
  PageBuilderConfig,
} from '../types'
import type { usePageBuilderStateStore } from '../stores/page-builder-state'

import tailwindColors from '../utils/builder/tailwaind-colors'
import tailwindOpacities from '../utils/builder/tailwind-opacities'
import tailwindFontSizes from '../utils/builder/tailwind-font-sizes'
import tailwindFontStyles from '../utils/builder/tailwind-font-styles'
import tailwindPaddingAndMargin from '../utils/builder/tailwind-padding-margin'
import tailwindBorderRadius from '../utils/builder/tailwind-border-radius'
import tailwindBorderStyleWidthPlusColor from '../utils/builder/tailwind-border-style-width-color'
import { computed, ref, nextTick } from 'vue'
import type { ComputedRef } from 'vue'
import { v4 as uuidv4 } from 'uuid'
import { delay } from './delay'

class PageBuilderClass {
  // Class properties with types
  private nextTick: Promise<void>
  private containsPagebuilder: Element | null
  private pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>
  private getTextAreaVueModel: ComputedRef<string | null>
  private getLocalStorageItemName: ComputedRef<string | null>
  private getCurrentImage: ComputedRef<ImageObject>
  private getHyberlinkEnable: ComputedRef<boolean>
  private getComponents: ComputedRef<ComponentObject[] | null>
  private getComponent: ComputedRef<ComponentObject | null>
  private getElement: ComputedRef<HTMLElement | null>
  private getNextSibling: ComputedRef<HTMLElement | null>
  private getParentElement: ComputedRef<HTMLElement | null>
  private getRestoredElement: ComputedRef<string | null>
  private getComponentArrayAddMethod: ComputedRef<string | null>
  private NoneListernesTags: string[]
  private delay: (ms?: number) => Promise<void>
  private observer?: MutationObserverType

  constructor(pageBuilderStateStore: ReturnType<typeof usePageBuilderStateStore>) {
    this.nextTick = nextTick()

    this.containsPagebuilder = document.querySelector('#contains-pagebuilder')

    this.pageBuilderStateStore = pageBuilderStateStore

    this.getTextAreaVueModel = computed(() => this.pageBuilderStateStore.getTextAreaVueModel)
    this.getLocalStorageItemName = computed(
      () => this.pageBuilderStateStore.getLocalStorageItemName,
    )

    this.getCurrentImage = computed(() => this.pageBuilderStateStore.getCurrentImage)
    this.getHyberlinkEnable = computed(() => this.pageBuilderStateStore.getHyberlinkEnable)
    this.getComponents = computed(() => this.pageBuilderStateStore.getComponents)

    this.getComponent = computed(() => this.pageBuilderStateStore.getComponent)

    this.getElement = computed(() => this.pageBuilderStateStore.getElement)
    this.getNextSibling = computed(() => this.pageBuilderStateStore.getNextSibling)
    this.getParentElement = computed(() => this.pageBuilderStateStore.getParentElement)
    this.getRestoredElement = computed(() => this.pageBuilderStateStore.getRestoredElement)

    this.getComponentArrayAddMethod = computed(
      () => this.pageBuilderStateStore.getComponentArrayAddMethod,
    )

    this.NoneListernesTags = [
      'P',
      'H1',
      'H2',
      'H3',
      'H4',
      'H5',
      'H6',
      'IFRAME',
      'UL',
      'OL',
      'LI',
      'EM',
      'STRONG',
      'B',
      'A',
      'SPAN',
      'BLOCKQUOTE',
      'BR',
    ]

    this.delay = delay
  }

  // Load existing content from HTML when in update mode
  async clearHtmlSelection(): Promise<void> {
    this.pageBuilderStateStore.setComponent(null)
    this.pageBuilderStateStore.setElement(null)
    await this.#removeHoveredAndSelected()
  }
  // Load existing content from HTML when in update mode
  setConfigPageBuilder(data: PageBuilderConfig): void {
    this.pageBuilderStateStore.setConfigPageBuilder(data)
  }

  #applyElementClassChanges(
    selectedCSS: string | undefined,
    CSSArray: string[],
    mutationName: string,
  ): string | undefined {
    const currentHTMLElement = this.getElement.value
    if (!currentHTMLElement) return

    const currentCSS = CSSArray.find((CSS) => {
      return currentHTMLElement.classList.contains(CSS)
    })

    // set to 'none' if undefined
    let elementClass = currentCSS || 'none'

    // If selectedCSS is undefined, just set the current state and return
    if (selectedCSS === undefined) {
      if (typeof mutationName === 'string' && mutationName.length > 2) {
        ;(this.pageBuilderStateStore as any)[mutationName](elementClass)
      }
      return currentCSS
    }

    // selectedCSS examples: bg-zinc-200, px-10, rounded-full etc.
    if (typeof selectedCSS === 'string' && selectedCSS !== 'none') {
      if (elementClass && currentHTMLElement.classList.contains(elementClass)) {
        currentHTMLElement.classList.remove(elementClass)
      }

      currentHTMLElement.classList.add(selectedCSS)
      elementClass = selectedCSS
    } else if (typeof selectedCSS === 'string' && selectedCSS === 'none' && elementClass) {
      currentHTMLElement.classList.remove(elementClass)
      elementClass = selectedCSS
    }

    // Only call store mutations after all DOM manipulation is complete
    if (typeof mutationName === 'string' && mutationName.length > 2) {
      ;(this.pageBuilderStateStore as any)[mutationName](elementClass)
      this.pageBuilderStateStore.setElement(currentHTMLElement)
    }

    return currentCSS
  }

  #applyHelperCSSToElements(element: HTMLElement): void {
    this.#wrapElementInDivIfExcluded(element)

    if (element.tagName === 'IMG') {
      element.classList.add('smooth-transition')
    }

    // Add padding to DIV
    if (element.tagName === 'DIV') {
      if (element.classList.length === 0) {
        // element.classList.add("p-2");
      }
    }
  }

  #wrapElementInDivIfExcluded(element: HTMLElement): void {
    if (!element) return

    if (
      this.NoneListernesTags.includes(element.tagName) &&
      ((element.previousElementSibling && element.previousElementSibling.tagName === 'IMG') ||
        (element.nextElementSibling && element.nextElementSibling.tagName === 'IMG'))
    ) {
      const divWrapper = document.createElement('div')
      element.parentNode?.insertBefore(divWrapper, element)
      divWrapper.appendChild(element)
    }
  }

  #handleElementClick = async (e: Event, element: HTMLElement): Promise<void> => {
    e.preventDefault()
    e.stopPropagation()

    const pagebuilder = document.querySelector('#pagebuilder')

    if (!pagebuilder) return

    this.pageBuilderStateStore.setMenuRight(true)

    const selectedElement = pagebuilder.querySelector('[selected]')
    if (selectedElement) {
      selectedElement.removeAttribute('selected')
    }

    element.removeAttribute('hovered')

    element.setAttribute('selected', '')

    this.pageBuilderStateStore.setElement(element)
  }

  #handleMouseOver = (e: Event, element: HTMLElement): void => {
    e.preventDefault()
    e.stopPropagation()

    const pagebuilder = document.querySelector('#pagebuilder')

    if (!pagebuilder) return

    const hoveredElement = pagebuilder.querySelector('[hovered]')
    if (hoveredElement) {
      hoveredElement.removeAttribute('hovered')
    }

    if (!element.hasAttribute('selected')) {
      element.setAttribute('hovered', '')
    }
  }

  #handleMouseLeave = (e: Event): void => {
    e.preventDefault()
    e.stopPropagation()

    const pagebuilder = document.querySelector('#pagebuilder')
    if (!pagebuilder) return

    const hoveredElement = pagebuilder.querySelector('[hovered]')
    if (hoveredElement) {
      hoveredElement.removeAttribute('hovered')
    }
  }

  isEditableElement(el: Element | null): boolean {
    if (!el) return false
    return !this.NoneListernesTags.includes(el.tagName)
  }

  /**
   * The function is used to
   * attach event listeners to each element within a 'section'
   */
  setEventListenersForElements = async () => {
    const elementsWithListeners = new WeakSet<Element>()

    const pagebuilder = document.querySelector('#pagebuilder')

    if (!pagebuilder) return

    // Wait for any pending DOM updates
    await nextTick()
    await new Promise((resolve) => requestAnimationFrame(resolve))

    pagebuilder.querySelectorAll('section *').forEach((element) => {
      // exclude NoneListernesTags && additional Tags for not listening
      if (this.isEditableElement(element)) {
        if (elementsWithListeners && !elementsWithListeners.has(element)) {
          elementsWithListeners.add(element)
          // Type assertion to HTMLElement since we know these are DOM elements
          const htmlElement = element as HTMLElement
          // Attach event listeners directly to individual elements
          htmlElement.addEventListener('click', (e) => this.#handleElementClick(e, htmlElement))
          htmlElement.addEventListener('mouseover', (e) => this.#handleMouseOver(e, htmlElement))
          htmlElement.addEventListener('mouseleave', (e) => this.#handleMouseLeave(e))
        }
      }

      // end for each iterating over elements
    })
  }

  handleAutoSave = async () => {
    const passedConfig = this.pageBuilderStateStore.getConfigPageBuilder

    // Check if config is set
    if (passedConfig && passedConfig.userSettings) {
      //
      // Enabled auto save
      if (
        typeof passedConfig.userSettings.autoSave === 'boolean' &&
        passedConfig.userSettings.autoSave !== false
      ) {
        if (this.pageBuilderStateStore.getIsSaving) return

        try {
          this.pageBuilderStateStore.setIsSaving(true)
          await this.delay(1000)
          await this.saveComponentsLocalStorage()
        } catch (err) {
          console.error('Error trying auto save.', err)
        } finally {
          this.pageBuilderStateStore.setIsSaving(false)
        }
      }
    }
    if (passedConfig && !passedConfig.userSettings) {
      try {
        this.pageBuilderStateStore.setIsSaving(true)
        await this.delay(200)

        await this.saveComponentsLocalStorage()
      } catch (err) {
        console.error('Error trying saving.', err)
      } finally {
        this.pageBuilderStateStore.setIsSaving(false)
      }
    }
  }

  handleManualSave = async () => {
    const passedConfig = this.pageBuilderStateStore.getConfigPageBuilder

    // Check if config is set
    if (passedConfig && passedConfig.userSettings) {
      //
      //
      // Enabled auto save
      if (
        (typeof passedConfig.userSettings.autoSave === 'boolean' &&
          !passedConfig.userSettings.autoSave) ||
        (typeof passedConfig.userSettings.autoSave === 'boolean' &&
          passedConfig.userSettings.autoSave)
      ) {
        this.pageBuilderStateStore.setIsSaving(true)
        await this.delay(200)

        await this.saveComponentsLocalStorage()

        this.pageBuilderStateStore.setIsSaving(false)
      }
    }
    if (passedConfig && !passedConfig.userSettings) {
      this.pageBuilderStateStore.setIsSaving(true)
      await this.delay(200)

      await this.saveComponentsLocalStorage()

      this.pageBuilderStateStore.setIsSaving(false)
    }
  }

  cloneCompObjForDOMInsertion(componentObject: ComponentObject): ComponentObject {
    // Deep clone clone component
    const clonedComponent = { ...componentObject }

    //  scoll to top or bottom # end
    if (this.containsPagebuilder) {
      if (
        this.getComponentArrayAddMethod.value === 'unshift' ||
        this.getComponentArrayAddMethod.value === 'push'
      ) {
        // push to top
        if (this.getComponentArrayAddMethod.value === 'unshift') {
          this.containsPagebuilder.scrollTo({
            top: 0,
            behavior: 'smooth',
          })
        }

        // push to bottom
        if (this.getComponentArrayAddMethod.value === 'push') {
          const maxHeight = this.containsPagebuilder.scrollHeight
          this.containsPagebuilder.scrollTo({
            top: maxHeight,
            behavior: 'smooth',
          })
        }
      }
    }

    // Create a DOMParser instance
    const parser = new DOMParser()

    // Parse the HTML content of the clonedComponent using the DOMParser
    const doc = parser.parseFromString(clonedComponent.html_code || '', 'text/html')

    // Selects all elements within the HTML document, including elements like:
    const elements = doc.querySelectorAll('*')

    elements.forEach((element) => {
      this.#applyHelperCSSToElements(element as HTMLElement)
    })

    // Add the component id to the section element
    const section = doc.querySelector('section')
    if (section) {
      // Generate a unique ID using uuidv4() and assign it to the section
      section.dataset.componentid = uuidv4()

      // Find all images within elements with "flex" or "grid" classes inside the section
      const images = section.querySelectorAll('img')

      // Add a unique ID as a data attribute to each image element
      images.forEach((image) => {
        image.setAttribute('data-image', uuidv4())
      })

      // Update the clonedComponent id with the newly generated unique ID
      clonedComponent.id = section.dataset.componentid

      // Update the HTML content of the clonedComponent with the modified HTML
      clonedComponent.html_code = section.outerHTML
    }

    // return to the cloned element to be dropped
    return clonedComponent
  }

  async #removeHoveredAndSelected() {
    await new Promise((resolve) => requestAnimationFrame(resolve))

    const pagebuilder = document.querySelector('#pagebuilder')

    if (!pagebuilder) return

    const hoveredElement = pagebuilder.querySelector('[hovered]')
    if (hoveredElement) {
      hoveredElement.removeAttribute('hovered')
    }

    const selectedElement = pagebuilder.querySelector('[selected]')

    if (selectedElement) {
      selectedElement.removeAttribute('selected')
    }
  }

  async currentClasses() {
    await new Promise((resolve) => requestAnimationFrame(resolve))

    // convert classList to array
    const classListArray = Array.from(this.getElement.value?.classList || [])

    // commit array to store
    this.pageBuilderStateStore.setCurrentClasses(classListArray)
  }

  handleAddClasses(userSelectedClass: string): void {
    if (
      typeof userSelectedClass === 'string' &&
      userSelectedClass !== '' &&
      !userSelectedClass.includes(' ') &&
      // Check if class already exists
      !this.getElement.value?.classList.contains(userSelectedClass)
    ) {
      // Remove any leading/trailing spaces
      const cleanedClass = userSelectedClass.trim()

      this.getElement.value?.classList.add(cleanedClass)

      this.pageBuilderStateStore.setElement(this.getElement.value)

      this.pageBuilderStateStore.setClass(userSelectedClass)
    }
  }

  handleRemoveClasses(userSelectedClass: string): void {
    // remove selected class from element
    if (this.getElement.value?.classList.contains(userSelectedClass)) {
      this.getElement.value?.classList.remove(userSelectedClass)

      this.pageBuilderStateStore.setElement(this.getElement.value)
      this.pageBuilderStateStore.removeClass(userSelectedClass)
    }
  }

  handleDeleteElement() {
    // Get the element to be deleted
    const element = this.getElement.value

    if (!element) return

    if (!element?.parentNode) {
      this.pageBuilderStateStore.setComponent(null)
      this.pageBuilderStateStore.setElement(null)
      return
    }

    // Store the parent of the deleted element
    // if parent element tag is section remove the hole component
    if (element.parentElement?.tagName !== 'SECTION') {
      this.pageBuilderStateStore.setParentElement(element.parentNode as HTMLElement)

      // Store the outerHTML of the deleted element
      this.pageBuilderStateStore.setRestoredElement(element.outerHTML)

      // Store the next sibling of the deleted element
      this.pageBuilderStateStore.setNextSibling(element.nextSibling as HTMLElement | null)
    }

    // if parent element tag is section remove the hole component
    if (element.parentElement?.tagName === 'SECTION') {
      this.deleteComponent()
    }

    // Remove the element from the DOM
    element.remove()
    this.pageBuilderStateStore.setComponent(null)
    this.pageBuilderStateStore.setElement(null)
  }

  async handleRestoreElement() {
    // Get the stored deleted element and its parent
    if (this.getRestoredElement.value && this.getParentElement.value) {
      // Create a new element from the stored outerHTML
      const newElement = document.createElement('div')
      // Fixed type conversion issue
      newElement.innerHTML = this.getRestoredElement.value

      // Append the restored element to its parent
      // Insert the restored element before its next sibling in its parent
      if (newElement.firstChild && this.getParentElement.value) {
        // insertBefore can accept null as second parameter (will append to end)
        this.getParentElement.value.insertBefore(newElement.firstChild, this.getNextSibling.value)
      }
    }

    // Clear
    this.pageBuilderStateStore.setParentElement(null)
    this.pageBuilderStateStore.setRestoredElement(null)
    this.pageBuilderStateStore.setNextSibling(null)
    this.pageBuilderStateStore.setComponent(null)
    this.pageBuilderStateStore.setElement(null)

    await this.setEventListenersForElements()
  }

  handleFontWeight(userSelectedFontWeight?: string): void {
    this.#applyElementClassChanges(
      userSelectedFontWeight,
      tailwindFontStyles.fontWeight,
      'setFontWeight',
    )
  }
  handleFontFamily(userSelectedFontFamily?: string): void {
    this.#applyElementClassChanges(
      userSelectedFontFamily,
      tailwindFontStyles.fontFamily,
      'setFontFamily',
    )
  }
  handleFontStyle(userSelectedFontStyle?: string): void {
    this.#applyElementClassChanges(
      userSelectedFontStyle,
      tailwindFontStyles.fontStyle,
      'setFontStyle',
    )
  }
  handleVerticalPadding(userSelectedVerticalPadding?: string): void {
    this.#applyElementClassChanges(
      userSelectedVerticalPadding,
      tailwindPaddingAndMargin.verticalPadding,
      'setFontVerticalPadding',
    )
  }
  handleHorizontalPadding(userSelectedHorizontalPadding?: string): void {
    this.#applyElementClassChanges(
      userSelectedHorizontalPadding,
      tailwindPaddingAndMargin.horizontalPadding,
      'setFontHorizontalPadding',
    )
  }

  handleVerticalMargin(userSelectedVerticalMargin?: string): void {
    this.#applyElementClassChanges(
      userSelectedVerticalMargin,
      tailwindPaddingAndMargin.verticalMargin,
      'setFontVerticalMargin',
    )
  }
  handleHorizontalMargin(userSelectedHorizontalMargin?: string): void {
    this.#applyElementClassChanges(
      userSelectedHorizontalMargin,
      tailwindPaddingAndMargin.horizontalMargin,
      'setFontHorizontalMargin',
    )
  }

  // border color, style & width / start
  handleBorderStyle(borderStyle?: string): void {
    this.#applyElementClassChanges(
      borderStyle,
      tailwindBorderStyleWidthPlusColor.borderStyle,
      'setBorderStyle',
    )
  }
  handleBorderWidth(borderWidth?: string): void {
    this.#applyElementClassChanges(
      borderWidth,
      tailwindBorderStyleWidthPlusColor.borderWidth,
      'setBorderWidth',
    )
  }
  handleBorderColor(borderColor?: string): void {
    this.#applyElementClassChanges(
      borderColor,
      tailwindBorderStyleWidthPlusColor.borderColor,
      'setBorderColor',
    )
  }
  // border color, style & width / end

  handleBackgroundColor(color?: string): void {
    this.#applyElementClassChanges(
      color,
      tailwindColors.backgroundColorVariables,
      'setBackgroundColor',
    )
  }

  handleTextColor(color?: string): void {
    this.#applyElementClassChanges(color, tailwindColors.textColorVariables, 'setTextColor')
  }

  // border radius / start
  handleBorderRadiusGlobal(borderRadiusGlobal?: string): void {
    this.#applyElementClassChanges(
      borderRadiusGlobal,
      tailwindBorderRadius.roundedGlobal,
      'setBorderRadiusGlobal',
    )
  }
  handleBorderRadiusTopLeft(borderRadiusTopLeft?: string): void {
    this.#applyElementClassChanges(
      borderRadiusTopLeft,
      tailwindBorderRadius.roundedTopLeft,
      'setBorderRadiusTopLeft',
    )
  }
  handleBorderRadiusTopRight(borderRadiusTopRight?: string): void {
    this.#applyElementClassChanges(
      borderRadiusTopRight,
      tailwindBorderRadius.roundedTopRight,
      'setBorderRadiusTopRight',
    )
  }
  handleBorderRadiusBottomleft(borderRadiusBottomleft?: string): void {
    this.#applyElementClassChanges(
      borderRadiusBottomleft,
      tailwindBorderRadius.roundedBottomLeft,
      'setBorderRadiusBottomleft',
    )
  }
  handleBorderRadiusBottomRight(borderRadiusBottomRight?: string): void {
    this.#applyElementClassChanges(
      borderRadiusBottomRight,
      tailwindBorderRadius.roundedBottomRight,
      'setBorderRadiusBottomRight',
    )
  }
  // border radius / end

  handleFontSize(userSelectedFontSize?: string): void {
    if (!userSelectedFontSize) return
    if (!this.getElement.value) return

    let fontBase = tailwindFontSizes.fontBase.find((size: string) => {
      return this.getElement.value?.classList.contains(size)
    })
    if (fontBase === undefined) {
      fontBase = 'base:none'
    }

    let fontDesktop = tailwindFontSizes.fontDesktop.find((size: string) => {
      return this.getElement.value?.classList.contains(size)
    })
    if (fontDesktop === undefined) {
      fontDesktop = 'lg:none'
    }

    let fontTablet = tailwindFontSizes.fontTablet.find((size: string) => {
      return this.getElement.value?.classList.contains(size)
    })
    if (fontTablet === undefined) {
      fontTablet = 'md:none'
    }

    let fontMobile = tailwindFontSizes.fontMobile.find((size: string) => {
      return this.getElement.value?.classList.contains(size)
    })
    if (fontMobile === undefined) {
      fontMobile = 'sm:none'
    }

    // set fonts
    this.pageBuilderStateStore.setFontBase(fontBase)
    this.pageBuilderStateStore.setFontDesktop(fontDesktop)
    this.pageBuilderStateStore.setFontTablet(fontTablet)
    this.pageBuilderStateStore.setFontMobile(fontMobile)

    const getFontBase = computed(() => {
      return this.pageBuilderStateStore.getFontBase
    })
    const getFontDesktop = computed(() => {
      return this.pageBuilderStateStore.getFontDesktop
    })
    const getFontTablet = computed(() => {
      return this.pageBuilderStateStore.getFontTablet
    })
    const getFontMobile = computed(() => {
      return this.pageBuilderStateStore.getFontMobile
    })

    if (userSelectedFontSize !== undefined && this.getElement.value) {
      if (
        !userSelectedFontSize.includes('sm:') &&
        !userSelectedFontSize.includes('md:') &&
        !userSelectedFontSize.includes('lg:')
      ) {
        if (getFontBase.value) {
          this.getElement.value.classList.remove(getFontBase.value)
        }
        if (!userSelectedFontSize.includes('base:none')) {
          this.getElement.value.classList.add(userSelectedFontSize)
        }

        this.pageBuilderStateStore.setFontBase(userSelectedFontSize)
      }
      if (userSelectedFontSize.includes('lg:')) {
        if (getFontDesktop.value) {
          this.getElement.value.classList.remove(getFontDesktop.value)
        }
        if (!userSelectedFontSize.includes('lg:none')) {
          this.getElement.value.classList.add(userSelectedFontSize)
        }

        this.pageBuilderStateStore.setFontDesktop(userSelectedFontSize)
      }
      if (userSelectedFontSize.includes('md:')) {
        if (getFontTablet.value) {
          this.getElement.value.classList.remove(getFontTablet.value)
        }
        if (!userSelectedFontSize.includes('md:none')) {
          this.getElement.value.classList.add(userSelectedFontSize)
        }

        this.pageBuilderStateStore.setFontTablet(userSelectedFontSize)
      }
      if (userSelectedFontSize.includes('sm:')) {
        if (getFontMobile.value) {
          this.getElement.value.classList.remove(getFontMobile.value)
        }
        if (!userSelectedFontSize.includes('sm:none')) {
          this.getElement.value.classList.add(userSelectedFontSize)
        }

        this.pageBuilderStateStore.setFontMobile(userSelectedFontSize)
      }
      this.pageBuilderStateStore.setElement(this.getElement.value)
    }
  }

  handleBackgroundOpacity(opacity?: string): void {
    this.#applyElementClassChanges(
      opacity,
      tailwindOpacities.backgroundOpacities,
      'setBackgroundOpacity',
    )
  }
  handleOpacity(opacity?: string): void {
    this.#applyElementClassChanges(opacity, tailwindOpacities.opacities, 'setOpacity')
  }

  deleteAllComponents() {
    this.pageBuilderStateStore.setComponents([])
  }

  deleteComponent() {
    if (!this.getComponents.value || !this.getComponent.value) return

    // Find the index of the component to delete
    const indexToDelete = this.getComponents.value.findIndex(
      (component) => component.id === this.getComponent.value?.id,
    )

    if (indexToDelete === -1) {
      // Component not found in the array, handle this case as needed.
      return
    }

    // Remove the component from the array
    this.getComponents.value.splice(indexToDelete, 1)
    this.pageBuilderStateStore.setComponents(this.getComponents.value)

    this.pageBuilderStateStore.setComponent(null)
    this.pageBuilderStateStore.setElement(null)
  }

  // move component
  // runs when html components are rearranged
  moveComponent(direction: number): void {
    if (!this.getComponents.value || !this.getComponent.value) return

    if (this.getComponents.value.length <= 1) return

    // Get the component you want to move (replace this with your actual logic)
    const componentToMove = this.getComponent.value

    // Determine the new index where you want to move the component
    const currentIndex = this.getComponents.value.findIndex(
      (component) => component.id === componentToMove.id,
    )

    if (currentIndex === -1) {
      // Component not found in the array, handle this case as needed.
      return
    }

    const newIndex = currentIndex + direction

    // Ensure the newIndex is within bounds
    if (newIndex < 0 || newIndex >= this.getComponents.value.length) {
      return
    }

    // Move the component to the new position
    this.getComponents.value.splice(currentIndex, 1)
    this.getComponents.value.splice(newIndex, 0, componentToMove)
  }

  ensureTextAreaHasContent = () => {
    if (!this.getElement.value) return

    // text content
    if (typeof this.getElement.value.innerHTML !== 'string') {
      return
    }
    const element = this.getElement.value
    const elementTag = element.tagName

    if (
      ['DIV'].includes(elementTag) &&
      element.tagName.toLowerCase() !== 'img' &&
      element.textContent &&
      Number(element.textContent.length) === 0
    ) {
      element.classList.add('h-6')
      element.classList.add('bg-red-50')
    } else {
      element.classList.remove('h-6')
      element.classList.remove('bg-red-50')
    }
  }

  //
  handleTextInput = async (textContentVueModel: string): Promise<void> => {
    // // text content
    if (typeof this.getElement.value?.innerHTML !== 'string') {
      return
    }

    if (typeof this.getElement.value.innerHTML === 'string') {
      await this.nextTick

      // Update text content
      this.getElement.value.textContent = textContentVueModel

      this.pageBuilderStateStore.setTextAreaVueModel(this.getElement.value.innerHTML)

      this.getElement.value.innerHTML = textContentVueModel
    }

    this.ensureTextAreaHasContent()
  }

  //
  //
  ElOrFirstChildIsIframe() {
    if (
      this.getElement.value?.tagName === 'IFRAME' ||
      this.getElement.value?.firstElementChild?.tagName === 'IFRAME'
    ) {
      return true
    } else {
      return false
    }
  }
  //
  //
  //
  selectedElementIsValidText() {
    let reachedElseStatement = false

    // Get all child elements of the parentDiv
    const childElements = this.getElement.value?.children
    if (
      this.getElement.value?.tagName === 'IMG' ||
      this.getElement.value?.firstElementChild?.tagName === 'IFRAME'
    ) {
      return
    }
    if (!childElements) {
      return
    }

    Array.from(childElements).forEach((element) => {
      if (element?.tagName === 'IMG' || element?.tagName === 'DIV') {
        reachedElseStatement = false
      } else {
        reachedElseStatement = true
      }
    })

    return reachedElseStatement
  }

  previewCurrentDesign() {
    this.pageBuilderStateStore.setElement(null)

    const pagebuilder = document.querySelector('#pagebuilder')

    if (!pagebuilder) return

    const addedHtmlComponents = ref<string[]>([])
    // preview current design in external browser tab
    // iterate over each top-level section component within pagebuilder only
    pagebuilder.querySelectorAll('section:not(section section)').forEach((section) => {
      // remove hovered and selected

      // remove hovered
      const hoveredElement = section.querySelector('[hovered]')
      if (hoveredElement) {
        hoveredElement.removeAttribute('hovered')
      }

      // remove selected
      const selectedElement = section.querySelector('[selected]')
      if (selectedElement) {
        selectedElement.removeAttribute('selected')
      }

      // push outer html into the array
      addedHtmlComponents.value.push(section.outerHTML)
    })

    // stringify added html components
    const stringifiedComponents = JSON.stringify(addedHtmlComponents.value)

    // commit
    this.pageBuilderStateStore.setCurrentLayoutPreview(stringifiedComponents)

    // set added html components back to empty array
    addedHtmlComponents.value = []

    //
  }

  // Helper function to sanitize title for localStorage key
  private sanitizeForLocalStorage(input: string): string {
    return input
      .trim() // Remove leading/trailing spaces
      .toLowerCase() // Convert to lowercase
      .replace(/\s+/g, '-') // Replace one or more spaces with single hyphen
      .replace(/[^a-z0-9-]/g, '') // Remove all non-alphanumeric characters except hyphens
      .replace(/-+/g, '-') // Replace multiple consecutive hyphens with single hyphen
      .replace(/^-+|-+$/g, '') // Remove leading/trailing hyphens
  }

  updateLocalStorageItemName(): void {
    const updateOrCreate = this.pageBuilderStateStore.getConfigPageBuilder?.updateOrCreate?.formType

    const resourceData = this.pageBuilderStateStore.getConfigPageBuilder?.resourceData

    const resourceFormName =
      this.pageBuilderStateStore.getConfigPageBuilder?.updateOrCreate?.formName

    // Logic for create resource
    if (updateOrCreate === 'create') {
      if (resourceFormName && resourceFormName.length > 0) {
        this.pageBuilderStateStore.setLocalStorageItemName(
          `page-builder-create-resource-${this.sanitizeForLocalStorage(resourceFormName)}`,
        )
        return
      }

      this.pageBuilderStateStore.setLocalStorageItemName(`page-builder-create-resource`)
      return
    }

    // Logic for create
    // Logic for update and with resource form name
    if (updateOrCreate === 'update') {
      //
      //
      //
      if (typeof resourceFormName === 'string' && resourceFormName.length > 0) {
        //
        //
        if (resourceData && resourceData != null && !resourceData.title) {
          // Check if id is missing, null, undefined, or an empty string (after trimming)
          if (!resourceData.id || typeof resourceData.id === 'string') {
            this.pageBuilderStateStore.setLocalStorageItemName(
              `page-builder-update-resource-${this.sanitizeForLocalStorage(resourceFormName)}`,
            )
            return
          }
        }

        // Runs when resourceData has title but no ID
        if (resourceData && resourceData != null) {
          if (
            resourceData.title &&
            typeof resourceData.title === 'string' &&
            resourceData.title.length > 0
          ) {
            if (!resourceData.id || typeof resourceData.id === 'string') {
              this.pageBuilderStateStore.setLocalStorageItemName(
                `page-builder-update-resource-${this.sanitizeForLocalStorage(resourceFormName)}-${this.sanitizeForLocalStorage(resourceData.title)}`,
              )
              return
            }
          }
        }

        // Runs when resourceData has ID but no title
        if (resourceData && resourceData != null) {
          if (!resourceData.title && typeof resourceData.title !== 'string') {
            if (resourceData.id || typeof resourceData.id === 'number') {
              this.pageBuilderStateStore.setLocalStorageItemName(
                `page-builder-update-resource-${this.sanitizeForLocalStorage(resourceFormName)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
              )
              return
            }
          }
        }

        // Runs when resourceData has both title and ID
        if (resourceData && resourceData != null) {
          if (
            resourceData.title &&
            typeof resourceData.title === 'string' &&
            resourceData.title.length > 0
          ) {
            if (resourceData.id || typeof resourceData.id === 'number') {
              this.pageBuilderStateStore.setLocalStorageItemName(
                `page-builder-update-resource-${this.sanitizeForLocalStorage(resourceFormName)}-${this.sanitizeForLocalStorage(resourceData.title)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
              )
              return
            }
          }
        }
      }

      // Logic for update without without resourceFormName
      if (
        !resourceFormName ||
        (typeof resourceFormName === 'string' && resourceFormName.length === 0)
      ) {
        //
        //
        if (resourceData && resourceData != null && !resourceData.title) {
          // Check if id is missing, null, undefined, or an empty string (after trimming)
          if (!resourceData.id || typeof resourceData.id === 'string') {
            this.pageBuilderStateStore.setLocalStorageItemName(`page-builder-update-resource`)
            return
          }
        }

        // Runs when resourceData has title but no ID
        if (resourceData && resourceData != null) {
          if (
            resourceData.title &&
            typeof resourceData.title === 'string' &&
            resourceData.title.length > 0
          ) {
            if (!resourceData.id || typeof resourceData.id === 'string') {
              this.pageBuilderStateStore.setLocalStorageItemName(
                `page-builder-update-resource-${this.sanitizeForLocalStorage(resourceData.title)}`,
              )
              return
            }
          }
        }

        // Runs when resourceData has ID but no title
        if (resourceData && resourceData != null) {
          if (!resourceData.title && typeof resourceData.title !== 'string') {
            if (resourceData.id || typeof resourceData.id === 'number') {
              this.pageBuilderStateStore.setLocalStorageItemName(
                `page-builder-update-resource-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
              )
              return
            }
          }
        }

        // Runs when resourceData has both title and ID
        if (resourceData && resourceData != null) {
          if (
            resourceData.title &&
            typeof resourceData.title === 'string' &&
            resourceData.title.length > 0
          ) {
            if (resourceData.id || typeof resourceData.id === 'number') {
              this.pageBuilderStateStore.setLocalStorageItemName(
                `page-builder-update-resource-${this.sanitizeForLocalStorage(resourceData.title)}-${this.sanitizeForLocalStorage(String(resourceData.id))}`,
              )
              return
            }
          }
        }
      }
    }
  }

  /**
   * Components from DOM → JS (not JS → DOM).
   * Saving the current DOM state into JS this.getComponents (for example, before saving to localStorage).
   * This function Only copies the current DOM HTML into JS this.getComponents (component.html_code).
   */
  #domToComponentsSync = async () => {
    const pagebuilder = document.querySelector('#pagebuilder')
    if (!pagebuilder) return

    const hoveredElement = pagebuilder.querySelector('[hovered]')

    if (hoveredElement) {
      hoveredElement.removeAttribute('hovered')
    }

    const componentsToSave: { html_code: string; id: string | null; title: string }[] = []

    pagebuilder.querySelectorAll('section[data-componentid]').forEach((section) => {
      componentsToSave.push({
        html_code: section.outerHTML,
        id: section.getAttribute('data-componentid'),
        title:
          section.getAttribute('title') ||
          section.getAttribute('data-componentid') ||
          'Untitled Component',
      })
    })

    if (this.getLocalStorageItemName.value) {
      localStorage.setItem(this.getLocalStorageItemName.value, JSON.stringify(componentsToSave))
    }

    // No DOM mutation here!
    await new Promise<void>((resolve) => resolve())
  }

  // save to local storage
  async saveComponentsLocalStorage() {
    await this.nextTick

    await this.#domToComponentsSync()
  }

  async removeItemComponentsLocalStorageCreate() {
    await this.nextTick

    if (this.getLocalStorageItemName.value) {
      localStorage.removeItem(this.getLocalStorageItemName.value)
    }
  }

  async removeItemComponentsLocalStorageUpdate() {
    if (this.getLocalStorageItemName.value) {
      localStorage.removeItem(this.getLocalStorageItemName.value)
    }
  }

  areComponentsStoredInLocalStorage() {
    if (!this.getLocalStorageItemName.value) return false

    if (
      this.getLocalStorageItemName.value &&
      localStorage.getItem(this.getLocalStorageItemName.value)
    ) {
      const savedCurrentDesign = localStorage.getItem(this.getLocalStorageItemName.value)

      if (savedCurrentDesign) {
        return savedCurrentDesign
      }
    }

    return false
  }
  //
  //
  async updateBasePrimaryImage(data?: { type: string }): Promise<void> {
    if (!this.getElement.value) return

    // If no data provided, apply current image if available (new simplified usage)
    if (this.getCurrentImage.value && this.getCurrentImage.value.src) {
      await this.nextTick
      this.pageBuilderStateStore.setBasePrimaryImage(`${this.getCurrentImage.value.src}`)
      await this.handleAutoSave()
    }
  }

  showBasePrimaryImage() {
    if (!this.getElement.value) return

    const currentImageContainer = document.createElement('div')

    currentImageContainer.innerHTML = this.getElement.value.outerHTML

    // Get all img and div within the current image container
    const imgElements = currentImageContainer.getElementsByTagName('img')
    const divElements = currentImageContainer.getElementsByTagName('div')

    // Check if there is exactly one img and no div
    if (imgElements.length === 1 && divElements.length === 0) {
      // Return the source of the only img

      this.pageBuilderStateStore.setBasePrimaryImage(imgElements[0].src)

      return
    }

    this.pageBuilderStateStore.setBasePrimaryImage(null)
  }

  #addHyperlinkToElement(
    hyperlinkEnable: boolean,
    urlInput: string | null,
    openHyperlinkInNewTab: boolean,
  ) {
    if (!this.getElement.value) return

    // Check if element is a proper DOM element and has closest method
    if (
      !(this.getElement.value instanceof HTMLElement) ||
      typeof this.getElement.value.closest !== 'function'
    )
      return

    const parentHyperlink = this.getElement.value.closest('a')
    const hyperlink = this.getElement.value.querySelector('a')

    this.pageBuilderStateStore.setHyperlinkError(null)

    // url validation
    const urlRegex = /^https?:\/\//

    const isValidURL = ref(true)

    if (hyperlinkEnable === true && urlInput !== null) {
      isValidURL.value = urlRegex.test(urlInput)
    }

    if (isValidURL.value === false) {
      this.pageBuilderStateStore.setHyperlinkMessage(null)

      this.pageBuilderStateStore.setHyperlinkError('URL is not valid')
      return
    }

    if (hyperlinkEnable === true && typeof urlInput === 'string') {
      // check if element contains child hyperlink tag
      // updated existing url
      if (hyperlink !== null && urlInput.length !== 0) {
        hyperlink.href = urlInput

        // Conditionally set the target attribute if openHyperlinkInNewTab is true
        if (openHyperlinkInNewTab === true) {
          hyperlink.target = '_blank'
        }
        if (openHyperlinkInNewTab === false) {
          hyperlink.removeAttribute('target')
        }

        hyperlink.textContent = this.getElement.value.textContent

        this.pageBuilderStateStore.setHyperlinkMessage('Succesfully updated element hyperlink')

        this.pageBuilderStateStore.setElementContainsHyperlink(true)

        return
      }

      // check if element contains child a tag
      if (hyperlink === null && urlInput.length !== 0) {
        // add a href
        if (parentHyperlink === null) {
          const link = document.createElement('a')
          link.href = urlInput

          // Conditionally set the target attribute if openHyperlinkInNewTab is true
          if (openHyperlinkInNewTab === true) {
            link.target = '_blank'
          }

          link.textContent = this.getElement.value.textContent
          this.getElement.value.textContent = ''
          this.getElement.value.appendChild(link)

          this.pageBuilderStateStore.setHyperlinkMessage('Successfully added hyperlink to element')

          this.pageBuilderStateStore.setElementContainsHyperlink(true)

          return
        }
      }
      //
    }

    if (hyperlinkEnable === false && urlInput === 'removeHyperlink') {
      // To remove the added hyperlink tag
      const originalText = this.getElement.value.textContent || ''
      const textNode = document.createTextNode(originalText)
      this.getElement.value.textContent = ''
      this.getElement.value.appendChild(textNode)

      this.pageBuilderStateStore.setHyberlinkEnable(false)
      this.pageBuilderStateStore.setElementContainsHyperlink(false)
    }
  }

  #checkForHyperlink() {
    if (!this.getElement.value) return

    const hyperlink = this.getElement.value.querySelector('a')
    if (hyperlink !== null) {
      this.pageBuilderStateStore.setHyberlinkEnable(true)
      this.pageBuilderStateStore.setElementContainsHyperlink(true)
      this.pageBuilderStateStore.setHyperlinkInput(hyperlink.href)
      this.pageBuilderStateStore.setHyperlinkMessage(null)
      this.pageBuilderStateStore.setHyperlinkError(null)

      if (hyperlink.target === '_blank') {
        this.pageBuilderStateStore.setOpenHyperlinkInNewTab(true)
      }
      if (hyperlink.target !== '_blank') {
        this.pageBuilderStateStore.setOpenHyperlinkInNewTab(false)
      }

      return
    }

    this.pageBuilderStateStore.setElementContainsHyperlink(false)
    this.pageBuilderStateStore.setHyperlinkInput('')
    this.pageBuilderStateStore.setHyperlinkError(null)
    this.pageBuilderStateStore.setHyperlinkMessage(null)
    this.pageBuilderStateStore.setHyberlinkEnable(false)
  }

  handleHyperlink(
    hyperlinkEnable?: boolean,
    urlInput?: string | null,
    openHyperlinkInNewTab?: boolean,
  ): void {
    this.pageBuilderStateStore.setHyperlinkAbility(true)

    if (!this.getElement.value) return

    // Check if element is a proper DOM element and has closest method
    if (
      !(this.getElement.value instanceof HTMLElement) ||
      typeof this.getElement.value.closest !== 'function'
    )
      return

    const parentHyperlink = this.getElement.value.closest('a')

    // handle case where parent element already has an a href tag
    // when clicking directly on a hyperlink
    if (parentHyperlink !== null) {
      this.pageBuilderStateStore.setHyperlinkAbility(false)
    }
    //
    const elementTag = this.getElement.value?.tagName.toUpperCase()

    if (
      elementTag !== 'P' &&
      elementTag !== 'H1' &&
      elementTag !== 'H2' &&
      elementTag !== 'H3' &&
      elementTag !== 'H4' &&
      elementTag !== 'H5' &&
      elementTag !== 'H6'
    ) {
      this.pageBuilderStateStore.setHyperlinkAbility(false)
    }

    if (hyperlinkEnable === undefined) {
      this.#checkForHyperlink()
      return
    }

    this.#addHyperlinkToElement(hyperlinkEnable, urlInput || null, openHyperlinkInNewTab || false)
  }

  // Helper method for custom components to easily add components
  async addComponent(componentObject: ComponentObject): Promise<void> {
    try {
      const clonedComponent = this.cloneCompObjForDOMInsertion({
        html_code: componentObject.html_code,
        id: componentObject.id,
        title: componentObject.title,
      })

      this.pageBuilderStateStore.setPushComponents({
        component: clonedComponent,
        componentArrayAddMethod: this.getComponentArrayAddMethod.value || 'push',
      })

      // Wait for the DOM to update before setting event listeners
      await nextTick()
      await this.setEventListenersForElements()
    } catch (error) {
      console.error('Error adding component:', error)
    }
  }

  /**
   * Parse and set components from JSON or HTML data
   *
   * Supports:
   * - JSON: Array of ComponentObject with html_code, id, title
   * - HTML: String containing <section data-componentid="..."> elements
   *
   * Auto-detects format and parses accordingly
   *
   * @param data - JSON string (e.g., '[{"html_code":"...","id":"123","title":"..."}]')
   *               OR HTML string (e.g., '<section data-componentid="123">...</section>')
   */
  setComponentsFromData(data: string): void {
    // Auto-detect if input is JSON or HTML
    const trimmedData = data.trim()

    if (trimmedData.startsWith('[') || trimmedData.startsWith('{')) {
      // Looks like JSON - parse as JSON
      this.#parseJSONComponents(trimmedData)
    } else if (trimmedData.startsWith('<')) {
      // Looks like HTML - parse as HTML
      this.#parseHTMLComponents(trimmedData)
    } else {
      this.#parseJSONComponents(trimmedData)
    }
  }

  toggleTipTapModal(status: boolean): void {
    this.pageBuilderStateStore.setShowModalTipTap(status)

    if (!status) {
      this.handleAutoSave()
    }
  }

  // Private method to parse JSON components
  #parseJSONComponents(jsonData: string): void {
    try {
      const parsedData = JSON.parse(jsonData)
      let savedCurrentDesign: ComponentObject[] = []

      // Load ComponentObjects from parsed data
      if (Array.isArray(parsedData) && parsedData.length > 0) {
        // Data is always ComponentObjects with html_code, id, and title
        savedCurrentDesign = parsedData
      }

      this.pageBuilderStateStore.setComponents(savedCurrentDesign)
    } catch (error) {
      console.error('Error parsing JSON components:', error)
      // Set empty array on error to ensure consistent state
      this.pageBuilderStateStore.setComponents([])
    }
  }

  // Private method to parse HTML components
  #parseHTMLComponents(htmlData: string): void {
    try {
      // Parse the HTML content using DOMParser
      const parser = new DOMParser()
      const doc = parser.parseFromString(htmlData, 'text/html')

      // Select all <section> elements with data-componentid attribute
      const sectionElements = doc.querySelectorAll('section[data-componentid]')

      const extractedSections: ComponentObject[] = []
      // Loop through the selected elements and extract outerHTML
      sectionElements.forEach((section) => {
        const htmlElement = section as HTMLElement
        extractedSections.push({
          html_code: htmlElement.outerHTML,
          id: htmlElement.dataset.componentid || null,
          title: htmlElement.title || htmlElement.dataset.componentid || 'Untitled Component',
        })
      })

      this.pageBuilderStateStore.setComponents(extractedSections)
    } catch (error) {
      console.error('Error parsing HTML components:', error)
      // Set empty array on error to ensure consistent state
      this.pageBuilderStateStore.setComponents([])
    }
  }

  // Load existing content from HTML when in update mode
  loadExistingContent(data?: string, injectCustomHTMLSections?: boolean): void {
    this.pageBuilderStateStore.setComponents([])

    if (!this.pageBuilderStateStore.getConfigPageBuilder) return

    if (injectCustomHTMLSections && data !== undefined) {
      this.setComponentsFromData(data)
    }

    const storedData = this.areComponentsStoredInLocalStorage()

    if (this.pageBuilderStateStore.getConfigPageBuilder?.updateOrCreate?.formType === 'create') {
      if (storedData) {
        this.setComponentsFromData(storedData)
      }
    }

    if (this.pageBuilderStateStore.getConfigPageBuilder?.updateOrCreate?.formType === 'update') {
      if (data) {
        this.setComponentsFromData(data)
      }
    }
  }

  async handlePageBuilderMethods(): Promise<void> {
    await new Promise((resolve) => requestAnimationFrame(resolve))

    // handle custom URL
    this.handleHyperlink(undefined, null, false)
    // handle opacity
    this.handleOpacity(undefined)
    // handle BG opacity
    this.handleBackgroundOpacity(undefined)
    // displayed image
    this.showBasePrimaryImage()
    // border style
    this.handleBorderStyle(undefined)
    // border width
    this.handleBorderWidth(undefined)
    // border color
    this.handleBorderColor(undefined)
    // border radius
    this.handleBorderRadiusGlobal(undefined)
    // border radius
    this.handleBorderRadiusTopLeft(undefined)
    // border radius
    this.handleBorderRadiusTopRight(undefined)
    // border radius
    this.handleBorderRadiusBottomleft(undefined)
    // border radius
    this.handleBorderRadiusBottomRight(undefined)
    // handle font size
    this.handleFontSize(undefined)
    // handle font weight
    this.handleFontWeight(undefined)
    // handle font family
    this.handleFontFamily(undefined)
    // handle font style
    this.handleFontStyle(undefined)
    // handle vertical padding
    this.handleVerticalPadding(undefined)
    // handle horizontal padding
    this.handleHorizontalPadding(undefined)
    // handle vertical margin
    this.handleVerticalMargin(undefined)
    // handle horizontal margin
    this.handleHorizontalMargin(undefined)
    // handle color
    this.handleBackgroundColor(undefined)
    // handle text color
    this.handleTextColor(undefined)
    // handle classes
    await this.currentClasses()
  }
}

export default PageBuilderClass
