import { h } from '../core/dom';

/* ---------- conversions ---------- */
export function hexToRgb(hex: string): { r: number; g: number; b: number } {
  let v = hex.replace('#', '').trim();
  if (v.length === 3) v = v.split('').map((c) => c + c).join('');
  const n = parseInt(v || '0', 16);
  return { r: (n >> 16) & 255, g: (n >> 8) & 255, b: n & 255 };
}
export function rgbToHex(r: number, g: number, b: number): string {
  const c = (x: number) => Math.max(0, Math.min(255, Math.round(x))).toString(16).padStart(2, '0');
  return `#${c(r)}${c(g)}${c(b)}`;
}
function rgbToHsv(r: number, g: number, b: number) {
  r /= 255; g /= 255; b /= 255;
  const max = Math.max(r, g, b), min = Math.min(r, g, b), d = max - min;
  let hh = 0;
  if (d) {
    if (max === r) hh = ((g - b) / d) % 6;
    else if (max === g) hh = (b - r) / d + 2;
    else hh = (r - g) / d + 4;
    hh *= 60;
    if (hh < 0) hh += 360;
  }
  return { h: hh, s: max ? d / max : 0, v: max };
}
function hsvToRgb(h: number, s: number, v: number) {
  const c = v * s, x = c * (1 - Math.abs(((h / 60) % 2) - 1)), m = v - c;
  let r = 0, g = 0, b = 0;
  if (h < 60) [r, g, b] = [c, x, 0];
  else if (h < 120) [r, g, b] = [x, c, 0];
  else if (h < 180) [r, g, b] = [0, c, x];
  else if (h < 240) [r, g, b] = [0, x, c];
  else if (h < 300) [r, g, b] = [x, 0, c];
  else [r, g, b] = [c, 0, x];
  return { r: (r + m) * 255, g: (g + m) * 255, b: (b + m) * 255 };
}

const DEFAULT_PRESETS = ['#12141b', '#565d6b', '#ffffff', '#2f6bff', '#7b5cff', '#12b5c9', '#1aa053', '#e0a500', '#e0483d', '#ff8fab', '#ffe27a', '#a6f0c6'];

export interface ColorPickerOptions {
  value: string;
  presets?: string[];
  allowNone?: boolean; // adds a "none"/transparent option (returns 'none')
  onpick: (color: string) => void;
}

/**
 * A color control: a row of preset swatches plus a trigger that opens a full
 * HSV popover with a saturation/value area, hue slider, and hex/RGB inputs.
 */
export function colorPicker(opts: ColorPickerOptions): HTMLElement {
  const presets = opts.presets ?? DEFAULT_PRESETS;
  let current = opts.value;

  const trigger = h('button.color-trigger', { title: 'Custom color', type: 'button' });
  const paintTrigger = () => {
    if (current === 'none') { trigger.classList.add('is-none'); trigger.style.background = ''; }
    else { trigger.classList.remove('is-none'); trigger.style.background = current; }
  };

  const swatchRow = h('div.color-swatches');
  const paintSwatches = () => {
    swatchRow.replaceChildren(
      ...(opts.allowNone ? [noneSwatch()] : []),
      ...presets.map((c) =>
        h('button.color-sw' + (sameColor(c, current) ? ' on' : ''), {
          type: 'button', style: { background: c }, title: c, 'aria-label': c,
          onclick: () => set(c),
        }),
      ),
      trigger,
    );
  };
  function noneSwatch() {
    return h('button.color-sw color-none' + (current === 'none' ? ' on' : ''), { type: 'button', title: 'None', onclick: () => set('none') }, '∅');
  }

  const set = (c: string) => { current = c; paintTrigger(); paintSwatches(); opts.onpick(c); };

  trigger.addEventListener('click', (e) => { e.preventDefault(); openPopover(trigger, current === 'none' ? '#2f6bff' : current, (c) => set(c)); });

  paintTrigger();
  paintSwatches();
  return h('div.color-picker', swatchRow);
}

function sameColor(a: string, b: string): boolean {
  if (a === b) return true;
  if (a === 'none' || b === 'none') return false;
  try { return rgbToHex(...(Object.values(hexToRgb(a)) as [number, number, number])) === rgbToHex(...(Object.values(hexToRgb(b)) as [number, number, number])); } catch { return false; }
}

