import create from './dom/create'
import repaint from './util/repaint'
import onFind from './dom/onFind'
import Cookies from 'js-cookie'
import Dropdown from './dropdown'
import Tether from './dom/Tether'
import { EditorView } from 'prosemirror-view'
import { Node } from 'prosemirror-model'

type ActionsFormPostFields =
  | 'containerId'
  | 'containerTypeId'
  | 'fieldName'
  | 'id'
  | 'inputName'
  | 'isConvert'
  | 'isSwappable'
  | 'itemState'
  | 'recordTypeId'
  | 'targetTypeId'
  | 'typeId'
  | 'validTypes'

interface ActionsFormPostData extends FormData {
  append(
    name: ActionsFormPostFields,
    value: string | Blob,
    fileName?: string,
  ): void
}

export default class ContentSelectorActions {
  private _element: HTMLElement
  private _options: {
    loadableTypes?: string
    node?: Node
    editorView?: EditorView
  }
  private _loadableTypes?: string
  private _node?: Node
  private _editorView?: EditorView

  constructor(
    element: HTMLElement,
    options: {
      loadableTypes?: string
      node?: Node
      editorView?: EditorView
    },
  ) {
    this._element = element
    this._options = options
    this._loadableTypes = options.loadableTypes
    this._node = options.node
    this._editorView = options.editorView
    this._update = repaint(this._update.bind(this))
    this._update()
  }

