import { extend } from '@opentiny/utils'
import { xss } from '@opentiny/utils'

import { getToolbarTips, defaultOptions } from './options'
import registerTableModule from './table-module'
import registerCustomClipboard from './clipboard'

// 注册自定义 Size 格式以支持像素值
const registerCustomSize = (Quill, sizeConfig) => {
  if (!sizeConfig || !Array.isArray(sizeConfig)) {
    return
  }

  // 检查是否包含像素值(如 '12px', '14px' 等)
  const hasPixelValues = sizeConfig.some((item) => {
    return item && typeof item === 'string' && item.includes('px')
  })

  if (!hasPixelValues) {
    return
  }

  // 使用 StyleAttributor 来支持像素值
  const SizeStyle = Quill.import('attributors/style/size')

  // 过滤掉 false 值,设置白名单
  const whitelist = sizeConfig.filter((item) => item !== false)

  // 设置白名单,允许的 size 值
  SizeStyle.whitelist = whitelist

  // 使用 true 参数强制覆盖已注册的格式
  Quill.register('formats/size', SizeStyle, true)
}

export const initContent =
  ({ state, props, nextTick }) =>
  () => {
    if (state.quill) {
      const flag = state.quill.selection.hasFocus()

      if (state.content && state.content !== state.innerContent) {
        state.innerContent = state.content
        state.quill.pasteHTML(xss.filterHtml(state.content))
      } else {
        if (!state.content) {
          state.quill.setText('')
        }
      }

      nextTick(() => {
        if (!props.disabled && !props.displayOnly) {
          state.quill.enable(true)
        }
        flag ? state.quill.selection.focus() : state.quill.blur()
      })
    }
  }

export const initQuill =
  ({ api, emit, props, vm, service, state, Quill, ImageDrop, ImageUpload, FileUpload }) =>
  () => {
    state.innerOptions = extend(true, {}, defaultOptions, props.globalOptions, props.options)

    // 检查并注册自定义 Size 格式(支持像素值)
    const toolbarConfig = state.innerOptions.modules?.toolbar?.container || []

    // 递归查找 size 配置
    const findSizeConfig = (config) => {
      if (!config) return null

      if (Array.isArray(config)) {
        for (let i = 0; i < config.length; i++) {
          const result = findSizeConfig(config[i])
          if (result) return result
        }
      } else if (typeof config === 'object' && config !== null) {
        // 检查是否有 size 属性
        if (config.size && Array.isArray(config.size)) {
          return config.size
        }
        // 递归检查对象的值
        for (const key in config) {
          const result = findSizeConfig(config[key])
          if (result) return result
        }
      }

      return null
    }

    const sizeConfig = findSizeConfig(toolbarConfig)
    if (sizeConfig) {
      registerCustomSize(Quill, sizeConfig)
    }

    if (document.caretRangeFromPoint) {
      if (props.imageDrop) {
        Quill.register('modules/imageDrop', ImageDrop, true)
        state.innerOptions.modules.imageDrop = props.imageDrop
      }

      if (props.imageUpload) {
        Quill.register('modules/imageUpload', ImageUpload, true)
        state.innerOptions.modules.imageUpload = props.imageUpload
      }

      if (props.fileUpload) {
        Quill.register('modules/fileUpload', FileUpload, true)

        state.innerOptions.modules.fileUpload = extend(
          true,
          {},
          { httpRequest: service && service.network.request },
          props.fileUpload,
          { url: state.fileUploadUrl }
        )
      }
    }

    // tableModule
    registerTableModule(Quill)

    // tableModule config
    if (state.innerOptions.modules) {
      state.innerOptions.modules.tableModule = props.tableModule === true
    }

    // CustomClipboard
    registerCustomClipboard(Quill, props.keepClass)

    state.quill = Object.freeze(new Quill(vm.$refs.editor, state.innerOptions))
    state.quill.enable(false)
    state.quill.on('selection-change', api.selectionChange)
    state.quill.on('text-change', api.textChange)
    state.quill.root.addEventListener('click', api.handleClick)

    if (state.content) {
      state.quill.pasteHTML(xss.filterHtml(state.content))
      api.textChange()
    }

    if (!props.disabled && !props.displayOnly) {
      state.quill.enable(true)
    }

    emit('ready', state.quill)

    api.setToolbarTips()
    api.setTooltipI18n()
    state.tooltipI18nHandler = () =>
      setTimeout(() => {
        api.setTooltipI18n()
        api.adjustTooltipPosition()
      })
    state.tooltipResizeHandler = () => api.adjustTooltipPosition()
    vm.$el.addEventListener('click', state.tooltipI18nHandler)
    vm.$el.addEventListener('keyup', state.tooltipI18nHandler)
    window.addEventListener('resize', state.tooltipResizeHandler)
    if (typeof MutationObserver !== 'undefined') {
      state.tooltipObserver = new MutationObserver(() => {
        requestAnimationFrame(() => api.adjustTooltipPosition())
      })
      state.tooltipObserver.observe(vm.$el, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ['style', 'class', 'data-mode']
      })
    }
  }

