<script setup lang="ts">
import { ref, computed, watch, onBeforeUnmount, nextTick } from 'vue'
import { Pipette } from 'lucide-vue-next'
import { computeDropdownPosition } from '../utils/dropdown'
import { useFocusTrap } from '../utils/focusTrap'

interface Props {
  modelValue: string
  presets?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  presets: () => ['#D97706', '#3B82F6', '#8B5CF6', '#EC4899', '#10B981', '#EF4444', '#06B6D4', '#6B7280'],
})

const emit = defineEmits<{
  'update:modelValue': [value: string]
}>()

const { activate: activateTrap, deactivate: deactivateTrap } = useFocusTrap()

const isOpen = ref(false)
const triggerRef = ref<HTMLButtonElement | null>(null)
const panelRef = ref<HTMLDivElement | null>(null)
const panelStyle = ref<Record<string, string>>({})
const hexInput = ref('')

// HSV state for the gradient picker
const hue = ref(0)
const saturation = ref(100)
const brightness = ref(100)

// Canvas refs
const gradientRef = ref<HTMLCanvasElement | null>(null)
const hueRef = ref<HTMLCanvasElement | null>(null)

// Dragging state
const draggingGradient = ref(false)
const draggingHue = ref(false)

// ── Color Conversion ────────────────────────────────────────────────

function hsvToRgb(h: number, s: number, v: number): [number, number, number] {
  s /= 100
  v /= 100
  const c = v * s
  const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
  const m = v - c
  let r = 0, g = 0, b = 0
  if (h < 60) { r = c; g = x; b = 0 }
  else if (h < 120) { r = x; g = c; b = 0 }
  else if (h < 180) { r = 0; g = c; b = x }
  else if (h < 240) { r = 0; g = x; b = c }
  else if (h < 300) { r = x; g = 0; b = c }
  else { r = c; g = 0; b = x }
  return [
    Math.round((r + m) * 255),
    Math.round((g + m) * 255),
    Math.round((b + m) * 255),
  ]
}

function rgbToHsv(r: number, g: number, b: number): [number, number, number] {
  r /= 255; g /= 255; b /= 255
  const max = Math.max(r, g, b)
  const min = Math.min(r, g, b)
  const d = max - min
  let h = 0
  if (d !== 0) {
    if (max === r) h = 60 * (((g - b) / d) % 6)
    else if (max === g) h = 60 * ((b - r) / d + 2)
    else h = 60 * ((r - g) / d + 4)
  }
  if (h < 0) h += 360
  const s = max === 0 ? 0 : (d / max) * 100
  const v = max * 100
  return [h, s, v]
}

