<script lang="ts" setup>
import { computed, defineModel, onMounted, ref, watch } from 'vue';
import type { UColorPanelProps } from './types';

const props = withDefaults(
  defineProps<UColorPanelProps>(),
  {
    presets: () => [
      '#8B572A',
      '#D0021B',
      '#F5A623',
      '#F8E71C',
      '#ACDB86',
      '#7ED321',
      '#417505',
      '#BD10E0',
      '#9013FE',
      '#1D7699',
      '#50E3C2',
      '#000000',
      '#4A4A4A',
      '#9B9B9B',
      '#FFFFFF'
    ]
  }
);

const emits = defineEmits<{
  (e: 'change', value: string): void;
}>();

const modelValue = defineModel<string>('value', { required: false });
//
// const color = defineModel<{
//   r: number;
//   g: number;
//   b: number;
//   a?: number;
// } | string>('color', {
//   required: false,
//   default: {
//     r: 212,
//     g: 86,
//     b: 55,
//     a: 1
//   }
// });

const colorPanelRef = ref<HTMLDivElement | null>(null);

const saturation_value = ref<any>(null);
const hue_slider = ref<any>(null);
const alpha_slider = ref<any>(null);
const pointStyle = ref('top: 25%;left: 80%;');
const hueSliderStyle = ref('left: 0;');
const alphaSliderStyle = ref('left: calc(100% - 6px);');

const hue = ref(0);
const saturation = ref(1);
const value = ref(1);

const red = ref(255);
const green = ref(0);
const blue = ref(0);

const alpha = ref(1);

onMounted(() => {
  const { r, g, b, a } = parseColor(modelValue.value)!;
  red.value = r;
  green.value = g;
  blue.value = b;
  alpha.value = a;
});

watch([red, green, blue], newValue => {
  // color.value = {
  //   r: red.value,
  //   g: green.value,
  //   b: blue.value,
  //   a: alpha.value
  // };

  const { h, s, v } = rgb2hsv(red.value, green.value, blue.value);

  hue.value = h;
  saturation.value = s;
  value.value = v;

  // 移动背景板圆圈
  pointStyle.value = `top: ${100 - v * 100}%;left: ${s * 100}%;`;
  // 移动色调滑块
  hueSliderStyle.value = `left: ${(hue.value / 360) * 100}%;`;
});

watch(alpha, () => {
  // color.value = {
  //   r: red.value,
  //   g: green.value,
  //   b: blue.value,
  //   a: alpha.value
  // };
  // 移动透明度滑块
  alphaSliderStyle.value = `left: ${alpha.value >= 1 ? 'calc(100% - 6px)' : `${alpha.value * 100}%`};`;
});

watch(
  () => [red.value, green.value, blue.value, alpha.value],
  () => {
    modelValue.value = rgba2hex(red.value, green.value, blue.value, alpha.value);
    emits('change', modelValue.value);
  }
);

const colorObj = computed(() => {
  const r = red.value;
  const g = green.value;
  const b = blue.value;
  const a = alpha.value;
  const h = hue.value;
  const s = saturation.value;
  const v = value.value;
  return {
    rgb: `rgba(${r},${g},${b})`,
    rgba: `rgba(${r},${g},${b},${a})`,
    hex6: rgba2hex(r, g, b),
    hex8: rgba2hex(r, g, b, a),
    hsv: `hsv(${h},${s},${v})`,
    hsl: ''
  };
});

// 输入框值变化,限制输入的值
function hexChange(e: any) {
  const v = e.target.value;
  if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(v)) {
    const { r, g, b, a } = hex2rgba(v);
    red.value = r;
    green.value = g;
    blue.value = b;
    alpha.value = a;
  }
}

function redChange(e: any) {
  const v = e.target.value;
  if (v !== '') {
    v > 255 && (red.value = 255);
    v < 0 && (red.value = 0);
    v >= 0 && v <= 255 && (red.value = parseInt(v));
  }
}

function greenChange(e: any) {
  const v = e.target.value;
  if (v !== '') {
    v > 255 && (green.value = 255);
    v < 0 && (green.value = 0);
    v >= 0 && v <= 255 && (green.value = parseInt(v));
  }
}

function blueChange(e: any) {
  const v = e.target.value;
  if (v !== '') {
    v > 255 && (blue.value = 255);
    v < 0 && (blue.value = 0);
    v >= 0 && v <= 255 && (blue.value = parseInt(v));
  }
}

function alphaChange(e: any) {
  let v = e.target.value;
  if (v !== '') {
    v = parseFloat(v);
    alpha.value = v;
    v > 1 && (alpha.value = 1);
    v < 0 && (alpha.value = 0);
    v >= 0 && v <= 1 && (alpha.value = v);
  }
}

