'use client';

import { useEffect, useRef, useState } from 'react';
import { getColorName } from '@/lib/colorNames';

function frontFieldName(hex: string) {
  return `colorPhoto__${hex.replace('#', '')}`;
}
function backFieldName(hex: string) {
  return `colorPhotoBack__${hex.replace('#', '')}`;
}

function loadImage(src: string): Promise<HTMLImageElement> {
  return new Promise((resolve, reject) => {
    const img = new window.Image();
    img.onload = () => resolve(img);
    img.onerror = reject;
    img.src = src;
  });
}

// Samples the photo on an offscreen canvas and averages the non-background
// pixels — product photos are typically shot on a plain white/light backdrop,
// so pixels that are both very light and low-saturation (the backdrop) are
// excluded, leaving mostly garment pixels to average into one hex color.
async function detectDominantColor(file: File): Promise<string> {
  const objectUrl = URL.createObjectURL(file);
  try {
    const img = await loadImage(objectUrl);
    const size = 120;
    const canvas = document.createElement('canvas');
    canvas.width = size;
    canvas.height = size;
    const ctx = canvas.getContext('2d');
    if (!ctx) return '#808080';
    ctx.drawImage(img, 0, 0, size, size);
    const { data } = ctx.getImageData(0, 0, size, size);

    let r = 0;
    let g = 0;
    let b = 0;
    let count = 0;
    for (let i = 0; i < data.length; i += 4) {
      const pr = data[i];
      const pg = data[i + 1];
      const pb = data[i + 2];
      const pa = data[i + 3];
      if (pa < 200) continue;
      const max = Math.max(pr, pg, pb);
      const min = Math.min(pr, pg, pb);
      const lightness = (max + min) / 2 / 255;
      const sat = max === min ? 0 : (max - min) / (255 - Math.abs(max + min - 255));
      if (lightness > 0.92 && sat < 0.12) continue; // near-white/gray backdrop
      r += pr;
      g += pg;
      b += pb;
      count += 1;
    }
    if (count === 0) {
      r = data[0];
      g = data[1];
      b = data[2];
      count = 1;
    }
    r = Math.round(r / count);
    g = Math.round(g / count);
    b = Math.round(b / count);
    return `#${[r, g, b].map((x) => x.toString(16).padStart(2, '0')).join('')}`;
  } finally {
    URL.revokeObjectURL(objectUrl);
  }
}

function hexDistance(a: string, b: string) {
  const [ar, ag, ab] = [1, 3, 5].map((i) => parseInt(a.slice(i, i + 2), 16));
  const [br, bg, bb] = [1, 3, 5].map((i) => parseInt(b.slice(i, i + 2), 16));
  return Math.sqrt((ar - br) ** 2 + (ag - bg) ** 2 + (ab - bb) ** 2);
}

// Snaps a freshly-detected color onto an existing one if they're close
// (different lighting/JPEG noise shouldn't produce two near-identical
// swatches for what's really the same shirt color).
function findNearbyColor(hex: string, existing: string[], threshold = 30) {
  return existing.find((c) => hexDistance(c, hex) < threshold) ?? null;
}

type BulkResult = { filename: string; hex: string; variant: 'front' | 'back' };