function hexToRgb(hex: string): [number, number, number] | null {
  const match = hex.match(/^#?([0-9a-f]{6})$/i)
  if (!match) return null
  const n = parseInt(match[1], 16)
  return [(n >> 16) & 255, (n >> 8) & 255, n & 255]
}

function rgbToHex(r: number, g: number, b: number): string {
  return '#' + [r, g, b].map(c => c.toString(16).padStart(2, '0')).join('').toUpperCase()
}

// ── Current color ────────────────────────────────────────────────────

const currentHex = computed(() => {
  const [r, g, b] = hsvToRgb(hue.value, saturation.value, brightness.value)
  return rgbToHex(r, g, b)
})

// ── Sync from prop ─────────────────────────────────────────────────

function syncFromHex(hex: string) {
  const rgb = hexToRgb(hex)
  if (!rgb) return
  const [h, s, v] = rgbToHsv(...rgb)
  hue.value = h
  saturation.value = s
  brightness.value = v
  hexInput.value = hex.toUpperCase()
}

// Initialize from prop
syncFromHex(props.modelValue || '#D97706')

watch(() => props.modelValue, (val) => {
  if (val && val.toUpperCase() !== currentHex.value) {
    syncFromHex(val)
  }
})

// ── Emit ────────────────────────────────────────────────────────────

function emitColor() {
  hexInput.value = currentHex.value
  emit('update:modelValue', currentHex.value)
}

// ── Hex Input ─────────────────────────────────────────────────────

function onHexInput(e: Event) {
  const val = (e.target as HTMLInputElement).value
  hexInput.value = val
  if (/^#[0-9a-fA-F]{6}$/.test(val)) {
    syncFromHex(val)
    emit('update:modelValue', val.toUpperCase())
  }
}

// ── Preset Click ──────────────────────────────────────────────────

function selectPreset(color: string) {
  syncFromHex(color)
  emitColor()
}

// ── Gradient Canvas (Saturation/Brightness) ────────────────────────

function drawGradient() {
  const canvas = gradientRef.value
  if (!canvas) return
  const ctx = canvas.getContext('2d')
  if (!ctx) return

  const w = canvas.width
  const h = canvas.height

  // Base hue color
  const [r, g, b] = hsvToRgb(hue.value, 100, 100)

  // White to hue (horizontal)
  const gradH = ctx.createLinearGradient(0, 0, w, 0)
  gradH.addColorStop(0, '#FFFFFF')
  gradH.addColorStop(1, `rgb(${r},${g},${b})`)
  ctx.fillStyle = gradH
  ctx.fillRect(0, 0, w, h)

  // Black overlay (vertical)
  const gradV = ctx.createLinearGradient(0, 0, 0, h)
  gradV.addColorStop(0, 'rgba(0,0,0,0)')
  gradV.addColorStop(1, 'rgba(0,0,0,1)')
  ctx.fillStyle = gradV
  ctx.fillRect(0, 0, w, h)
}

function drawHueStrip() {
  const canvas = hueRef.value
  if (!canvas) return
  const ctx = canvas.getContext('2d')
  if (!ctx) return

  const w = canvas.width
  const h = canvas.height
  const grad = ctx.createLinearGradient(0, 0, w, 0)
  for (let i = 0; i <= 360; i += 60) {
    const [r, g, b] = hsvToRgb(i, 100, 100)
    grad.addColorStop(i / 360, `rgb(${r},${g},${b})`)
  }
  ctx.fillStyle = grad
  ctx.fillRect(0, 0, w, h)
}

// ── Gradient Pointer ────────────────────────────────────────────────

const gradientCursorX = computed(() => (saturation.value / 100) * 100)
const gradientCursorY = computed(() => ((100 - brightness.value) / 100) * 100)
const hueCursorX = computed(() => (hue.value / 360) * 100)

function handleGradientInteraction(e: MouseEvent | PointerEvent) {
  const canvas = gradientRef.value
  if (!canvas) return
  const rect = canvas.getBoundingClientRect()
  const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
  const y = Math.max(0, Math.min(1, (e.clientY - rect.top) / rect.height))
  saturation.value = x * 100
  brightness.value = (1 - y) * 100
  emitColor()
}

function handleHueInteraction(e: MouseEvent | PointerEvent) {
  const canvas = hueRef.value
  if (!canvas) return
  const rect = canvas.getBoundingClientRect()
  const x = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width))
  hue.value = x * 360
  nextTick(() => drawGradient())
  emitColor()
}

function onGradientPointerDown(e: PointerEvent) {
  draggingGradient.value = true
  ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
  handleGradientInteraction(e)
}

function onGradientPointerMove(e: PointerEvent) {
  if (!draggingGradient.value) return
  handleGradientInteraction(e)
}

function onGradientPointerUp() {
  draggingGradient.value = false
}

function onHuePointerDown(e: PointerEvent) {
  draggingHue.value = true
  ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)
  handleHueInteraction(e)
}

function onHuePointerMove(e: PointerEvent) {
  if (!draggingHue.value) return
  handleHueInteraction(e)
}

function onHuePointerUp() {
  draggingHue.value = false
}

// ── Positioning ─────────────────────────────────────────────────────