export const handleClick =
  ({ state, Quill }) =>
  (event) => {
    const el = event.target

    if (!(el instanceof HTMLElement) || el.tagName !== 'IMG') {
      return
    }
    event.stopPropagation()

    const imgBlot = Quill.find(el)
    const index = state.quill.getIndex(imgBlot)

    state.quill.setSelection(index + 1)
  }

export const setToolbarTips =
  ({ t, vm }) =>
  () => {
    let tip
    const item = getToolbarTips(t)
    const length = item.length
    const richTextEl = vm.$el

    for (let i = 0; i < length; i++) {
      tip = richTextEl.querySelectorAll('.quill-editor ' + item[i].Choice)

      if (tip.length) {
        Array.prototype.slice.call(tip).forEach((ite) => {
          ite.setAttribute('title', item[i].title)
        })
      }
    }

    tip = richTextEl.querySelectorAll('.quill-editor .ql-file')

    if (tip.length) {
      const iconEl = vm.$refs.iconFile.$el

      Array.prototype.slice.call(tip).forEach((ite) => {
        ite.innerHTML = getOuterHTML(iconEl)
      })
    }
  }

export const setTooltipI18n =
  ({ t, vm }) =>
  () => {
    const richTextEl = vm.$el
    const tips = richTextEl.querySelectorAll('.ql-container .ql-tooltip')
    const getPlaceholderByMode = (mode) => {
      if (mode === 'video') return t('ui.richText.enterVideo')
      if (mode === 'formula') return t('ui.richText.enterFormula')
      return t('ui.richText.enterLink')
    }

    if (tips.length) {
      Array.prototype.slice.call(tips).forEach((tip) => {
        const mode = tip.getAttribute('data-mode') || 'link'
        const input = tip.querySelector("input[type='text']")

        tip.setAttribute('data-visit-url-text', `${t('ui.richText.visitUrl')}:`)
        tip.setAttribute('data-edit-text', t('ui.richText.edit'))
        tip.setAttribute('data-remove-text', t('ui.richText.remove'))
        tip.setAttribute('data-save-text', t('ui.richText.save'))
        tip.setAttribute('data-enter-link-text', `${t('ui.richText.enterLink')}:`)
        tip.setAttribute('data-enter-formula-text', `${t('ui.richText.enterFormula')}:`)
        tip.setAttribute('data-enter-video-text', `${t('ui.richText.enterVideo')}:`)

        if (input) {
          input.setAttribute('placeholder', getPlaceholderByMode(mode))
        }
      })
    }
  }

export const adjustTooltipPosition =
  ({ vm }) =>
  () => {
    const container = vm.$el.querySelector('.ql-container')
    const tips = vm.$el.querySelectorAll('.ql-container .ql-tooltip')
    const minGap = 8

    if (!container) {
      return
    }

    const containerRect = container.getBoundingClientRect()

    if (tips.length) {
      Array.prototype.slice.call(tips).forEach((tip) => {
        if (!tip.classList.contains('ql-editing')) {
          return
        }

        const offsetParent = tip.offsetParent || tip.parentElement

        if (!offsetParent) {
          return
        }

        const parentRect = offsetParent.getBoundingClientRect()
        const currentLeft = parseFloat(tip.style.left || '0')
        const safeCurrentLeft = Number.isFinite(currentLeft) ? currentLeft : 0
        const minLeft = containerRect.left - parentRect.left + minGap
        const maxLeft = containerRect.right - parentRect.left - tip.offsetWidth - minGap

        // 当 tooltip 比容器更宽时,至少保持左侧可见
        if (maxLeft <= minLeft) {
          tip.style.left = `${minLeft}px`
          return
        }

        const clampedLeft = Math.min(Math.max(safeCurrentLeft, minLeft), maxLeft)
        tip.style.left = `${clampedLeft}px`
      })
    }
  }

