import type { CSSProperties, ExtractPropTypes } from 'vue'
import { computed, defineComponent, onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'

import { useMutationObserver } from '../_util/hooks/_vueuse/useMutationObserver'
import { initDefaultProps } from '../_util/props-util'
import { arrayType, objectType, someType, withInstall } from '../_util/type'
import { useToken } from '../theme/internal'
import { getPixelRatio, getStyleStr, reRendering, rotateWatermark } from './utils'

/**
 * Base size of the canvas, 1 for parallel layout and 2 for alternate layout
 * Only alternate layout is currently supported
 */
const BaseSize = 2
const FontGap = 3

export interface WatermarkFontType {
  color?: string
  fontSize?: number | string
  fontWeight?: 'normal' | 'light' | 'weight' | number
  fontStyle?: 'none' | 'normal' | 'italic' | 'oblique'
  fontFamily?: string
}
export const watermarkProps = () => ({
  zIndex: Number,
  rotate: Number,
  width: Number,
  height: Number,
  image: String,
  content: someType<string | string[]>([String, Array]),
  font: objectType<WatermarkFontType>(),
  rootClassName: String,
  gap: arrayType<[number, number]>(),
  offset: arrayType<[number, number]>(),
})
export type WatermarkProps = Partial<ExtractPropTypes<ReturnType<typeof watermarkProps>>>
const Watermark = defineComponent({
  name: 'AWatermark',
  inheritAttrs: false,
  props: initDefaultProps(watermarkProps(), {
    zIndex: 9,
    rotate: -22,
    font: {},
    gap: [100, 100],
  }),
  setup(props, { slots, attrs }) {
    const [, token] = useToken()
    const containerRef = shallowRef<HTMLDivElement>()
    const watermarkRef = shallowRef<HTMLDivElement>()
    const stopObservation = shallowRef(false)
    const gapX = computed(() => props.gap?.[0] ?? 100)
    const gapY = computed(() => props.gap?.[1] ?? 100)
    const gapXCenter = computed(() => gapX.value / 2)
    const gapYCenter = computed(() => gapY.value / 2)
    const offsetLeft = computed(() => props.offset?.[0] ?? gapXCenter.value)
    const offsetTop = computed(() => props.offset?.[1] ?? gapYCenter.value)
    const fontSize = computed(() => props.font?.fontSize ?? token.value.fontSizeLG)
    const fontWeight = computed(() => props.font?.fontWeight ?? 'normal')
    const fontStyle = computed(() => props.font?.fontStyle ?? 'normal')
    const fontFamily = computed(() => props.font?.fontFamily ?? 'sans-serif')
    const color = computed(() => props.font?.color ?? token.value.colorFill)
    const markStyle = computed(() => {
      const markStyle: CSSProperties = {
        zIndex: props.zIndex ?? 9,
        position: 'absolute',
        left: 0,
        top: 0,
        width: '100%',
        height: '100%',
        pointerEvents: 'none',
        backgroundRepeat: 'repeat',
      }

      /** Calculate the style of the offset */
      let positionLeft = offsetLeft.value - gapXCenter.value
      let positionTop = offsetTop.value - gapYCenter.value
      if (positionLeft > 0) {
        markStyle.left = `${positionLeft}px`
        markStyle.width = `calc(100% - ${positionLeft}px)`
        positionLeft = 0
      }
      if (positionTop > 0) {
        markStyle.top = `${positionTop}px`
        markStyle.height = `calc(100% - ${positionTop}px)`
        positionTop = 0
      }
      markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`

      return markStyle
    })
    const destroyWatermark = () => {
      if (watermarkRef.value) {
        watermarkRef.value.remove()
        watermarkRef.value = undefined
      }
    }

    const appendWatermark = (base64Url: string, markWidth: number) => {
      if (containerRef.value && watermarkRef.value) {
        stopObservation.value = true
        watermarkRef.value.setAttribute(
          'style',
          getStyleStr({
            ...markStyle.value,
            backgroundImage: `url('${base64Url}')`,
            backgroundSize: `${(gapX.value + markWidth) * BaseSize}px`,
          }),
        )
        containerRef.value?.append(watermarkRef.value)
        // Delayed execution
        setTimeout(() => {
          stopObservation.value = false
        })
      }
    }
    /**
     * Get the width and height of the watermark. The default values are as follows
     * Image: [120, 64]; Content: It's calculated by content;
     */
    const getMarkSize = (ctx: CanvasRenderingContext2D) => {
      let defaultWidth = 120
      let defaultHeight = 64
      const content = props.content
      const image = props.image
      const width = props.width
      const height = props.height
      if (!image && ctx.measureText) {
        ctx.font = `${Number(fontSize.value)}px ${fontFamily.value}`
        const contents = Array.isArray(content) ? content : [content]
        const widths = contents.map((item) => ctx.measureText(item!).width)
        defaultWidth = Math.ceil(Math.max(...widths))
        defaultHeight = Number(fontSize.value) * contents.length + (contents.length - 1) * FontGap
      }
      return [width ?? defaultWidth, height ?? defaultHeight] as const
    }
    const fillTexts = (
      ctx: CanvasRenderingContext2D,
      drawX: number,
      drawY: number,
      drawWidth: number,
      drawHeight: number,
    ) => {
      const ratio = getPixelRatio()
      const content = props.content
      const mergedFontSize = Number(fontSize.value) * ratio
      ctx.font = `${fontStyle.value} normal ${fontWeight.value} ${mergedFontSize}px/${drawHeight}px ${fontFamily.value}`
      ctx.fillStyle = color.value
      ctx.textAlign = 'center'
      ctx.textBaseline = 'top'
      ctx.translate(drawWidth / 2, 0)
      const contents = Array.isArray(content) ? content : [content]
      contents?.forEach((item, index) => {
        ctx.fillText(item ?? '', drawX, drawY + index * (mergedFontSize + FontGap * ratio))
      })
    }
    const renderWatermark = () => {
      const canvas = document.createElement('canvas')
      const ctx = canvas.getContext('2d')
      const image = props.image
      const rotate = props.rotate ?? -22

      if (ctx) {
        if (!watermarkRef.value) {
          watermarkRef.value = document.createElement('div')
        }

        const ratio = getPixelRatio()
        const [markWidth, markHeight] = getMarkSize(ctx)
        const canvasWidth = (gapX.value + markWidth) * ratio
        const canvasHeight = (gapY.value + markHeight) * ratio
        canvas.setAttribute('width', `${canvasWidth * BaseSize}px`)
        canvas.setAttribute('height', `${canvasHeight * BaseSize}px`)

        const drawX = (gapX.value * ratio) / 2
        const drawY = (gapY.value * ratio) / 2
        const drawWidth = markWidth * ratio
        const drawHeight = markHeight * ratio
        const rotateX = (drawWidth + gapX.value * ratio) / 2
        const rotateY = (drawHeight + gapY.value * ratio) / 2
        /** Alternate drawing parameters */
        const alternateDrawX = drawX + canvasWidth
        const alternateDrawY = drawY + canvasHeight
        const alternateRotateX = rotateX + canvasWidth
        const alternateRotateY = rotateY + canvasHeight

        ctx.save()
        rotateWatermark(ctx, rotateX, rotateY, rotate)

        if (image) {
          const img = new Image()
          img.onload = () => {
            ctx.drawImage(img, drawX, drawY, drawWidth, drawHeight)
            /** Draw interleaved pictures after rotation */
            ctx.restore()
            rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate)
            ctx.drawImage(img, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
            appendWatermark(canvas.toDataURL(), markWidth)
          }
          img.crossOrigin = 'anonymous'
          img.referrerPolicy = 'no-referrer'
          img.src = image
        } else {
          fillTexts(ctx, drawX, drawY, drawWidth, drawHeight)
          /** Fill the interleaved text after rotation */
          ctx.restore()
          rotateWatermark(ctx, alternateRotateX, alternateRotateY, rotate)
          fillTexts(ctx, alternateDrawX, alternateDrawY, drawWidth, drawHeight)
          appendWatermark(canvas.toDataURL(), markWidth)
        }
      }
    }
    onMounted(() => {
      renderWatermark()
    })
    watch(
      () => [props, token.value.colorFill, token.value.fontSizeLG],
      () => {
        renderWatermark()
      },
      {
        deep: true,
        flush: 'post',
      },
    )
    onBeforeUnmount(() => {
      destroyWatermark()
    })
    const onMutate = (mutations: MutationRecord[]) => {
      if (stopObservation.value) {
        return
      }
      mutations.forEach((mutation) => {
        if (reRendering(mutation, watermarkRef.value)) {
          destroyWatermark()
          renderWatermark()
        }
      })
    }
    useMutationObserver(containerRef, onMutate, {
      attributes: true,
      subtree: true,
      childList: true,
      attributeFilter: ['style', 'class'],
    })
    return () => {
      return (
        <div
          {...attrs}
          ref={containerRef}
          class={[attrs.class, props.rootClassName]}
          style={[{ position: 'relative' }, attrs.style as CSSProperties]}
        >
          {slots.default?.()}
        </div>
      )
    }
  },
})

export default withInstall(Watermark)