function updatePosition() {
  if (!triggerRef.value) return
  panelStyle.value = computeDropdownPosition(triggerRef.value, { minWidth: 260, estimatedHeight: 330, panelEl: panelRef.value })
}

// ── Open / Close ────────────────────────────────────────────────────

function toggle() {
  if (isOpen.value) close()
  else open()
}

function open() {
  syncFromHex(props.modelValue || '#D97706')
  isOpen.value = true
  updatePosition()

  nextTick(() => {
    updatePosition()
    drawGradient()
    drawHueStrip()
    document.addEventListener('click', onClickOutside, true)
    document.addEventListener('scroll', onScrollOrResize, true)
    window.addEventListener('resize', onScrollOrResize)
    if (panelRef.value) activateTrap(panelRef.value)
  })
}

function close() {
  deactivateTrap()
  isOpen.value = false
  document.removeEventListener('click', onClickOutside, true)
  document.removeEventListener('scroll', onScrollOrResize, true)
  window.removeEventListener('resize', onScrollOrResize)
}

function onClickOutside(e: MouseEvent) {
  const target = e.target as Node
  if (triggerRef.value?.contains(target) || panelRef.value?.contains(target)) return
  close()
}

function onScrollOrResize() {
  if (isOpen.value) updatePosition()
}

// Redraw gradient when hue changes
watch(hue, () => {
  if (isOpen.value) nextTick(() => drawGradient())
})

onBeforeUnmount(() => {
  document.removeEventListener('click', onClickOutside, true)
  document.removeEventListener('scroll', onScrollOrResize, true)
  window.removeEventListener('resize', onScrollOrResize)
})

// ── Keyboard Handlers for Accessibility ─────────────────────────────

function onGradientKeydown(e: KeyboardEvent) {
  const step = 5
  let handled = false
  if (e.key === 'ArrowRight') { saturation.value = Math.min(100, saturation.value + step); handled = true }
  else if (e.key === 'ArrowLeft') { saturation.value = Math.max(0, saturation.value - step); handled = true }
  else if (e.key === 'ArrowUp') { brightness.value = Math.min(100, brightness.value + step); handled = true }
  else if (e.key === 'ArrowDown') { brightness.value = Math.max(0, brightness.value - step); handled = true }
  if (handled) {
    e.preventDefault()
    emitColor()
  }
}

function onHueKeydown(e: KeyboardEvent) {
  const step = 5
  if (e.key === 'ArrowRight' || e.key === 'ArrowUp') {
    e.preventDefault()
    hue.value = Math.min(360, hue.value + step)
    emitColor()
  } else if (e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
    e.preventDefault()
    hue.value = Math.max(0, hue.value - step)
    emitColor()
  }
}
</script>

