import Wheel from "@uiw/react-color-wheel";
import { hsvaToRgba, rgbaToHsva } from "@uiw/color-convert";
import { RotateCcw, Zap } from "lucide-react";
import { useEffect, useRef, useState } from "react";

import { useThrottledParameterValue } from "../hooks/useThrottledParameterValue";

function valuesMatch(left, right) {
  return JSON.stringify(left) === JSON.stringify(right);
}

function ParameterHeader({ layer, parameter, onReset, resetDisabled }) {
  const layerKey = layer.shaderId || layer.shaderName || layer.id;
  const oscRoute = `/VideoShaderToys/${layerKey}/${parameter.id}`;

  function copyRoute() {
    if (navigator.clipboard) {
      navigator.clipboard.writeText(oscRoute);
    }
  }

  return (
    <div className="parameter__header">
      <div className="parameter__title">
        <label>{parameter.label}</label>
        {parameter.description ? <p title={parameter.description}>{parameter.description}</p> : null}
      </div>
      <button
        type="button"
        className="parameter__osc"
        title="Copy OSC route"
        aria-label={`Copy OSC route ${oscRoute}`}
        onClick={copyRoute}
      >
        <span aria-hidden="true">OSC</span>
      </button>
      <button
        type="button"
        className="parameter__reset"
        title={`Reset ${parameter.label}`}
        aria-label={`Reset ${parameter.label}`}
        disabled={resetDisabled}
        onClick={onReset}
      >
        <RotateCcw size={13} strokeWidth={1.9} aria-hidden="true" />
      </button>
    </div>
  );
}

function clamp01(value) {
  return Math.max(0, Math.min(1, Number(value) || 0));
}

function colorComponentToHex(value) {
  return Math.round(clamp01(value) * 255)
    .toString(16)
    .padStart(2, "0");
}

function colorValueToHex(value) {
  const values = [...(value ?? [])];
  while (values.length < 3) {
    values.push(0);
  }
  return `#${colorComponentToHex(values[0])}${colorComponentToHex(values[1])}${colorComponentToHex(values[2])}`;
}

function colorValueToHsva(value) {
  const values = [...(value ?? [])];
  while (values.length < 4) {
    values.push(values.length === 3 ? 1 : 0);
  }
  return rgbaToHsva({
    r: Math.round(clamp01(values[0]) * 255),
    g: Math.round(clamp01(values[1]) * 255),
    b: Math.round(clamp01(values[2]) * 255),
    a: clamp01(values[3]),
  });
}

function hsvaToColorValue(hsva, alpha) {
  const rgba = hsvaToRgba({ ...hsva, a: clamp01(alpha ?? hsva.a ?? 1) });
  return [
    clamp01(rgba.r / 255),
    clamp01(rgba.g / 255),
    clamp01(rgba.b / 255),
    clamp01(alpha ?? rgba.a ?? 1),
  ];
}

