<!--
 * @Author: ShawnPhang
 * @Date: 2023-05-26 17:42:26
 * @Description: 调色板
 * @LastEditors: ShawnPhang <https://m.palxp.cn>
 * @LastEditTime: 2024-01-31 10:44:41
-->
<template>
  <div class="color-picker">
    <Tabs v-if="modes.length > 1" :value="mode" @update:value="onChangeMode">
      <TabPanel v-for="label in modes" :key="label" :label="label"> </TabPanel>
    </Tabs>
    <div v-else class="title">{{ mode }}</div>

    <template v-if="showGradient">
      <div v-show="mode === '渐变'" class="cp__gradient flex-center">
        <div class="cp__gradient-bar">
          <div ref="elGradientTrack" class="cpgb__track" style="width: 100%" :style="{ background: value }">
            <!-- tabindex="-1" 是元素可以触发 keydown 事件 -->
            <div
              v-for="(gradient, index) in gradients"
              :key="index"
              :class="[
                'cpgb__pointer',
                {
                  'cpgb__pointer--active': gradient === activeGradient,
                },
              ]"
              :data-sort="index"
              :style="{
                left: `${gradient.offset * 100}%`,
                background: gradient.color,
              }"
              tabindex="-1"
              @mousedown="onMousedownGradientPointer(gradient)"
              @keydown.stop="onKeyupGradientPointer"
            ></div>
          </div>
        </div>
        <AngleHandleVue v-model="angle" @change="angleChange" />
      </div>
    </template>

    <div ref="elPalette" class="cp__palette" :style="{ background: paletteBackground }">
      <div class="cpp__color-saturation"></div>
      <div class="cpp__color-value"></div>
      <div ref="elPalettePointer" class="cpp__pointer"></div>
    </div>

    <div ref="elSliderHux" class="cp__slider cp__slider-hux">
      <div class="cps__track">
        <div ref="elSliderHuxPointer" class="cpst__pointer"></div>
      </div>
    </div>

    <div ref="elSliderAlpha" class="cp__slider cp__slider-alpha">
      <div class="cpsa__background" :style="sliderAlphaBackgroundStyle"></div>
      <div class="cps__track">
        <div ref="elSliderAlphaPointer" class="cpst__pointer"></div>
      </div>
    </div>

    <div class="cp__box">
      <div class="item" @click="onClickStraw">
        <xiguan v-if="hasEyeDrop" />
        <input v-else class="native" type="color" @input="onClickStraw" />
      </div>
      <!-- <input :value="value" @input="$emit('update:value', $event.target.value)" class="input" /> -->
      <input v-if="mode === '渐变'" class="input" :value="activeGradient.color" />
      <input v-else :value="value" class="input" @blur="onInputBlur" />
      <template v-if="mode === '纯色'">
        <div v-for="pc in predefine" :key="pc" class="item item-color" :style="{ background: pc }" @click="onClickStraw({ target: { value: pc } })"></div>
      </template>
      <!-- <input :value="alpha" class="w-12" size="small" :min="0" :max="100" @input="onChangeAlpha" @change="onChangeAlpha" /> -->
    </div>
  </div>
</template>

<script>
import './index.css'
export default {
  name: 'ColorPicker',
  inheritAttrs: false,
}
</script>

<script setup>
import { ref, reactive, computed, watch, onMounted, nextTick, onBeforeUnmount } from 'vue'
import { registerMoveableElement } from './utils/moveable.ts'
import { hexA2HSLA, HSLA2HexA, hex2RGB, RGB2HSL, hexA2RGBA, RGBA2HexA } from './utils/color.ts'
import { toGradientString, parseBackgroundValue, toolTip } from './utils/helper.ts'
import Tabs from './comps/Tabs.vue'
import xiguan from './comps/svg.vue'
import TabPanel from './comps/TabPanel.vue'
import { debounce } from 'throttle-debounce'
import AngleHandleVue from './comps/AngleHandle.vue'

const props = defineProps({
  value: {
    type: String,
    default: '#ffffffff',
  },

  modes: {
    type: Array,
    default: () => ['纯色', '渐变'], // 图案
  },

  defaultColor: {
    type: String,
    default: '#ffffffff',
  },

  defaultGradient: {
    type: String,
    default: 'linear-gradient(90deg, #fffae0ff 0%, #ffd1f1ff 100%)',
  },

  defaultImage: {
    type: String,
    default: 'https://st0.dancf.com/csc/157/material-2d-textures/0/20190714-174653-ed3c.jpg',
  },
})