<template>
  <div class="relative">
    <!-- Trigger button -->
    <button
      ref="triggerRef"
      type="button"
      @click="toggle"
      aria-label="Color picker"
      :aria-expanded="isOpen"
      aria-haspopup="dialog"
      class="w-full flex items-center gap-2.5 px-3 py-2 bg-bg-inset border border-border-subtle rounded-xl text-[0.8125rem] text-left cursor-pointer transition-colors"
      :style="
        isOpen
          ? {
              borderColor: 'var(--color-accent)',
              boxShadow: '0 0 0 2px var(--color-accent-muted)',
              outline: 'none',
            }
          : {}
      "
    >
      <span
        role="img"
        :aria-label="'Current color: ' + (modelValue?.toUpperCase() || '#000000')"
        class="w-5 h-5 rounded-md border border-border-subtle shrink-0"
        :style="{ backgroundColor: modelValue }"
      />
      <span class="text-text-primary font-mono text-[0.75rem] tracking-wide">{{ modelValue?.toUpperCase() || '#000000' }}</span>
      <Pipette class="w-4 h-4 text-text-secondary shrink-0 ml-auto" :stroke-width="2" aria-hidden="true" />
    </button>

    <!-- Color picker popover -->
    <Teleport to="#app">
      <Transition name="dropdown">
      <div
        v-if="isOpen"
        ref="panelRef"
        :style="panelStyle"
        role="dialog"
        aria-modal="true"
        aria-label="Choose color"
        @keydown.escape.prevent="close"
        class="bg-bg-surface border border-border-visible rounded-xl shadow-[0_4px_24px_rgba(0,0,0,0.4)] overflow-hidden"
      >
        <!-- Preset swatches -->
        <div class="px-3 pt-3 pb-2">
          <div class="flex gap-2 flex-wrap">
            <button
              v-for="c in presets"
              :key="c"
              type="button"
              @click="selectPreset(c)"
              :aria-label="'Color preset ' + c"
              :aria-pressed="currentHex === c.toUpperCase()"
              class="w-8 h-8 rounded-full border-2 transition-colors cursor-pointer hover:scale-110"
              :class="currentHex === c.toUpperCase() ? 'border-text-primary' : 'border-transparent'"
              :style="{ backgroundColor: c }"
            />
          </div>
        </div>

        <!-- Saturation/Brightness gradient -->
        <div class="px-3 pb-2">
          <div
            role="application"
            aria-label="Saturation and brightness"
            tabindex="0"
            class="relative rounded-lg overflow-hidden cursor-crosshair"
            style="touch-action: none;"
            @pointerdown="onGradientPointerDown"
            @pointermove="onGradientPointerMove"
            @pointerup="onGradientPointerUp"
            @keydown="onGradientKeydown"
          >
            <canvas
              ref="gradientRef"
              width="260"
              height="150"
              class="w-full h-[150px] block rounded-lg"
            />
            <!-- Cursor -->
            <div
              class="absolute w-4 h-4 rounded-full border-2 border-white shadow-[0_0_2px_rgba(0,0,0,0.6)] pointer-events-none -translate-x-1/2 -translate-y-1/2"
              :style="{
                left: gradientCursorX + '%',
                top: gradientCursorY + '%',
                backgroundColor: currentHex,
              }"
            />
          </div>
        </div>

        <!-- Hue slider -->
        <div class="px-3 pb-2">
          <div
            role="slider"
            aria-label="Hue"
            :aria-valuenow="Math.round(hue)"
            aria-valuemin="0"
            aria-valuemax="360"
            tabindex="0"
            class="relative rounded-md overflow-hidden cursor-pointer"
            style="touch-action: none;"
            @pointerdown="onHuePointerDown"
            @pointermove="onHuePointerMove"
            @pointerup="onHuePointerUp"
            @keydown="onHueKeydown"
          >
            <canvas
              ref="hueRef"
              width="260"
              height="14"
              class="w-full h-3.5 block rounded-md"
            />
            <!-- Hue cursor -->
            <div
              class="absolute top-1/2 w-3.5 h-3.5 rounded-full border-2 border-white shadow-[0_0_2px_rgba(0,0,0,0.6)] pointer-events-none -translate-x-1/2 -translate-y-1/2"
              :style="{
                left: hueCursorX + '%',
                backgroundColor: rgbToHex(...hsvToRgb(hue, 100, 100)),
              }"
            />
          </div>
        </div>

        <!-- Hex input + preview -->
        <div class="px-3 pb-3 flex items-center gap-2">
          <span
            role="img"
            :aria-label="'Selected color: ' + currentHex"
            class="w-8 h-8 rounded-lg border border-border-subtle shrink-0"
            :style="{ backgroundColor: currentHex }"
          />
          <input
            :value="hexInput"
            @input="onHexInput"
            type="text"
            maxlength="7"
            aria-label="Hex color value"
            class="flex-1 px-2.5 py-1.5 bg-bg-inset border border-border-subtle rounded-lg text-[0.75rem] text-text-primary font-mono tracking-wide placeholder-text-tertiary focus:outline-none focus:border-border-visible"
            placeholder="#D97706"
          />
        </div>
      </div>
      </Transition>
    </Teleport>
  </div>
</template>