export default function ColorPicker({
  name,
  defaultColors = [],
  defaultColorPhotos = {},
  defaultColorPhotosBack = {},
}: {
  name: string;
  defaultColors?: string[];
  defaultColorPhotos?: Record<string, string>;
  defaultColorPhotosBack?: Record<string, string>;
}) {
  const [colors, setColors] = useState<string[]>(defaultColors.length ? defaultColors : ['#17181C']);
  const [picked, setPicked] = useState('#1EA7E0');
  // Local previews for newly-picked files, so admins see what they just attached.
  const [previews, setPreviews] = useState<Record<string, string>>({});
  const [previewsBack, setPreviewsBack] = useState<Record<string, string>>({});

  const bulkInputRef = useRef<HTMLInputElement | null>(null);
  const fileInputRefs = useRef<Map<string, HTMLInputElement>>(new Map());
  const pendingFilesRef = useRef<Map<string, File>>(new Map());
  const [bulkBusy, setBulkBusy] = useState(false);
  const [bulkResults, setBulkResults] = useState<BulkResult[]>([]);

  const addColor = () => {
    if (colors.includes(picked)) return;
    setColors((prev) => [...prev, picked]);
  };

  const removeColor = (c: string) => {
    setColors((prev) => prev.filter((x) => x !== c));
  };

  const handlePhotoChange = (hex: string, file: File | null, back: boolean) => {
    const setter = back ? setPreviewsBack : setPreviews;
    if (!file) {
      setter((prev) => {
        const next = { ...prev };
        delete next[hex];
        return next;
      });
      return;
    }
    setter((prev) => ({ ...prev, [hex]: URL.createObjectURL(file) }));
  };

  // Once a pending bulk-detected file's color has a rendered <input> (i.e. its
  // color row just mounted), push the File into that native input via
  // DataTransfer and fire the same change handler a manual pick would — so
  // the preview and the real file both update, and the form submits it
  // exactly like any other per-color upload.
  useEffect(() => {
    if (pendingFilesRef.current.size === 0) return;
    for (const [key, file] of pendingFilesRef.current) {
      const input = fileInputRefs.current.get(key);
      if (!input) continue;
      const dt = new DataTransfer();
      dt.items.add(file);
      input.files = dt.files;
      const [hex, variant] = key.split('__');
      handlePhotoChange(hex, file, variant === 'back');
      pendingFilesRef.current.delete(key);
    }
  }, [colors]);

  const handleBulkFiles = async (files: FileList | null) => {
    if (!files || files.length === 0) return;
    setBulkBusy(true);
    setBulkResults([]);
    const results: BulkResult[] = [];
    const nextColors = [...colors];

    for (const file of Array.from(files)) {
      if (!file.type.startsWith('image/')) continue;
      const variant: 'front' | 'back' = /back/i.test(file.name) ? 'back' : 'front';
      const detected = await detectDominantColor(file);
      const hex = findNearbyColor(detected, nextColors) ?? detected;
      if (!nextColors.includes(hex)) nextColors.push(hex);

      const key = `${hex}__${variant}`;
      pendingFilesRef.current.set(key, file);
      results.push({ filename: file.name, hex, variant });
    }

    setColors(nextColors);
    setBulkResults(results);
    setBulkBusy(false);
    if (bulkInputRef.current) bulkInputRef.current.value = '';
  };

  return (
    <div>
      <input type="hidden" name={name} value={colors.join(',')} />

      <div className="mb-4 border border-line bg-surface p-3">
        <p className="tag-label mb-1">Bulk upload from a folder</p>
        <p className="mb-2 text-xs text-muted">
          Point at a folder of T-shirt photos and each one is scanned to detect the shirt&apos;s
          color and added automatically. Name a file with &quot;back&quot; in it (e.g.{' '}
          <code>navy-back.jpg</code>) to attach it as that color&apos;s back photo instead of front.
        </p>
        <input
          ref={bulkInputRef}
          type="file"
          accept="image/*"
          multiple
          // @ts-expect-error non-standard attributes for folder selection, supported in Chromium/Firefox
          webkitdirectory=""
          directory=""
          onChange={(e) => handleBulkFiles(e.target.files)}
          className="text-xs"
        />
        {bulkBusy && <p className="mt-2 text-xs text-muted">Scanning images…</p>}
        {bulkResults.length > 0 && !bulkBusy && (
          <div className="mt-3 space-y-1">
            {bulkResults.map((r, i) => (
              <div key={`${r.filename}-${i}`} className="flex items-center gap-2 text-xs">
                <div className="h-4 w-4 shrink-0 rounded-full border border-line" style={{ backgroundColor: r.hex }} />
                <span className="font-mono text-muted">{r.hex}</span>
                <span className="text-muted">({getColorName(r.hex)})</span>
                <span className="truncate text-muted">{r.filename}</span>
                <span className="tag-label shrink-0 text-muted">{r.variant}</span>
              </div>
            ))}
          </div>
        )}
      </div>

      <div className="flex flex-wrap gap-3">
        {colors.map((c) => (
          <div key={c} className="group relative">
            <div
              className="h-10 w-10 rounded-full border border-line"
              style={{ backgroundColor: c }}
              title={c}
            />
            <button
              type="button"
              onClick={() => removeColor(c)}
              aria-label={`Remove ${c}`}
              className="absolute -right-1 -top-1 flex h-5 w-5 items-center justify-center rounded-full border border-line bg-paper text-xs opacity-0 transition-opacity group-hover:opacity-100 hover:border-splash-pink hover:text-splash-pink"
            >
              ×
            </button>
          </div>
        ))}
      </div>

      <div className="mt-3 flex items-center gap-3">
        <input
          type="color"
          value={picked}
          onChange={(e) => setPicked(e.target.value)}
          className="h-9 w-9 cursor-pointer border border-line bg-paper p-0"
        />
        <div className="flex flex-col gap-0.5">
          <span className="text-xs font-medium">{getColorName(picked)}</span>
          <span className="font-mono text-xs text-muted">{picked}</span>
        </div>
        <button
          type="button"
          onClick={addColor}
          className="border border-ink px-3 py-1.5 text-xs hover:border-clay hover:bg-clay hover:text-paper"
        >
          + Add color
        </button>
      </div>

      {colors.length === 0 && <p className="mt-2 text-xs text-splash-pink">Add at least one color.</p>}

      {colors.length > 0 && (
        <div className="mt-5 border-t border-line pt-4">
          <p className="tag-label mb-1">Photo per color (optional)</p>
          <p className="mb-3 text-xs text-muted">
            Attach the real front and/or back photo for a color and it&apos;ll be shown exactly as-is
            when a customer picks that color — more accurate than tinting one photo. Leave either
            blank to fall back to the product&apos;s main front/back photo (or the illustration).
          </p>
          <div className="space-y-3">
            {colors.map((hex) => {
              const preview = previews[hex] ?? defaultColorPhotos[hex];
              const previewBack = previewsBack[hex] ?? defaultColorPhotosBack[hex];
              return (
                <div key={hex} className="border border-line p-2">
                  <div className="mb-2 flex items-center gap-2">
                    <div className="h-6 w-6 shrink-0 rounded-full border border-line" style={{ backgroundColor: hex }} />
                    <div className="flex flex-col gap-0.5">
                      <span className="font-medium text-xs">{getColorName(hex)}</span>
                      <span className="font-mono text-xs text-muted">{hex}</span>
                    </div>
                  </div>
                  <div className="grid gap-2 sm:grid-cols-2">
                    <div className="flex items-center gap-2">
                      <span className="tag-label w-10 shrink-0">Front</span>
                      {preview && (
                        <img src={preview} alt={`${hex} front preview`} className="h-9 w-9 shrink-0 border border-line object-cover" />
                      )}
                      <input
                        ref={(el) => {
                          if (el) fileInputRefs.current.set(`${hex}__front`, el);
                        }}
                        type="file"
                        accept="image/*"
                        name={frontFieldName(hex)}
                        onChange={(e) => handlePhotoChange(hex, e.target.files?.[0] ?? null, false)}
                        className="min-w-0 flex-1 text-xs"
                      />
                    </div>
                    <div className="flex items-center gap-2">
                      <span className="tag-label w-10 shrink-0">Back</span>
                      {previewBack && (
                        <img src={previewBack} alt={`${hex} back preview`} className="h-9 w-9 shrink-0 border border-line object-cover" />
                      )}
                      <input
                        ref={(el) => {
                          if (el) fileInputRefs.current.set(`${hex}__back`, el);
                        }}
                        type="file"
                        accept="image/*"
                        name={backFieldName(hex)}
                        onChange={(e) => handlePhotoChange(hex, e.target.files?.[0] ?? null, true)}
                        className="min-w-0 flex-1 text-xs"
                      />
                    </div>
                  </div>
                </div>
              );
            })}
          </div>
        </div>
      )}
    </div>
  );
}