const emit = defineEmits(['update:value', 'change', 'native-pick', 'blur'])

const mode = ref(parseBackgroundValue(props.value)) // 颜色、渐变、图片
const angle = ref(90)
const gradients = ref([])
const hsla = reactive({ h: 0, s: 0, l: 0, a: 0 })
const paletteBackground = ref('#f00')
const hex = ref('#000')
const alpha = ref(0)
let activeGradient = ref({})
const hasEyeDrop = 'EyeDropper' in window

const elGradientTrack = ref()
const elPalette = ref()
const elPalettePointer = ref()
const elSliderHuxPointer = ref()
const elSliderHux = ref()
const elSliderAlphaPointer = ref()
const elSliderAlpha = ref()
// const elStrawCanvas = ref();

let gradientMoveable = null
let paletteMoveable = null
let sliderHuxMoveable = null
let sliderAlphaMoveable = null
let mousedownGradientPointer = null
let backendHex = null
// 是否可以改变 palette sliderHux sliderAlpha 的 pointer 位置
let canChangeHSLAPointerPos = true
let canChangeHSLAPointerPosTimer = null

const predefine = ref([]) // 历史记录

const record = {
  color: props.defaultColor,
  gradient: props.defaultGradient,
  image: props.defaultImage,
}

const showGradient = computed(() => {
  return props.modes.includes('渐变')
})

const sliderAlphaBackgroundStyle = computed(() => {
  const rgb = hex2RGB(hex.value).join(',')
  return {
    background: `linear-gradient(to right, rgba(${rgb}, 0) 0%, rgb(${rgb}) 100%)`,
  }
})

watch(activeGradient, (value) => {
  setColor(value.color)
})

watch(hex, (value) => {
  onChangeHex(value)
})

watch(
  () => props.value,
  (value) => {
    const _mode = parseBackgroundValue(value)
    if (_mode !== mode.value) {
      mode.value = _mode
    }
    changeMode(_mode)
    recordValue(value)
    addHistory(value)
  },
)

// TODO: 添加选择历史记录
const addHistory = debounce(300, async (value) => {
  const history = predefine.value
  // 如果已经存在就提到前面来，避免重复
  const index = history.indexOf(value)
  if (index !== -1) {
    predefine.value.splice(index, 1)
  }
  if (history.length >= 4) {
    predefine.value.splice(history.length - 1, 1)
  }
  // 把最新的颜色放在头部
  const head = [value]
  predefine.value = head.concat(history)
})

const unwatchHSLA = watch(hsla, onChangeHSLA, { deep: true })
function onChangeHSLA(newHsla) {
  const hexA = HSLA2HexA(...Object.values(newHsla))

  let value
  if (mode.value === '纯色') {
    value = hexA
  } else if (mode.value === '渐变') {
    activeGradient.value.color = hexA
    value = toGradientString(angle.value, gradients.value)
  }
  updateColorData(hexA)
  updateValue(value)
}