  _update() {
    // omit gallery items
    if (
      !this._element ||
      this._element.closest('.is-gridItem') ||
      this._element.closest('.is-listItem')
    ) {
      return
    }

    const dropdown = new Dropdown()

    // RTE Body Conversion List Dropdown
    if (this._node) {
      const moreList = create('ul', {
        className: 'Dropdown',
        role: 'menu',
        'aria-label': window.BRIGHTSPOT?.ui.tooltips.actions?.contentSelector,
        'aria-orientation': 'vertical',
      })

      // Tether dropdown when clicked
      this._element.addEventListener('click', () => {
        const list = this._element.nextSibling

        if (list instanceof HTMLElement) {
          Tether.toggle(this._element, list, () => {
            this._element.classList.toggle(
              'is-active',
              list.classList.toggle('is-open'),
            ),
              moreList.setAttribute(
                'aria-expanded',
                list.classList.contains('is-open').valueOf().toString(),
              )

            return true
          })
        }
      })

      // Add convert buttons to RTE dropdown
      if (this._loadableTypes) {
        const loadableTypes = JSON.parse(this._loadableTypes)
        for (const typeName in loadableTypes) {
          const convertButton = create(
            'li',
            {
              className: 'Dropdown-item',
              'aria-label': typeName,
              role: 'menuitem',
            },
            typeName,
          )
          moreList.appendChild(convertButton)

          // Trigger RTE event to convert enhancement
          convertButton.addEventListener('click', () => {
            const event = new CustomEvent('interchange', {
              detail: this._options,
            })

            if (this._loadableTypes) {
              const loadableTypes = JSON.parse(this._loadableTypes)

              if (this._node) {
                this._node.attrs.attributes['data-convert-target'] =
                  loadableTypes[typeName]
              }

              this._editorView?.dispatchEvent(event)
            }
          })
        }
      }

      this._element.after(moreList)

      return
    }

    const parentEl = this._element.parentElement
    const parentList = parentEl?.parentElement

    if (!parentEl || !parentList) {
      return
    }

    const editButton = getDirectDesc(parentEl, '.objectId-edit')
    const clearButton = getDirectDesc(parentEl, '.objectId-clear')
    const swapButton =
      getDirectDesc(parentEl, '.RCIG-swap') ||
      getDirectDesc(parentEl, '.EIG-swap')
    const removeButton = getDirectDesc(parentEl, '.removeButton')
    const toggleButton = getDirectDesc(parentEl, '.MultiSortable-buttonToggle')
    const convertButtons = getDirectDesc(parentEl, '.convertButton')
    const copyButton = getDirectDesc(parentEl, '.copyButton')

    const form = parentEl.closest<HTMLFormElement>('form')
    const id = this._element.dataset.objectId
    const inputName =
      parentList.closest<HTMLElement>('[data-name]')?.dataset.name
    const fieldName = parentList.dataset.fieldName
    const containerId = parentList.dataset.containerId
    const containerTypeId = parentList.dataset.typeId
    const recordTypeId = parentList.dataset.recordTypeId
    const validTypes = parentList.dataset.sortableValidItemTypes
    const isSwappable = parentList.dataset.isSwappable

    const body: ActionsFormPostData = new FormData()

    if (inputName) {
      body.set('inputName', inputName)
    }

    if (fieldName) {
      body.set('fieldName', fieldName)
    }

    if (containerId) {
      body.set('containerId', containerId)
    }

    if (containerTypeId) {
      body.set('containerTypeId', containerTypeId)
    }

    if (recordTypeId) {
      body.set('recordTypeId', recordTypeId)
    }

    if (validTypes) {
      body.set('validTypes', validTypes)
    }

    if (isSwappable) {
      body.set('isSwappable', isSwappable)
    }

    const evt = new MouseEvent('click', {
      bubbles: true,
      cancelable: true,
      view: window,
    })

    if (!editButton && !clearButton && !swapButton && !removeButton) {
      return
    }

    // Add selector to position multiSortable button
    if (
      toggleButton ||
      (parentEl.classList.contains('Sortable-item') &&
        parentEl.classList.contains('RCIG-item'))
    ) {
      parentEl.classList.add('toggleVisible')
    }

    // Add more/dropdown elements to DOM
    this._element.after(dropdown)

    // Create dropdown list
    if (editButton.length) {
      dropdown._addDropdownItem(create('li', editButton))
    }

    if (clearButton.length) {
      dropdown._addDropdownItem(create('li', clearButton))
    }

    if (swapButton.length) {
      dropdown._addDropdownItem(create('li', swapButton))
      swapButton[0].ariaLabel = swapButton[0].textContent
    }

    if (copyButton.length) {
      dropdown._addDropdownItem(create('li', copyButton))
      copyButton[0].ariaLabel = copyButton[0].textContent
    }

    // Convert
    if (convertButtons.length) {
      for (const convertButton of convertButtons) {
        dropdown._addDropdownItem(create('li', convertButton))
        convertButton.ariaLabel = convertButton.textContent

        convertButton.addEventListener('click', () => {
          if (form && id) {
            const itemState = getItemState(parentEl, form, id)

            if (itemState) {
              const typeId = JSON.parse(itemState)['_type']
              const targetTypeId = convertButton.dataset.targetType
              const convertedModule = <HTMLElement>parentEl.cloneNode(false)

              if (typeId) {
                body.append('typeId', typeId)
              }

              if (targetTypeId) {
                body.append('targetTypeId', targetTypeId)
              }

              body.append('itemState', itemState)
              body.append('isConvert', 'true')

              postForm(body, '/cms/drawer/item-interchange').then((html) => {
                convertedModule.innerHTML = html
                const repeatable = parentEl.closest('.plugin-repeatable')

                // Call jquery.repeatable initialization methods on item.
                if (repeatable) {
                  $(repeatable)
                    .data('repeatable')
                    .itemLoad(parentEl)
                    .then(() => {
                      $(repeatable).data('repeatable').initItem(convertedModule)
                      $(convertedModule).trigger('create')

                      parentList?.replaceChild(convertedModule, parentEl)

                      // Open convert module
                      onFind(
                        convertedModule,
                        ':scope > .repeatableLabel',
                        (titleEl: HTMLElement) => {
                          if (
                            convertedModule.classList.contains('is-collapsed')
                          ) {
                            titleEl.dispatchEvent(evt)
                          }
                          window.requestAnimationFrame(() => {
                            window.requestAnimationFrame(() => {
                              titleEl.classList.contains('RCIG-title') &&
                                titleEl.focus()
                            })
                          })
                        },
                      )
                    })
                }
              })
            }
          }
        })
      }
    }

    // Make a copy
    if (copyButton.length) {
      copyButton[0].addEventListener('click', () => {
        if (form && id) {
          const itemState = getItemState(parentEl, form, id)

          if (itemState) {
            const typeId = JSON.parse(itemState)['_type']

            if (typeId) {
              body.append('typeId', typeId)
            }

            body.append('itemState', itemState)

            postForm(body, '/cms/copy-record').then((html) => {
              const doc = new DOMParser().parseFromString(html, 'text/html')
              const copiedItem = <HTMLElement>doc.body.firstChild

              if (copiedItem) {
                parentList?.appendChild(copiedItem)

                const repeatable = copiedItem.closest('.plugin-repeatable')

                if (repeatable) {
                  $(repeatable)
                    .data('repeatable')
                    .itemLoad(copiedItem)
                    .then(() => {
                      $(repeatable).data('repeatable').initItem(copiedItem)

                      // Open copied module and scroll to it
                      onFind(
                        copiedItem,
                        ':scope > .repeatableLabel',
                        (titleEl: HTMLElement) => {
                          if (copiedItem.classList.contains('is-collapsed')) {
                            titleEl.dispatchEvent(evt)
                          }

                          copiedItem.scrollIntoView({
                            behavior: 'smooth',
                            block: 'start',
                            inline: 'nearest',
                          })

                          window.requestAnimationFrame(() => {
                            titleEl.classList.contains('RCIG-title') &&
                              titleEl.focus()
                          })
                        },
                      )
                    })
                }
              }
            })
          }
        }
      })
    }
  }
}

const getDirectDesc = (elem: HTMLElement, selector: string): HTMLElement[] => {
  const desc = Array.prototype.filter.call(elem.children, function (child) {
    return child.matches(selector)
  })

  return desc.length > 0 ? desc : []
}

const postForm = async (body: ActionsFormPostData, url: string) => {
  const headers = new Headers()
  const cookies = Cookies.get('bsp.csrf')

  if (cookies) {
    headers.append('Brightspot-CSRF', cookies)
  }

  const response = await fetch(url, {
    method: 'POST',
    credentials: 'include',
    body,
    headers,
  })

  return await response.text()
}

const getItemState = (element: HTMLElement, form: HTMLElement, id: string) => {
  const formState = $(form).data('brightspot-content-state')

  if (formState) {
    const formStateObject = JSON.parse(formState)
    const nestedObject = findNestedObject(formStateObject, id)

    if (nestedObject) {
      return JSON.stringify(nestedObject)
    }
  }

  return element.dataset.state
}

const findNestedObject = (formObject: any, id: string): unknown => {
  if (formObject['_id'] === id) {
    return formObject
  } else {
    const keys = Object.keys(formObject)

    for (const key of keys) {
      const object = formObject[key]
      if (object && typeof object == 'object') {
        const nestedObject = findNestedObject(object, id)
        if (nestedObject) {
          return nestedObject
        }
      }
    }
  }
}
