"use client";

import { ChangeEvent, useEffect, useMemo, useRef, useState } from "react";
import { MARD_221 } from "./mard-palette";

type BeadColor = {
  code: string;
  name: string;
  hex: string;
  rgb: [number, number, number];
};

type Pixel = {
  color: BeadColor;
};

type HoveredPixel = {
  row: number;
  column: number;
  color: BeadColor;
  left: number;
  top: number;
};

type SavedPattern = {
  id: string;
  name: string;
  savedAt: string;
  width: number;
  height: number;
  colorLimit: number;
  codes: string[];
};

const MARD_SERIES_NAMES: Record<string, string> = {
  A: "黄橙系", B: "绿色系", C: "蓝青系", D: "紫蓝系", E: "粉红系",
  F: "红色系", G: "肤棕系", H: "黑白灰系", M: "莫兰迪系",
};

const PALETTE: BeadColor[] = MARD_221.map(([code, hex]) => ({
  code,
  name: `MARD ${MARD_SERIES_NAMES[code[0]]}`,
  hex,
  rgb: [parseInt(hex.slice(1, 3), 16), parseInt(hex.slice(3, 5), 16), parseInt(hex.slice(5, 7), 16)],
}));

const hexToRgb = (hex: string): [number, number, number] => [
  parseInt(hex.slice(1, 3), 16),
  parseInt(hex.slice(3, 5), 16),
  parseInt(hex.slice(5, 7), 16),
];

function colorDistance(a: [number, number, number], b: [number, number, number]) {
  const meanR = (a[0] + b[0]) / 2;
  const r = a[0] - b[0];
  const g = a[1] - b[1];
  const blue = a[2] - b[2];
  return (2 + meanR / 256) * r * r + 4 * g * g + (2 + (255 - meanR) / 256) * blue * blue;
}

function nearestColor(rgb: [number, number, number], palette: BeadColor[]) {
  let closest = palette[0];
  let smallest = Number.POSITIVE_INFINITY;
  for (const color of palette) {
    const distance = colorDistance(rgb, color.rgb);
    if (distance < smallest) {
      smallest = distance;
      closest = color;
    }
  }
  return closest;
}

function makeDemoImage() {
  const canvas = document.createElement("canvas");
  canvas.width = 640;
  canvas.height = 640;
  const ctx = canvas.getContext("2d")!;
  const sky = ctx.createLinearGradient(0, 0, 0, 640);
  sky.addColorStop(0, "#bfe8f1");
  sky.addColorStop(0.65, "#fff2ce");
  sky.addColorStop(1, "#f6c783");
  ctx.fillStyle = sky;
  ctx.fillRect(0, 0, 640, 640);
  ctx.fillStyle = "#f6c742";
  ctx.beginPath(); ctx.arc(488, 134, 70, 0, Math.PI * 2); ctx.fill();
  ctx.fillStyle = "#58a95b";
  ctx.beginPath(); ctx.moveTo(0, 455); ctx.quadraticCurveTo(135, 310, 300, 455); ctx.quadraticCurveTo(480, 285, 640, 435); ctx.lineTo(640, 640); ctx.lineTo(0, 640); ctx.fill();
  ctx.fillStyle = "#27865a";
  ctx.beginPath(); ctx.moveTo(0, 535); ctx.quadraticCurveTo(180, 390, 350, 520); ctx.quadraticCurveTo(505, 410, 640, 505); ctx.lineTo(640, 640); ctx.lineTo(0, 640); ctx.fill();
  ctx.fillStyle = "#f7f5ed";
  ctx.fillRect(245, 390, 152, 126);
  ctx.fillStyle = "#d9383a";
  ctx.beginPath(); ctx.moveTo(218, 405); ctx.lineTo(321, 323); ctx.lineTo(424, 405); ctx.closePath(); ctx.fill();
  ctx.fillStyle = "#754a32";
  ctx.fillRect(300, 447, 43, 69);
  ctx.fillStyle = "#79cbe1";
  ctx.fillRect(258, 415, 39, 37); ctx.fillRect(350, 415, 34, 37);
  return canvas.toDataURL("image/png");
}