export const setPlaceholder =
  ({ state, props }) =>
  () => {
    if (state.quill) {
      state.quill.root.setAttribute('data-placeholder', props.options.placeholder)
    }
  }

export const getFileUploadUrl =
  ({ service }) =>
  () => {
    return service ? service.common.getFileUploadUrl() : Promise.resolve('')
  }

export const selectionChange =
  ({ emit, state }) =>
  (range) => {
    if (!range) {
      emit('blur', state.quill)
    } else {
      emit('focus', state.quill)
    }
  }

export const handlePaste =
  ({ state }) =>
  (event) => {
    const rangeLength = state.quill.selection.savedRange.length || 0
    const newLength = event.clipboardData.getData('text').length || 0
    const currentLength = state.quill.getText().length || 0

    const totalLength = currentLength - rangeLength + newLength

    if (totalLength > state.maxLength) {
      event.preventDefault()
      state.pasteCanceled = true
    } else {
      state.pasteCanceled = false
    }
  }

export const maxLength =
  ({ props, constants }) =>
  () => {
    return props.maxLength > 0 ? props.maxLength : constants.MAX_LENGTH
  }

export const isDisplayOnly =
  ({ state, props, parent, nextTick }) =>
  () => {
    let displayOnly = props.displayOnly || (parent.auiForm || {}).displayOnly

    nextTick(() => {
      if (state.quill && !props.disabled) {
        state.quill.root.setAttribute('contenteditable', !displayOnly)
      }
    })

    return displayOnly
  }

export const textChange =
  ({ emit, vm, state, Modal, t }) =>
  (delta, oldDelta) => {
    const quill = state.quill

    if (state.pasteCanceled) {
      state.pasteCanceled = false
      quill.setContents(oldDelta)

      Modal.message({ message: `${t('ui.richText.maxLength')}${state.maxLength}`, status: 'warning' })

      return
    }

    const text = state.quill.getText()
    const currentLength = text.length - 1
    const maxLength = state.maxLength

    if (currentLength > maxLength) {
      quill.setContents(oldDelta)

      Modal.message({ message: `${t('ui.richText.maxLength')}${state.maxLength}`, status: 'warning' })

      return
    }

    let html = xss.filterHtml(vm.$refs.editor.children[0].innerHTML)

    if (html === '<p><br></p>') html = ''

    state.innerContent = html

    emit('update:modelValue', html)
    emit('change', { html, text, quill })
  }

const getOuterHTML = (el) => {
  if (!el.outerHTML) {
    const container = document.createElement('div')

    container.appendChild(el)

    return xss.filterHtml(container.innerHTML)
  } else {
    return xss.filterHtml(el.outerHTML)
  }
}

export const mounted =
  ({ api, props, state, i18n, watch }) =>
  () => {
    if (props.fileUpload && !props.fileUpload.url) {
      api.getFileUploadUrl().then((url) => {
        url = xss.filterUrl(url)
        state.fileUploadUrl = url
        api.initQuill()
      })
    } else {
      api.initQuill()
    }

    if (i18n) {
      watch(() => i18n.locale, () => {
        api.setToolbarTips()
        api.setTooltipI18n()
        api.adjustTooltipPosition()
      })
    }
  }

export const beforeUnmount =
  ({ api, state, vm }) =>
  () => {
    state.quill.off('selection-change', api.selectionChange)
    state.quill.off('text-change', api.textChange)
    state.quill.root.removeEventListener('click', api.handleClick)
    state.tooltipI18nHandler && vm.$el.removeEventListener('click', state.tooltipI18nHandler)
    state.tooltipI18nHandler && vm.$el.removeEventListener('keyup', state.tooltipI18nHandler)
    state.tooltipResizeHandler && window.removeEventListener('resize', state.tooltipResizeHandler)
    state.tooltipObserver && state.tooltipObserver.disconnect()
    state.tooltipObserver = null
    state.quill = null
    delete state.quill
  }