/* ---------- the popover ---------- */
function openPopover(anchor: HTMLElement, initial: string, onChange: (hex: string) => void) {
  document.querySelector('.color-pop')?.remove();

  let { r, g, b } = hexToRgb(initial);
  let { h: hue, s, v } = rgbToHsv(r, g, b);

  const sv = h('div.color-sv');
  const svThumb = h('div.color-sv-thumb');
  sv.appendChild(svThumb);
  const hueBar = h('div.color-hue');
  const hueThumb = h('div.color-hue-thumb');
  hueBar.appendChild(hueThumb);

  const hexInput = h('input.input color-hex', { value: initial }) as HTMLInputElement;
  const rIn = numField(), gIn = numField(), bIn = numField();

  const pop = h(
    'div.color-pop glass',
    sv,
    hueBar,
    h('div.color-fields',
      h('label.color-field', h('span', 'HEX'), hexInput),
      h('div.color-rgb',
        h('label.color-field', h('span', 'R'), rIn),
        h('label.color-field', h('span', 'G'), gIn),
        h('label.color-field', h('span', 'B'), bIn),
      ),
    ),
  );

  function syncFromHsv(emit = true) {
    const rgb = hsvToRgb(hue, s, v);
    r = rgb.r; g = rgb.g; b = rgb.b;
    const hex = rgbToHex(r, g, b);
    sv.style.background = `linear-gradient(to top, #000, transparent), linear-gradient(to right, #fff, ${rgbToHex(...(Object.values(hsvToRgb(hue, 1, 1)) as [number, number, number]))})`;
    svThumb.style.left = `${s * 100}%`;
    svThumb.style.top = `${(1 - v) * 100}%`;
    svThumb.style.background = hex;
    hueThumb.style.left = `${(hue / 360) * 100}%`;
    hexInput.value = hex;
    rIn.value = String(Math.round(r)); gIn.value = String(Math.round(g)); bIn.value = String(Math.round(b));
    if (emit) onChange(hex);
  }

  const dragSV = (e: PointerEvent) => {
    const rect = sv.getBoundingClientRect();
    s = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
    v = Math.max(0, Math.min(1, 1 - (e.clientY - rect.top) / rect.height));
    syncFromHsv();
  };
  const dragHue = (e: PointerEvent) => {
    const rect = hueBar.getBoundingClientRect();
    hue = Math.max(0, Math.min(360, ((e.clientX - rect.left) / rect.width) * 360));
    syncFromHsv();
  };
  attachDrag(sv, dragSV);
  attachDrag(hueBar, dragHue);

  hexInput.addEventListener('input', () => {
    const m = hexInput.value.match(/^#?[0-9a-f]{6}$/i);
    if (m) { const c = hexToRgb(hexInput.value); const hsv = rgbToHsv(c.r, c.g, c.b); hue = hsv.h; s = hsv.s; v = hsv.v; syncFromHsv(); }
  });
  const rgbChanged = () => {
    const nr = clampByte(rIn.value), ng = clampByte(gIn.value), nb = clampByte(bIn.value);
    const hsv = rgbToHsv(nr, ng, nb); hue = hsv.h; s = hsv.s; v = hsv.v; syncFromHsv();
  };
  [rIn, gIn, bIn].forEach((el) => el.addEventListener('input', rgbChanged));

  document.body.appendChild(pop);
  positionPopover(pop, anchor);
  syncFromHsv(false);

  const onDown = (e: MouseEvent) => { if (!pop.contains(e.target as Node) && e.target !== anchor) close(); };
  const onKey = (e: KeyboardEvent) => { if (e.key === 'Escape') close(); };
  function close() { pop.remove(); document.removeEventListener('mousedown', onDown); document.removeEventListener('keydown', onKey); }
  setTimeout(() => { document.addEventListener('mousedown', onDown); document.addEventListener('keydown', onKey); }, 0);
}

function attachDrag(el: HTMLElement, handler: (e: PointerEvent) => void) {
  el.addEventListener('pointerdown', (e) => {
    e.preventDefault();
    handler(e);
    const move = (ev: PointerEvent) => handler(ev);
    const up = () => { window.removeEventListener('pointermove', move); window.removeEventListener('pointerup', up); };
    window.addEventListener('pointermove', move);
    window.addEventListener('pointerup', up);
  });
}

function numField(): HTMLInputElement {
  return h('input.color-num', { type: 'number', min: 0, max: 255 }) as HTMLInputElement;
}
function clampByte(s: string): number { return Math.max(0, Math.min(255, parseInt(s, 10) || 0)); }

function positionPopover(pop: HTMLElement, anchor: HTMLElement) {
  const a = anchor.getBoundingClientRect();
  const w = 232, hgt = 260;
  let left = a.left;
  let top = a.bottom + 8;
  if (left + w > window.innerWidth - 8) left = window.innerWidth - w - 8;
  if (top + hgt > window.innerHeight - 8) top = Math.max(8, a.top - hgt - 8);
  pop.style.left = `${Math.max(8, left)}px`;
  pop.style.top = `${top}px`;
}