onMounted(onMountedCallback)
async function onMountedCallback() {
  elPalettePointer.value.style.left = `${hsla.s}%`
  elPalettePointer.value.style.top = `${100 - hsla.l}%`
  elSliderHuxPointer.value.style.left = `${(hsla.h / 360) * 100}%`
  elSliderAlphaPointer.value.style.left = `${hsla.a * 100}%`

  if (showGradient.value) {
    gradientMoveable = registerMoveableElement(elGradientTrack.value, {
      onmousedown: onMousedownGradient,
      onmousemove: onMousemoveGradient,
      onmouseup: onMouseupGradient,
    })
  }

  function onMousedownGradient(position) {
    if (mousedownGradientPointer) {
      return
    }

    const index = gradients.value.findIndex((stop) => stop.offset >= position.x)
    const start = gradients.value[index - 1]
    const startRGBA = hexA2RGBA(start.color)
    const end = gradients.value[index]
    const endRGBA = hexA2RGBA(end.color)

    const rgb = []
    for (let i = 0; i < 3; i += 1) {
      rgb.push(startRGBA[i] + (endRGBA[i] - startRGBA[i]) * position.x)
    }

    const a = end.offset - position.x - (position.x - start.offset) > 0 ? startRGBA[3] : endRGBA[3]

    const color = RGBA2HexA(...rgb, a)
    activeGradient.value = {
      color,
      offset: position.x,
    }

    gradients.value.splice(index, 0, activeGradient.value)
  }

  function onMousemoveGradient(position) {
    if (!mousedownGradientPointer) return

    activeGradient.value.offset = position.x
    gradients.value.sort((a, b) => a.offset - b.offset)

    const value = toGradientString(angle.value, gradients.value)
    updateValue(value)
  }

  function onMouseupGradient() {
    mousedownGradientPointer = false
  }

  paletteMoveable = registerMoveableElement(elPalette.value, {
    onmousemove: onChangeSL,
    onmouseup: onChangeSL,
  })

  function onChangeSL(position) {
    disableChangeHSLA()

    const x = position.x * 100
    const y = position.y * 100

    hsla.s = Math.round(x)
    hsla.l = Math.round(100 - y)

    elPalettePointer.value.style.left = `${x}%`
    elPalettePointer.value.style.top = `${y}%`
  }

  sliderHuxMoveable = registerMoveableElement(elSliderHux.value, {
    onmousemove: onChangeHux,
    onmouseup: onChangeHux,
  })

  function onChangeHux(position) {
    disableChangeHSLA()

    hsla.h = position.x * 360
    elSliderHuxPointer.value.style.left = `${position.x * 100}%`
  }

  sliderAlphaMoveable = registerMoveableElement(elSliderAlpha.value, {
    onmousemove: onChangeAlpha,
    onmouseup: onChangeAlpha,
  })

  function onChangeAlpha(position) {
    disableChangeHSLA()

    hsla.a = position.x
    elSliderAlphaPointer.value.style.left = `${position.x * 100}%`
  }

  changeMode(mode.value)
  recordValue(props.value)
}

onBeforeUnmount(() => {
  paletteMoveable?.destroy()
  sliderHuxMoveable?.destroy()
  sliderAlphaMoveable?.destroy()
  unwatchHSLA()

  if (gradientMoveable) {
    gradientMoveable.destroy()
  }
})

function recordValue(value) {
  if (mode.value === '纯色') {
    record.color = value
  } else if (mode.value === '渐变') {
    record.gradient = value
  } else if (mode.value === '图案') {
    record.image = value
  }
}

function updateValue(value) {
  if (value === props.value) return

  recordValue(value)
  emit('update:value', value)

  emit('change', {
    mode: mode.value,
    color: value,
    angle: Number(angle.value),
    stops: gradients.value,
  })
}

async function onChangeMode(value) {
  if (value === mode.value) return
  mode.value = value

  let color
  if (value === '纯色') {
    color = record.color
  } else if (value === '渐变') {
    color = record.gradient
  } else if (value === '图案') {
    color = record.image
  }
  updateValue(color)
}

function changeMode(mode) {
  if (mode === '纯色') {
    setColor(props.value)
  } else if (mode === '渐变') {
    if (gradients.value.length === 0) {
      props.value.match(/[^,]+/g).forEach((item, index) => {
        if (index === 0) {
          angle.value = Number(item.match(/\d+/)[0])
          return
        }

        let [color, offset] = item.trim().split(' ')
        if (!color.startsWith('#')) color = RGBA2HexA(color)

        offset = offset.match(/\d+/)[0] / 100
        gradients.value.push({ color, offset })
        activeGradient.value = gradients.value[0]
      })
    } else {
      setColor(activeGradient.value.color)
    }
  }

  // TODO: 图案
}

function updateColorData(hexA) {
  paletteBackground.value = `hsl(${hsla.h}, 100%, 50%)`
  hex.value = hexA.slice(0, 7)
  backendHex = hex.value
  alpha.value = Math.round((hsla.a ?? 1) * 100)
}