export function ParameterField({ layer, parameter, onParameterChange }) {
  const [colorPickerOpen, setColorPickerOpen] = useState(false);
  const colorControlRef = useRef(null);
  const {
    beginInteraction,
    draftValue,
    endInteraction,
    scheduleSendValue,
    sendValue,
  } = useThrottledParameterValue(parameter, onParameterChange);

  const defaultValue = parameter.defaultValue;
  const resetDisabled = parameter.type === "trigger" || defaultValue === undefined || valuesMatch(draftValue, defaultValue);
  const resetParameter = () => {
    if (parameter.type !== "trigger" && defaultValue !== undefined) {
      sendValue(defaultValue);
    }
  };

  useEffect(() => {
    if (!colorPickerOpen) {
      return undefined;
    }

    function handlePointerDown(event) {
      if (!colorControlRef.current || !colorControlRef.current.contains(event.target)) {
        setColorPickerOpen(false);
      }
    }

    function handleKeyDown(event) {
      if (event.key === "Escape") {
        setColorPickerOpen(false);
      }
    }

    document.addEventListener("pointerdown", handlePointerDown, true);
    document.addEventListener("keydown", handleKeyDown);
    return () => {
      document.removeEventListener("pointerdown", handlePointerDown, true);
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [colorPickerOpen]);

  const header = (
    <ParameterHeader
      layer={layer}
      parameter={parameter}
      resetDisabled={resetDisabled}
      onReset={resetParameter}
    />
  );

  if (parameter.type === "float") {
    return (
      <section className="parameter">
        {header}
        <div className="parameter__control">
          <div className="parameter__pair">
            <input
              type="range"
              min={parameter.min?.[0] ?? 0}
              max={parameter.max?.[0] ?? 1}
              step={parameter.step?.[0] ?? 0.01}
              value={draftValue}
              onMouseDown={beginInteraction}
              onPointerDown={beginInteraction}
              onTouchStart={beginInteraction}
              onChange={(event) => scheduleSendValue(Number(event.target.value))}
              onMouseUp={endInteraction}
              onTouchEnd={endInteraction}
              onPointerUp={endInteraction}
              onKeyDown={beginInteraction}
              onKeyUp={endInteraction}
              onBlur={endInteraction}
            />
            <input
              type="number"
              min={parameter.min?.[0] ?? ""}
              max={parameter.max?.[0] ?? ""}
              step={parameter.step?.[0] ?? 0.01}
              value={draftValue}
              onFocus={beginInteraction}
              onChange={(event) => sendValue(Number(event.target.value))}
              onBlur={endInteraction}
            />
          </div>
        </div>
      </section>
    );
  }

  if (parameter.type === "vec2") {
    const values = [...(draftValue ?? [])];
    while (values.length < 2) {
      values.push(0);
    }

    return (
      <section className="parameter">
        {header}
        <div className="parameter__control">
          <div className="parameter__pair parameter__pair--vec2">
            {Array.from({ length: 2 }, (_, index) => (
              <input
                key={index}
                type="number"
                aria-label={`${parameter.label} ${index === 0 ? "X" : "Y"}`}
                min={parameter.min?.[index] ?? ""}
                max={parameter.max?.[index] ?? ""}
                step={parameter.step?.[index] ?? 0.01}
                value={values[index]}
                onFocus={beginInteraction}
                onChange={(event) => {
                  const next = [...values];
                  next[index] = Number(event.target.value);
                  sendValue(next);
                }}
                onBlur={endInteraction}
              />
            ))}
          </div>
        </div>
      </section>
    );
  }

  if (parameter.type === "color") {
    const values = [...(draftValue ?? [])];
    while (values.length < 4) {
      values.push(values.length === 3 ? 1 : 0);
    }
    const hsva = colorValueToHsva(values);
    const wheelHsva = { ...hsva, v: 100 };
    const sendHsva = (nextHsva) => scheduleSendValue(hsvaToColorValue(nextHsva, values[3]));
    const updateColorComponent = (index, nextValue) => {
      const next = [...values];
      next[index] = Number(nextValue);
      sendValue(next);
    };

    return (
      <section className="parameter parameter--color">
        {header}
        <div className="parameter__control" ref={colorControlRef}>
          <div className="parameter__color-compact">
            <button
              type="button"
              className="parameter__swatch-button"
              title={`Open ${parameter.label} color picker`}
              aria-label={`Open ${parameter.label} color picker`}
              onClick={() => setColorPickerOpen((open) => !open)}
            >
              <span className="parameter__swatch" style={{ background: colorValueToHex(values) }} aria-hidden="true" />
            </button>
            <div className="parameter__rgba-grid">
              {["R", "G", "B", "A"].map((label, index) => (
                <label key={label} className="parameter__rgba-field">
                  <span>{label}</span>
                  <input
                    type="number"
                    min={parameter.min?.[index] ?? 0}
                    max={parameter.max?.[index] ?? 1}
                    step={parameter.step?.[index] ?? 0.01}
                    value={values[index]}
                    onFocus={beginInteraction}
                    onChange={(event) => updateColorComponent(index, event.target.value)}
                    onBlur={endInteraction}
                  />
                </label>
              ))}
            </div>
          </div>
          {colorPickerOpen ? (
            <div className="parameter__color-popover">
              <div
                className="parameter__wheel"
                onPointerDown={beginInteraction}
                onPointerUp={endInteraction}
                onPointerCancel={endInteraction}
                onBlur={endInteraction}
              >
                <Wheel
                  color={wheelHsva}
                  width={196}
                  height={196}
                  onChange={(color) => sendHsva({ ...color.hsva, v: hsva.v })}
                />
              </div>
              <label className="parameter__value-slider">
                <span>Value</span>
                <input
                  type="range"
                  min={0}
                  max={100}
                  step={1}
                  value={Math.round(hsva.v)}
                  aria-label={`${parameter.label} value`}
                  onMouseDown={beginInteraction}
                  onPointerDown={beginInteraction}
                  onTouchStart={beginInteraction}
                  onChange={(event) => sendHsva({ ...hsva, v: Number(event.target.value) })}
                  onMouseUp={endInteraction}
                  onTouchEnd={endInteraction}
                  onPointerUp={endInteraction}
                  onKeyDown={beginInteraction}
                  onKeyUp={endInteraction}
                  onBlur={endInteraction}
                />
              </label>
            </div>
          ) : null}
        </div>
      </section>
    );
  }

  if (parameter.type === "bool") {
    return (
      <section className="parameter">
        {header}
        <div className="parameter__control">
          <label className="toggle toggle--field">
            <input
              type="checkbox"
              checked={Boolean(draftValue)}
              onFocus={beginInteraction}
              onChange={(event) => sendValue(event.target.checked)}
              onBlur={endInteraction}
            />
            <span>{draftValue ? "Enabled" : "Disabled"}</span>
          </label>
        </div>
      </section>
    );
  }

  if (parameter.type === "enum") {
    return (
      <section className="parameter">
        {header}
        <div className="parameter__control">
          <select
            value={draftValue}
            onFocus={beginInteraction}
            onChange={(event) => sendValue(event.target.value)}
            onBlur={endInteraction}
          >
            {parameter.options.map((option) => (
              <option key={option.value} value={option.value}>
                {option.label}
              </option>
            ))}
          </select>
        </div>
      </section>
    );
  }

  if (parameter.type === "text") {
    return (
      <section className="parameter">
        {header}
        <div className="parameter__control">
          <input
            type="text"
            maxLength={parameter.maxLength ?? 64}
            placeholder={parameter.defaultValue ? `Default: ${parameter.defaultValue}` : ""}
            value={draftValue ?? ""}
            onFocus={beginInteraction}
            onChange={(event) => scheduleSendValue(event.target.value, false, 350)}
            onBlur={endInteraction}
          />
        </div>
      </section>
    );
  }

  if (parameter.type === "trigger") {
    const triggerCount = Number(draftValue ?? 0);
    return (
      <section className="parameter">
        {header}
        <div className="parameter__control">
          <button
            type="button"
            className="button-with-icon parameter__trigger"
            onClick={() => sendValue(triggerCount + 1)}
          >
            <Zap size={16} strokeWidth={1.9} aria-hidden="true" />
            <span>Trigger</span>
          </button>
        </div>
      </section>
    );
  }

  return null;
}