// 点击预设方块事件
function presetClick(item: string) {
  const { r, g, b, a } = hex2rgba(item);
  red.value = r;
  green.value = g;
  blue.value = b;
  alpha.value = a;
}

// 饱和度和亮度
function handleChangeSV(e: any) {
  const w = saturation_value.value.clientWidth;
  const h = saturation_value.value.clientHeight;
  let x = e.pageX - saturation_value.value.getBoundingClientRect().left;
  let y = e.pageY - saturation_value.value.getBoundingClientRect().top;
  x = x < w && x > 0 ? x : x > w ? w : 0;
  y = y < h && y > 0 ? y : y > h ? h : 0;
  // 计算饱和度和亮度
  saturation.value = Math.floor((x / w) * 100 + 0.5) / 100;
  value.value = Math.floor((1 - y / h) * 100 + 0.5) / 100;
  // hsv转化为rgb
  const { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
  red.value = r;
  green.value = g;
  blue.value = b;
  // 移动背景板圆圈
  pointStyle.value = `top: ${y}px;left: ${x}px;`;
}

function mousedownSV(e: any) {
  // 鼠标按下计算饱和度和亮度并添加事件
  handleChangeSV(e);
  // 添加整个页面的鼠标事件
  window.addEventListener('mousemove', handleChangeSV);
  window.addEventListener('mouseup', mouseupSV);
}

function mouseupSV(e: any) {
  // 鼠标松开后移除事件
  window.removeEventListener('mousemove', handleChangeSV);
  window.removeEventListener('mouseup', mouseupSV);
}

// 色调
function handleChangeHue(e: any) {
  const w = hue_slider.value.clientWidth;
  let x = e.pageX - saturation_value.value.getBoundingClientRect().left;
  x = x < w && x > 0 ? x : x > w ? w : 0;
  // 计算色调
  hue.value = Math.floor((x / w) * 360 + 0.5);
  // hsv转化为rgb
  const { r, g, b } = hsv2rgb(hue.value, saturation.value, value.value);
  red.value = r;
  green.value = g;
  blue.value = b;
  // 移动滑块
  hueSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
}

function mousedownHue(e: any) {
  handleChangeHue(e);
  window.addEventListener('mousemove', handleChangeHue);
  window.addEventListener('mouseup', mouseupHue);
}

function mouseupHue(e: any) {
  window.removeEventListener('mousemove', handleChangeHue);
  window.removeEventListener('mouseup', mouseupHue);
}

// 透明度
function handleChangeAlpha(e: any) {
  const w = alpha_slider.value.clientWidth;
  let x = e.pageX - saturation_value.value.getBoundingClientRect().left;
  x = x < w && x > 0 ? x : x > w ? w : 0;
  // 计算透明度
  alpha.value = Math.floor((x / w) * 100 + 0.5) / 100;
  // 移动滑块
  alphaSliderStyle.value = `left: ${x >= w - 6 ? w - 6 : x}px;`;
}

function mousedownAlpha(e: any) {
  handleChangeAlpha(e);
  window.addEventListener('mousemove', handleChangeAlpha);
  window.addEventListener('mouseup', mouseupAlpha);
}

function mouseupAlpha(e: any) {
  window.removeEventListener('mousemove', handleChangeAlpha);
  window.removeEventListener('mouseup', mouseupAlpha);
}

/**
 * 解析输入的数据,只能解析hex颜色和rgb对象形式的数据
 * @param color
 */
function parseColor(color: any) {
  if (color) {
    let r, g, b, a;
    if (typeof color === 'string') {
      if (/^#?([0-9a-fA-F]{6}|[0-9a-fA-F]{8}|[0-9a-fA-F]{3}|[0-9a-fA-F]{4})$/.test(color)) {
        return hex2rgba(color);
      }
    } else {
      r = color.r > 255 ? 255 : color.r < 0 ? 0 : color.r;
      g = color.g > 255 ? 255 : color.g < 0 ? 0 : color.g;
      b = color.b > 255 ? 255 : color.b < 0 ? 0 : color.b;
      a = color.a > 1 ? 1 : color.a < 0 ? 0 : color.a;
      return { r, g, b, a };
    }
  } else {
    return {};
  }
}

function hsv2rgb(h: number, s: number, v: number) {
  h === 360 && (h = 0);
  const i = Math.floor(h / 60) % 6;
  const f = h / 60 - i;
  const p = v * (1 - s);
  const q = v * (1 - s * f);
  const t = v * (1 - s * (1 - f));
  let r = 0;
  let g = 0;
  let b = 0;
  if (i === 0) {
    r = v;
    g = t;
    b = p;
  } else if (i === 1) {
    r = q;
    g = v;
    b = p;
  } else if (i === 2) {
    r = p;
    g = v;
    b = t;
  } else if (i === 3) {
    r = p;
    g = q;
    b = v;
  } else if (i === 4) {
    r = t;
    g = p;
    b = v;
  } else if (i === 5) {
    r = v;
    g = p;
    b = q;
  }
  r = Math.floor(r * 255 + 0.5);
  g = Math.floor(g * 255 + 0.5);
  b = Math.floor(b * 255 + 0.5);
  return { r, g, b };
}

function rgb2hsv(r: number, g: number, b: number) {
  const r1 = r / 255;
  const g1 = g / 255;
  const b1 = b / 255;
  const cmax = Math.max(r1, g1, b1);
  const cmin = Math.min(r1, g1, b1);
  const d = cmax - cmin;
  let h = 0;
  let s = 0;
  let v = 0;
  if (d === 0) {
    h = 0;
  } else if (cmax === r1) {
    h = ((60 * (g1 - b1)) / d + 360) % 360;
  } else if (cmax === g1) {
    h = 60 * ((b1 - r1) / d + 2);
  } else if (cmax === b1) {
    h = 60 * ((r1 - g1) / d + 4);
  }
  if (cmax === 0) {
    s = 0;
  } else {
    s = d / cmax;
  }
  v = cmax;
  h = Math.floor(h + 0.5);
  s = Math.floor(s * 100 + 0.5) / 100;
  v = Math.floor(v * 100 + 0.5) / 100;
  return { h, s, v };
}

function rgba2hex(r: any, g: any, b: any, a = 1) {
  r = parseInt(r);
  const r1 = r.toString(16).length !== 2 ? `0${r.toString(16)}` : r.toString(16);
  g = parseInt(g);
  const g1 = g.toString(16).length !== 2 ? `0${g.toString(16)}` : g.toString(16);
  b = parseInt(b);
  const b1 = b.toString(16).length !== 2 ? `0${b.toString(16)}` : b.toString(16);
  a = parseFloat(a.toString());
  let a1 = '';
  if (a !== 1) {
    const temp = Math.floor(256 * a);
    a1 = temp.toString(16).length !== 2 ? `0${temp.toString(16)}` : temp.toString(16);
  }
  return `#${r1}${g1}${b1}${a1}`.toUpperCase();
}

function hex2rgba(s: string): { r: number; g: number; b: number; a: number } {
  if (/^#?[0-9a-fA-F]{3}$/.test(s)) {
    const b = s.substring(s.length - 1, s.length);
    const g = s.substring(s.length - 2, s.length - 1);
    const r = s.substring(s.length - 3, s.length - 2);
    return hex2rgba(`${r + r}${g + g}${b + b}`);
  }
  if (/^#?[0-9a-fA-F]{4}$/.test(s)) {
    const a = s.substring(s.length - 1, s.length);
    const b = s.substring(s.length - 2, s.length - 1);
    const g = s.substring(s.length - 3, s.length - 2);
    const r = s.substring(s.length - 4, s.length - 3);
    return hex2rgba(`${r + r}${g + g}${b + b}${a + a}`);
  }
  if (/^#?[0-9a-fA-F]{6}$/.test(s)) {
    const b = parseInt(`0x${s.substring(s.length - 2, s.length)}`);
    const g = parseInt(`0x${s.substring(s.length - 4, s.length - 2)}`);
    const r = parseInt(`0x${s.substring(s.length - 6, s.length - 4)}`);
    return { r, g, b, a: 1 };
  }
  if (/^#?[0-9a-fA-F]{8}$/.test(s)) {
    let a = parseInt(`0x${s.substring(s.length - 2, s.length)}`);
    a = a / 255;
    const b = parseInt(`0x${s.substring(s.length - 4, s.length - 2)}`);
    const g = parseInt(`0x${s.substring(s.length - 6, s.length - 4)}`);
    const r = parseInt(`0x${s.substring(s.length - 8, s.length - 6)}`);
    return { r, g, b, a };
  }
  return { r: 0, g: 0, b: 0, a: 0 };
}

defineExpose({
  color: colorObj,
  ref: colorPanelRef,
  hsv2rgb,
  rgb2hsv,
  rgba2hex,
  hex2rgba
});
</script>

<template>
  <div ref="colorPanelRef" class="color-select" :style="style">
    <div ref="saturation_value" class="saturation-value" @mousedown="mousedownSV">
      <div :style="`background-color: hsl(${hue}, 100%, 50%);`">
        <div class="point" :style="pointStyle" />
      </div>
      <div class="saturation-value-2" />
      <div class="saturation-value-3" />
    </div>
    <div class="color-select-middle">
      <div style="flex: auto">
        <div ref="hue_slider" class="hue-slider" @mousedown="mousedownHue">
          <div class="slider" :style="hueSliderStyle" />
        </div>
        <div ref="alpha_slider" class="alpha-slider" @mousedown="mousedownAlpha">
          <div class="slider" :style="alphaSliderStyle" />
          <div
            :style="`background: linear-gradient(to right, rgba(0,0,0,0), ${colorObj.rgb});width: 100%;height: 100%`"
          />
        </div>
      </div>
      <div class="color-diamond">
        <div
          :style="`background-color: ${colorObj.rgba};width: 100%;height: 100%;box-shadow: inset 0 0 0 1px rgba(0, 0, 0, .15), inset 0 0 4px rgba(0, 0, 0, .25);`"
        />
      </div>
    </div>
    <div class="color-value">
      <div class="hex">
        <label>
          <input :value="colorObj.hex8" spellcheck="false" @input="hexChange">
        </label>
        <p>Hex</p>
      </div>
      <div class="rgba-r">
        <label>
          <input :value="red" @input="redChange">
        </label>
        <p>R</p>
      </div>
      <div class="rgba-g">
        <label>
          <input :value="green" @input="greenChange">
        </label>
        <p>G</p>
      </div>
      <div class="rgba-b">
        <label>
          <input :value="blue" @input="blueChange">
        </label>
        <p>B</p>
      </div>
      <div class="rgba-a">
        <label>
          <input :value="alpha" @input="alphaChange">
        </label>
        <p>A</p>
      </div>
    </div>
    <ul class="preset">
      <li v-for="item in presets" :key="item" :style="`background-color: ${item}`" @click="presetClick(item)" />
    </ul>
  </div>
</template>

<style scoped>
.color-select{background:#fff;overflow:hidden;padding:0;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:100%}.saturation-value{box-shadow:1px 1px 1px rgba(0,0,0,.1);cursor:pointer;height:150px;margin-bottom:10px;position:relative;width:100%}.saturation-value>div{height:100%;left:0;position:absolute;top:0;width:100%}.point{background-color:transparent;border:2px solid #ccc;border-radius:50%;box-sizing:border-box;height:6px;position:absolute;transform:translate(-50%,-50%);width:6px;z-index:9}.saturation-value-2{background:linear-gradient(90deg,#fff,#ffffff00)}.saturation-value-3{background:linear-gradient(0deg,#000,#ffffff00)}.color-select-middle{display:flex;margin-bottom:10px;padding:0 6px;width:100%}.hue-slider{background:linear-gradient(90deg,red,#ff0 17%,#0f0 33%,#0ff 50%,#00f 67%,#f0f 83%,red);margin-bottom:6px}.alpha-slider,.hue-slider{box-shadow:1px 1px 1px rgba(0,0,0,.1);height:10px;position:relative}.alpha-slider{background:#fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");background-size:10px 10px}.slider{background-color:#fff;box-shadow:0 0 2px rgba(0,0,0,.6);box-sizing:border-box;height:100%;position:absolute;width:6px;z-index:1}.color-diamond{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAWElEQVRIiWM8fubkfwYygKWJOSM5+mCAhRLNoxaPWjxq8ajFoxbTyeL/DAfJ0Xjs3Cl7Siwmu4Yht1aDgZEYx6MWj1o8avGoxaMWD3qLya5X//4nqx6HAQC7RBGFzolqTAAAAABJRU5ErkJggg==");background-size:10px 10px;border-radius:3px;height:26px;margin-left:5px;overflow:hidden;position:relative;width:26px}.color-value{display:flex;justify-content:space-between;padding:0 6px;width:100%}.color-value div{padding:0 3px;text-align:center}.color-value input{border:1px solid #ccc;border-radius:3px;box-sizing:border-box;font-size:12px;height:24px;margin:0;outline:none;padding:0;text-align:center;width:34px}.color-value p{font-size:12px;margin:3px 0 0}.color-value .rgba-a{padding-right:0}.color-value .hex{flex:1;padding-left:0}.color-value .hex input{height:24px;width:100%}.preset{display:flex;flex-wrap:wrap;justify-content:flex-start;list-style:none;margin:10px 0 0;padding:0 6px;width:100%}.preset li{border-radius:3px;cursor:pointer;height:16px;margin-bottom:6px;margin-right:6px;width:16px}.preset li:hover{border:1px solid #ccc;opacity:1}
</style>