function setColor(color) {
  // 通过 palette sliderHux sliderAlpha 交互改变 pointer 位置
  // 已经改变 hsla 的值并触发 update:value
  // watch props.value 再调用当前方法时无需再更新 hsla
  if (canChangeHSLAPointerPos) {
    const _hsla = hexA2HSLA(color)
    hsla.h = _hsla[0]
    hsla.s = _hsla[1]
    hsla.l = _hsla[2]
    hsla.a = _hsla[3]

    updateColorData(color)

    let x = hsla.s
    const y = Math.round(100 - hsla.l)
    elPalettePointer.value.style.left = `${x}%`
    elPalettePointer.value.style.top = `${y}%`

    x = hsla.h / 360
    elSliderHuxPointer.value.style.left = `${x * 100}%`

    elSliderAlphaPointer.value.style.left = `${hsla.a * 100}%`
  }
}

function onMousedownGradientPointer(stop) {
  mousedownGradientPointer = true
  activeGradient.value = stop
}

function onKeyupGradientPointer(event) {
  event.stopPropagation()
  event.preventDefault()
  if (!['Backspace', 'Delete'].includes(event.key)) return
  if (gradients.value.length === 2) return

  const index = gradients.value.indexOf(activeGradient.value)
  gradients.value.splice(index, 1)
  activeGradient.value = gradients.value[0]
}

function onChangeHex(value) {
  if (/^#(?:[0-9a-f]{3}){1,2}$/i.test(value)) {
    const rgb = hex2RGB(value)
    const [h, s, l] = RGB2HSL(...rgb)
    hsla.h = h
    hsla.s = s
    hsla.l = l

    elPalettePointer.value.style.left = `${hsla.s}%`
    elPalettePointer.value.style.top = `${100 - hsla.l}%`
    elSliderHuxPointer.value.style.left = `${(hsla.h / 360) * 100}%`

    hex.value = value
  } else {
    // hex.value = backendHex
  }
}

function onChangeAlpha(value) {
  hsla.a = value / 100
  elSliderAlphaPointer.value.style.left = `${value}%`
}

function disableChangeHSLA() {
  canChangeHSLAPointerPos = false

  if (canChangeHSLAPointerPosTimer) clearTimeout(canChangeHSLAPointerPosTimer)
  canChangeHSLAPointerPosTimer = setTimeout(() => {
    canChangeHSLAPointerPos = true
  }, 16)
}

async function onClickStraw(val) {
  let result = ''
  if (val && val.target.value) {
    const color = val.target.value
    result = color + (color.length === 7 ? 'ff' : '')
  } else {
    const eyeDropper = new window.EyeDropper() // 初始化一个EyeDropper对象
    toolTip('按Esc可退出')
    try {
      const drop = await eyeDropper.open() // 开始拾取颜色
      const colorHexValue = drop.sRGBHex
      result = colorHexValue + 'ff'
    } catch (e) {
      console.log('用户取消了取色')
    }
  }
  if (mode.value === '渐变') {
    activeGradient.value.color = result
    activeGradient.value = { ...activeGradient.value }
  } else {
    emit('update:value', result)
  }
  emit('native-pick', result)
}

const onInputBlur = (e) => {
  const fixColor = patchHexColor(e.target.value)
  emit('blur', fixColor)
  emit('update:value', fixColor)
}

function patchHexColor(str) {
  let hex = str.replace(/\s/g, '') // 移除空格
  if (!str.startsWith('#')) {
    hex = '#' + hex
  }
  if (hex.length < 9) {
    hex = hex.padEnd(9, 'f')
  }
  return hex
}

function angleChange() {
  updateValue(toGradientString(angle.value, gradients.value))
}
</script>

<style lang="less" scoped>
*,
::before,
::after {
  box-sizing: border-box;
  border-width: 0;
  border-style: solid;
  border-color: #e5e7eb;
}
.flex-center {
  display: flex;
  justify-content: center;
  align-items: center;
}
.title {
  margin-bottom: 0.75rem;
  font-size: 15px;
  font-weight: 600;
}
.color-picker {
  -webkit-user-select: none;
  user-select: none;
  min-width: 220px;
}

.cp__gradient {
  &-bar {
    display: flex;
    justify-content: center;
    height: 16px;
    width: 100%;
    padding: 0 8px;
  }
}

.cpgb__track {
  position: relative;
  cursor: pointer;
}