export default function Home() {
  const [sourceUrl, setSourceUrl] = useState("");
  const [sourceName, setSourceName] = useState("示例：田野小屋");
  const [gridWidth, setGridWidth] = useState(32);
  const [gridHeight, setGridHeight] = useState(32);
  const [colorLimit, setColorLimit] = useState(18);
  const [fitMode, setFitMode] = useState<"cover" | "contain">("cover");
  const [pixels, setPixels] = useState<Pixel[]>([]);
  const [selectedCodes, setSelectedCodes] = useState<Set<string>>(new Set());
  const [onlySelected, setOnlySelected] = useState(false);
  const [zoom, setZoom] = useState(24);
  const [showGrid, setShowGrid] = useState(true);
  const [showCodes, setShowCodes] = useState(true);
  const [query, setQuery] = useState("");
  const [hoveredPixel, setHoveredPixel] = useState<HoveredPixel | null>(null);
  const [history, setHistory] = useState<SavedPattern[]>([]);
  const [historyQuery, setHistoryQuery] = useState("");
  const [status, setStatus] = useState("示例图已准备好，可以直接转换");
  const sourceImageRef = useRef<HTMLImageElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);

  useEffect(() => {
    const saved = localStorage.getItem("bead-pattern-history");
    if (!saved) return;
    queueMicrotask(() => {
      try { setHistory(JSON.parse(saved)); } catch { localStorage.removeItem("bead-pattern-history"); }
    });
  }, []);

  const convertImage = (image = sourceImageRef.current) => {
    if (!image) return;
    const width = Math.max(8, Math.min(128, gridWidth));
    const height = Math.max(8, Math.min(128, gridHeight));
    const sample = document.createElement("canvas");
    sample.width = width;
    sample.height = height;
    const ctx = sample.getContext("2d", { willReadFrequently: true })!;
    ctx.fillStyle = "#f7f5ed";
    ctx.fillRect(0, 0, width, height);
    const imageRatio = image.naturalWidth / image.naturalHeight;
    const boxRatio = width / height;
    let drawWidth = width;
    let drawHeight = height;
    if ((fitMode === "cover" && imageRatio > boxRatio) || (fitMode === "contain" && imageRatio < boxRatio)) {
      drawHeight = height;
      drawWidth = height * imageRatio;
    } else {
      drawWidth = width;
      drawHeight = width / imageRatio;
    }
    ctx.drawImage(image, (width - drawWidth) / 2, (height - drawHeight) / 2, drawWidth, drawHeight);
    const data = ctx.getImageData(0, 0, width, height).data;
    const initialMatches: BeadColor[] = [];
    const counts = new Map<string, number>();
    for (let i = 0; i < data.length; i += 4) {
      const match = nearestColor([data[i], data[i + 1], data[i + 2]], PALETTE);
      initialMatches.push(match);
      counts.set(match.code, (counts.get(match.code) ?? 0) + 1);
    }
    const limitedPalette = [...PALETTE]
      .sort((a, b) => (counts.get(b.code) ?? 0) - (counts.get(a.code) ?? 0))
      .slice(0, Math.max(2, Math.min(colorLimit, PALETTE.length)));
    const converted = initialMatches.map((match, index) => {
      const rgb: [number, number, number] = [data[index * 4], data[index * 4 + 1], data[index * 4 + 2]];
      return { color: limitedPalette.some((c) => c.code === match.code) ? match : nearestColor(rgb, limitedPalette) };
    });
    setGridWidth(width);
    setGridHeight(height);
    setPixels(converted);
    setSelectedCodes(new Set());
    setStatus(`已转换为 ${width} × ${height}，共 ${width * height} 颗拼豆`);
  };

  useEffect(() => {
    const demo = makeDemoImage();
    const image = new Image();
    image.onload = () => {
      sourceImageRef.current = image;
      setSourceUrl(demo);
      convertImage(image);
    };
    image.src = demo;
    // This intentionally runs once to create the initial example.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  const usedColors = useMemo(() => {
    const counts = new Map<string, number>();
    pixels.forEach(({ color }) => counts.set(color.code, (counts.get(color.code) ?? 0) + 1));
    return PALETTE.filter((color) => counts.has(color.code))
      .map((color) => ({ ...color, count: counts.get(color.code)! }))
      .sort((a, b) => b.count - a.count);
  }, [pixels]);

  const filteredColors = useMemo(() => {
    const key = query.trim().toLowerCase();
    return usedColors.filter((color) => !key || color.code.toLowerCase().includes(key) || color.name.includes(key) || color.hex.toLowerCase().includes(key));
  }, [query, usedColors]);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas || pixels.length === 0) return;
    const cell = zoom;
    const ruler = Math.max(22, cell);
    const ratio = Math.min(window.devicePixelRatio || 1, 2);
    canvas.width = (gridWidth * cell + ruler) * ratio;
    canvas.height = (gridHeight * cell + ruler) * ratio;
    canvas.style.width = `${gridWidth * cell + ruler}px`;
    canvas.style.height = `${gridHeight * cell + ruler}px`;
    const ctx = canvas.getContext("2d")!;
    ctx.scale(ratio, ratio);
    ctx.fillStyle = "#ffffff";
    ctx.fillRect(0, 0, gridWidth * cell + ruler, gridHeight * cell + ruler);
    ctx.fillStyle = "#f0eee7";
    ctx.fillRect(ruler, 0, gridWidth * cell, ruler);
    ctx.fillRect(0, ruler, ruler, gridHeight * cell);
    ctx.strokeStyle = "rgba(32,38,36,.25)";
    ctx.lineWidth = 0.6;
    ctx.font = `600 ${Math.max(7, Math.min(10, cell * 0.35))}px Arial`;
    ctx.fillStyle = "#56605b";
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    for (let column = 0; column < gridWidth; column++) {
      const x = ruler + column * cell;
      ctx.strokeRect(x + 0.3, 0.3, cell - 0.6, ruler - 0.6);
      ctx.fillText(String(column + 1), x + cell / 2, ruler / 2);
    }
    for (let row = 0; row < gridHeight; row++) {
      const y = ruler + row * cell;
      ctx.strokeRect(0.3, y + 0.3, ruler - 0.6, cell - 0.6);
      ctx.fillText(String(row + 1), ruler / 2, y + cell / 2);
    }
    pixels.forEach(({ color }, index) => {
      const x = ruler + (index % gridWidth) * cell;
      const y = ruler + Math.floor(index / gridWidth) * cell;
      const hasSelection = selectedCodes.size > 0;
      const isSelected = selectedCodes.has(color.code);
      if (onlySelected && hasSelection && !isSelected) {
        ctx.fillStyle = "#ffffff";
      } else {
        ctx.fillStyle = color.hex;
        ctx.globalAlpha = hasSelection && !isSelected ? 0.12 : 1;
        ctx.fillRect(x, y, cell, cell);
        ctx.globalAlpha = 1;
      }
      if (showGrid) {
        ctx.strokeStyle = hasSelection && !isSelected ? "rgba(32,38,36,.07)" : "rgba(32,38,36,.22)";
        ctx.lineWidth = 0.6;
        ctx.strokeRect(x + 0.3, y + 0.3, cell - 0.6, cell - 0.6);
      }
      if (showCodes && cell >= 22 && (!hasSelection || isSelected)) {
        const [r, g, b] = hexToRgb(color.hex);
        ctx.fillStyle = r * 0.299 + g * 0.587 + b * 0.114 > 160 ? "#1f2925" : "#ffffff";
        ctx.font = `600 ${Math.max(7, cell * 0.28)}px Arial`;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.fillText(color.code, x + cell / 2, y + cell / 2);
      }
    });
  }, [pixels, gridWidth, gridHeight, zoom, selectedCodes, onlySelected, showGrid, showCodes]);

  const handleUpload = (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.target.files?.[0];
    if (!file) return;
    if (!file.type.startsWith("image/")) {
      setStatus("请选择 JPG、PNG 或 WebP 图片");
      return;
    }
    const url = URL.createObjectURL(file);
    const image = new Image();
    image.onload = () => {
      if (sourceUrl.startsWith("blob:")) URL.revokeObjectURL(sourceUrl);
      sourceImageRef.current = image;
      setSourceUrl(url);
      setSourceName(file.name);
      setStatus("图片已载入，点击“重新转换”生成图纸");
      convertImage(image);
    };
    image.src = url;
  };

  const toggleColor = (code: string) => {
    setSelectedCodes((current) => {
      const next = new Set(current);
      if (next.has(code)) next.delete(code); else next.add(code);
      return next;
    });
  };

  const handleCanvasClick = (event: React.MouseEvent<HTMLCanvasElement>) => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const rect = canvas.getBoundingClientRect();
    const ruler = Math.max(22, zoom);
    const x = Math.floor((event.clientX - rect.left - ruler) / zoom);
    const y = Math.floor((event.clientY - rect.top - ruler) / zoom);
    const pixel = pixels[y * gridWidth + x];
    if (x >= 0 && y >= 0 && x < gridWidth && y < gridHeight && pixel) toggleColor(pixel.color.code);
  };

  const handleCanvasMove = (event: React.MouseEvent<HTMLCanvasElement>) => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const rect = canvas.getBoundingClientRect();
    const ruler = Math.max(22, zoom);
    const column = Math.floor((event.clientX - rect.left - ruler) / zoom);
    const row = Math.floor((event.clientY - rect.top - ruler) / zoom);
    const pixel = pixels[row * gridWidth + column];
    if (column < 0 || row < 0 || column >= gridWidth || row >= gridHeight || !pixel) {
      setHoveredPixel(null);
      return;
    }
    setHoveredPixel({ row: row + 1, column: column + 1, color: pixel.color, left: event.clientX - rect.left + 14, top: event.clientY - rect.top + 14 });
  };

  const persistHistory = (next: SavedPattern[]) => {
    setHistory(next);
    localStorage.setItem("bead-pattern-history", JSON.stringify(next));
  };

  const savePattern = () => {
    if (!pixels.length) return;
    const entry: SavedPattern = {
      id: crypto.randomUUID(),
      name: sourceName.replace(/\.[^.]+$/, "") || "未命名图纸",
      savedAt: new Date().toISOString(),
      width: gridWidth,
      height: gridHeight,
      colorLimit,
      codes: pixels.map((pixel) => pixel.color.code),
    };
    persistHistory([entry, ...history].slice(0, 30));
    setStatus("图纸已保存到本机历史记录");
  };

  const restorePattern = (entry: SavedPattern) => {
    const colorMap = new Map(PALETTE.map((color) => [color.code, color]));
    setPixels(entry.codes.map((code) => ({ color: colorMap.get(code) ?? PALETTE[0] })));
    setGridWidth(entry.width);
    setGridHeight(entry.height);
    setColorLimit(entry.colorLimit);
    setSourceName(entry.name);
    setSelectedCodes(new Set());
    setStatus(`已恢复历史图纸：${entry.name}`);
    document.getElementById("pattern-preview")?.scrollIntoView({ behavior: "smooth" });
  };

  const removePattern = (id: string) => persistHistory(history.filter((entry) => entry.id !== id));

  const filteredHistory = useMemo(() => {
    const key = historyQuery.trim().toLowerCase();
    return history.filter((entry) => !key || entry.name.toLowerCase().includes(key) || `${entry.width}x${entry.height}`.includes(key));
  }, [history, historyQuery]);

  const exportPng = () => {
    const canvas = canvasRef.current;
    if (!canvas) return;
    const link = document.createElement("a");
    link.download = `拼豆图纸-${gridWidth}x${gridHeight}.png`;
    link.href = canvas.toDataURL("image/png");
    link.click();
  };

  return (
    <main>
      <header className="topbar">
        <a className="brand" href="#top" aria-label="豆格工坊首页">
          <span className="brand-mark" aria-hidden="true"><i /><i /><i /><i /></span>
          <span>豆格工坊</span>
        </a>
        <span className="top-note">图片转拼豆图纸 · 本地处理</span>
      </header>

      <section className="hero" id="top">
        <div>
          <p className="eyebrow">PIXEL BEAD STUDIO</p>
          <h1>把喜欢的图片，<br /><em>变成一格一格的快乐。</em></h1>
          <p className="hero-copy">上传图片，选择图纸大小和颜色数量；点击任意像素格，就能单独查看该颜色需要摆放的区域。</p>
        </div>
        <div className="hero-badge"><strong>{gridWidth} × {gridHeight}</strong><span>当前图纸格数</span></div>
      </section>

      <section className="workspace" aria-label="拼豆图纸转换工具">
        <aside className="control-panel">
          <div className="panel-heading"><span>01</span><div><h2>图片与规格</h2><p>先决定图纸需要多少格</p></div></div>
          <label className="upload-card">
            {/* The source may be a local blob URL, so framework image optimization is not applicable. */}
            {/* eslint-disable-next-line @next/next/no-img-element */}
            {sourceUrl ? <img src={sourceUrl} alt="待转换的原图预览" /> : <span className="upload-placeholder">＋</span>}
            <span className="upload-overlay">更换图片</span>
            <input type="file" accept="image/png,image/jpeg,image/webp" onChange={handleUpload} />
          </label>
          <p className="file-name" title={sourceName}>{sourceName}</p>

          <div className="field-row">
            <label><span>横向格数</span><input type="number" min="8" max="128" value={gridWidth} onChange={(e) => setGridWidth(Number(e.target.value))} /></label>
            <span className="times">×</span>
            <label><span>纵向格数</span><input type="number" min="8" max="128" value={gridHeight} onChange={(e) => setGridHeight(Number(e.target.value))} /></label>
          </div>

          <label className="field"><span>最多使用颜色 <b>{colorLimit}</b></span><input type="range" min="2" max="64" value={colorLimit} onChange={(e) => setColorLimit(Number(e.target.value))} /></label>
          <label className="field"><span>图片适应方式</span><select value={fitMode} onChange={(e) => setFitMode(e.target.value as "cover" | "contain")}><option value="cover">裁剪铺满</option><option value="contain">完整显示</option></select></label>
          <button className="primary-button" onClick={() => convertImage()}>重新转换</button>
          <p className="privacy-note">图片只在当前浏览器中处理，不会上传到服务器。</p>
        </aside>

        <section className="pattern-panel" id="pattern-preview">
          <div className="pattern-toolbar">
            <div><p className="section-kicker">02 / 图纸预览</p><h2>点击格子，筛选颜色</h2></div>
            <div className="zoom-control" aria-label="图纸缩放">
              <button onClick={() => setZoom((z) => Math.max(8, z - 2))} aria-label="缩小">−</button>
              <input aria-label="缩放比例" type="range" min="8" max="42" value={zoom} onChange={(e) => setZoom(Number(e.target.value))} />
              <button onClick={() => setZoom((z) => Math.min(42, z + 2))} aria-label="放大">＋</button>
              <output>{zoom}px</output>
            </div>
          </div>

          <div className="canvas-stage">
            <div className="canvas-wrap">
              <canvas ref={canvasRef} onClick={handleCanvasClick} onMouseMove={handleCanvasMove} onMouseLeave={() => setHoveredPixel(null)} aria-label="转换后的拼豆像素图，点击格子可选择颜色" />
              {hoveredPixel && <div className="pixel-tooltip" style={{ left: hoveredPixel.left, top: hoveredPixel.top }}>
                <span className="tooltip-swatch" style={{ backgroundColor: hoveredPixel.color.hex }} />
                <strong>第 {hoveredPixel.row} 行 · 第 {hoveredPixel.column} 列</strong>
                <small>{hoveredPixel.color.code} · {hoveredPixel.color.name} · {hoveredPixel.color.hex}</small>
              </div>}
            </div>
          </div>

          <div className="pattern-footer">
            <span className="status-dot" /> <span>{status}</span>
            <div className="view-options">
              <label><input type="checkbox" checked={showGrid} onChange={(e) => setShowGrid(e.target.checked)} /> 网格</label>
              <label><input type="checkbox" checked={showCodes} onChange={(e) => setShowCodes(e.target.checked)} /> 格内编号</label>
              <button className="save-button" onClick={savePattern}>保存历史</button>
              <button onClick={exportPng}>下载 PNG</button>
            </div>
          </div>
          <div className="pattern-legend">
            <div className="legend-heading"><strong>本图所需颜色与用量</strong><span>按数量从多到少，共 {usedColors.length} 色</span></div>
            <div className="legend-list">
              {usedColors.map((color) => <button key={color.code} className={selectedCodes.has(color.code) ? "active" : ""} onClick={() => toggleColor(color.code)}>
                <i style={{ backgroundColor: color.hex }} /><strong>{color.code}</strong><span>{color.name}</span><b>{color.count} 颗</b>
              </button>)}
            </div>
          </div>
        </section>

        <aside className="color-panel">
          <div className="panel-heading"><span>03</span><div><h2>分色查看</h2><p>{usedColors.length} 种颜色 · {pixels.length} 颗</p></div></div>
          <div className="search-box"><span>⌕</span><input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="输入 A6、MARD A 或 #FEAC4C" /></div>
          <div className="selection-tools">
            <label className="switch-row"><input type="checkbox" checked={onlySelected} onChange={(e) => setOnlySelected(e.target.checked)} /><span className="switch" /><span>只显示选中颜色</span></label>
            {selectedCodes.size > 0 && <button onClick={() => setSelectedCodes(new Set())}>清除 {selectedCodes.size} 项</button>}
          </div>
          <div className="color-list">
            {filteredColors.map((color) => {
              const active = selectedCodes.has(color.code);
              return <button key={color.code} className={`color-item ${active ? "active" : ""}`} onClick={() => toggleColor(color.code)} aria-pressed={active}>
                <span className="swatch" style={{ backgroundColor: color.hex }} />
                <span className="color-meta"><strong>{color.code} · {color.name}</strong><small>{color.hex}</small></span>
                <b>{color.count}<small>颗</small></b>
              </button>;
            })}
          </div>
          <p className="palette-note"><strong>MARD 221 基础色卡</strong>：默认使用 A/B/C/D/E/F/G/H/M 九个系列。屏幕色值仅用于近似匹配，实体豆颜色会受批次、光线和屏幕影响。</p>
        </aside>
      </section>

      <section className="history-section" aria-labelledby="history-title">
        <div className="history-heading"><div><p className="section-kicker">LOCAL PATTERN ARCHIVE</p><h2 id="history-title">历史图纸</h2><p>历史保存在当前浏览器，最多保留 30 份。</p></div>
          <div className="search-box"><span>⌕</span><input value={historyQuery} onChange={(e) => setHistoryQuery(e.target.value)} placeholder="按名称或 32x32 查询" /></div>
        </div>
        {filteredHistory.length ? <div className="history-grid">{filteredHistory.map((entry) => {
          const colorCounts = new Map<string, number>();
          entry.codes.forEach((code) => colorCounts.set(code, (colorCounts.get(code) ?? 0) + 1));
          const topCodes = [...colorCounts.entries()].sort((a, b) => b[1] - a[1]).slice(0, 6);
          return <article className="history-card" key={entry.id}>
            <div className="history-swatches">{topCodes.map(([code, count]) => <i key={code} style={{ backgroundColor: PALETTE.find((color) => color.code === code)?.hex, flexGrow: count }} />)}</div>
            <div><h3>{entry.name}</h3><p>{entry.width} × {entry.height} · {new Date(entry.savedAt).toLocaleString("zh-CN", { hour12: false })}</p></div>
            <div className="history-actions"><button onClick={() => restorePattern(entry)}>打开</button><button onClick={() => removePattern(entry.id)}>删除</button></div>
          </article>;
        })}</div> : <div className="history-empty">还没有符合条件的历史图纸。转换完成后点击“保存历史”即可在这里查询。</div>}
      </section>
    </main>
  );
}