.cpgb__pointer {
  cursor: grab;
  position: absolute;
  top: -0px;
  top: -0.125rem;
  height: 1.25rem;
  --tw-translate-x: -50%;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
  border-width: 2px;
  border-style: solid;
  --tw-border-opacity: 1;
  border-color: rgb(255 255 255 / var(--tw-border-opacity));
  --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
  --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
  box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
  outline: 2px solid transparent;
  outline-offset: 2px;
  width: 18px;

  &--active {
    z-index: 1;
    border-radius: 3px;
    box-shadow: 0 0 4px 0 rgb(0 0 0 / 20%), 0 0 0 1.2px #2254f4;
  }
}

.cp__palette {
  height: 140px;
  position: relative;
  margin-top: 0.75rem;
  margin-top: 0.875rem;
  cursor: pointer;
  overflow: hidden;
  border-radius: 0.25rem;
  .cpp__color-saturation,
  .cpp__color-value {
    position: absolute;
    bottom: 0px;
    right: 0px;
    top: 0px;
    width: 100%;
    height: 100%;
  }

  .cpp__color-saturation {
    background-image: linear-gradient(to right, var(--tw-gradient-stops));
    --tw-gradient-from: #fff var(--tw-gradient-from-position);
    --tw-gradient-to: rgb(255 255 255 / 0) var(--tw-gradient-to-position);
    --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
  }

  .cpp__color-value {
    background-image: linear-gradient(to top, var(--tw-gradient-stops));
    --tw-gradient-from: #000 var(--tw-gradient-from-position);
    --tw-gradient-to: rgb(0 0 0 / 0) var(--tw-gradient-to-position);
    --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
  }

  .cpp__pointer {
    position: absolute;
    height: 0.75rem;
    width: 0.75rem;
    --tw-translate-x: -0.25rem;
    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
    --tw-translate-x: -0.375rem;
    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
    --tw-translate-y: -0.25rem;
    transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
    border-radius: 9999px;
    border-width: 2px;
    --tw-border-opacity: 1;
    border-color: rgb(255 255 255 / var(--tw-border-opacity));
    --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
    --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);
    box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
  }
}

.cp__slider {
  position: relative;
  margin-top: 0.75rem;
  margin-top: 0.875rem;
  height: 0.5rem;
  border-radius: 0.25rem;
  &-hux {
    background: linear-gradient(90deg, red 0, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, red);
  }

  &-alpha {
    background: linear-gradient(to top right, hsla(0, 0%, 80%, 0.4) 25%, transparent 0, transparent 75%, hsla(0, 0%, 80%, 0.4) 0, hsla(0, 0%, 80%, 0.4)), linear-gradient(to top right, hsla(0, 0%, 80%, 0.4) 25%, transparent 0, transparent 75%, hsla(0, 0%, 80%, 0.4) 0, hsla(0, 0%, 80%, 0.4));
    background-size: 6px 6px;
    background-position: 0 0, 3px 3px;
  }

  .cpsa__background {
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / 6%);
    height: 100%;
    border-radius: 0.25rem;
  }
}
.cp__box {
  margin-top: 0.75rem;
  margin-top: 0.875rem;
  display: flex;
  .item {
    display: flex;
    justify-content: center;
    align-items: center;
    cursor: pointer;
    margin-left: 6px;
    width: 24px;
    height: 24px;
    box-sizing: border-box;
    border-radius: 4px;
  }
  .item-color {
    box-shadow: inset 0 0 0 1px rgb(0 0 0 / 6%);
  }
  .item:first-of-type {
    margin: 0;
  }
  .item:hover {
    transform: scale(1.08);
  }
  .input {
    width: 4.7rem;
    margin-left: 2px;
  }
  .native {
    width: 100%;
    height: 100%;
  }
}

.cps__track {
  position: absolute;
  left: 0.25rem;
  right: 0.25rem;
  top: 0px;
}

.cpst__pointer {
  cursor: pointer;
  box-shadow: 0 0 2px rgb(0 0 0 / 60%);
  position: absolute;
  top: 0px;
  box-sizing: content-box;
  height: 0.5rem;
  width: 0.5rem;
  --tw-translate-x: -0.5rem;
  --tw-translate-y: -0.25rem;
  transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y));
  border-radius: 9999px;
  border-width: 4px;
  --tw-border-opacity: 1;
  border-color: rgb(255 255 255 / var(--tw-border-opacity));
}
</style>
