/* global React, ReactDOM */
/* ============================================
   もざいくにゃん — Editor
   - Upload image (JPG/PNG/HEIC)
   - Toggle mosaic / mask mode
   - Drag to add rectangles (accumulate)
   - Undo
   - Confirm → output → download (JPG/PNG)
   - Persist settings to localStorage
   ============================================ */

const { useState, useEffect, useRef, useCallback } = React;

// ----- Settings persistence -----
const SETTINGS_KEY = 'mozaikunyan:settings';
const DEFAULTS = {
  mosaicSize: 18,
  maskColor: '#2B2030',
  borderEnabled: false,
  borderColor: '#FF6B9D',
  borderWidth: 4,
};
function loadSettings() {
  try {
    const raw = localStorage.getItem(SETTINGS_KEY);
    if (!raw) return { ...DEFAULTS };
    return { ...DEFAULTS, ...JSON.parse(raw) };
  } catch (e) { return { ...DEFAULTS }; }
}
function saveSettings(s) {
  try { localStorage.setItem(SETTINGS_KEY, JSON.stringify(s)); } catch (e) {}
}

// ----- Cat icon (compact) -----
const MiniCat = () => (
  <span className="mini-cat">
    <img src="images/mascot-header.png" alt="" width="28" height="28" decoding="async" />
  </span>
);

// ----- Mosaic preset presets -----
const MOSAIC_PRESETS = [
  { label: 'やさしめ', value: 10 },
  { label: 'ふつう', value: 20 },
  { label: 'しっかり', value: 35 },
  { label: 'がっつり', value: 60 },
];

// ----- Mask preset colors -----
const MASK_PRESETS = [
  '#2B2030', // ink (dark)
  '#000000', // black
  '#FFFFFF', // white
  '#FF6B9D', // pink
  '#4D7FFF', // blue
  '#3FB97A', // green
  '#FFB23F', // yellow
  '#A395A8', // gray
];

// ----- Border preset colors -----
const BORDER_PRESETS = [
  '#FF6B9D', '#2B2030', '#FFFFFF', '#FFB23F', '#4D7FFF', '#3FB97A',
];

// ============================================
// Image loading (with HEIC support)
// ============================================
async function loadImageFromFile(file) {
  let blob = file;
  const name = (file.name || '').toLowerCase();
  const isHeic = /\.(heic|heif)$/i.test(name) || /heic|heif/i.test(file.type);
  if (isHeic) {
    if (typeof window.heic2any !== 'function') {
      throw new Error('HEIC変換ライブラリの読み込みに失敗しました。再読み込みしてください。');
    }
    try {
      blob = await window.heic2any({ blob: file, toType: 'image/jpeg', quality: 0.92 });
    } catch (e) {
      throw new Error('HEIC画像の変換に失敗しました。JPG/PNGに変換してからお試しください。');
    }
  }
  return await new Promise((resolve, reject) => {
    const url = URL.createObjectURL(blob);
    const img = new Image();
    img.onload = () => { URL.revokeObjectURL(url); resolve(img); };
    img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('画像の読み込みに失敗しました。')); };
    img.src = url;
  });
}

// ============================================
// Render full canvas: image + operations
// ============================================
function renderToCanvas(canvas, image, ops, previewRect) {
  const ctx = canvas.getContext('2d');
  canvas.width = image.naturalWidth;
  canvas.height = image.naturalHeight;
  ctx.drawImage(image, 0, 0);

  // Snapshot original pixels for mosaic sampling
  let original = null;
  const needsMosaic = ops.some(o => o.type === 'mosaic') || (previewRect && previewRect.mode === 'mosaic');
  if (needsMosaic) {
    original = ctx.getImageData(0, 0, canvas.width, canvas.height);
  }

  for (const op of ops) {
    drawOperation(ctx, op, original, canvas.width, canvas.height);
  }
  if (previewRect && previewRect.w > 1 && previewRect.h > 1) {
    drawOperation(ctx, previewRect, original, canvas.width, canvas.height);
  }
}

function drawOperation(ctx, op, original, canvasW, canvasH) {
  const x = Math.max(0, Math.round(op.x));
  const y = Math.max(0, Math.round(op.y));
  const w = Math.min(canvasW - x, Math.round(op.w));
  const h = Math.min(canvasH - y, Math.round(op.h));
  if (w <= 0 || h <= 0) return;

  if (op.type === 'mosaic') {
    applyMosaic(ctx, original, x, y, w, h, Math.max(2, Math.round(op.size || 18)));
  } else if (op.type === 'mask') {
    ctx.fillStyle = op.color || '#2B2030';
    ctx.fillRect(x, y, w, h);
    if (op.borderEnabled) {
      const bw = Math.max(1, op.borderWidth || 4);
      ctx.lineWidth = bw;
      ctx.strokeStyle = op.borderColor || '#FF6B9D';
      // Stroke inside the rect so it doesn't bleed past selection
      ctx.strokeRect(x + bw / 2, y + bw / 2, w - bw, h - bw);
    }
  }
}

function applyMosaic(ctx, original, x, y, w, h, size) {
  if (!original) {
    original = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
  }
  const data = original.data;
  const W = original.width;
  for (let by = y; by < y + h; by += size) {
    for (let bx = x; bx < x + w; bx += size) {
      const bw = Math.min(size, x + w - bx);
      const bh = Math.min(size, y + h - by);
      // Alpha-weighted (premultiplied) average so transparent PNGs stay transparent
      // instead of bleeding into a black block. See README / mosaic transparency fix.
      let rSum = 0, gSum = 0, bSum = 0, aSum = 0, n = 0;
      const stepX = Math.max(1, Math.floor(bw / 3));
      const stepY = Math.max(1, Math.floor(bh / 3));
      for (let sy = by; sy < by + bh; sy += stepY) {
        for (let sx = bx; sx < bx + bw; sx += stepX) {
          const i = (sy * W + sx) * 4;
          const a = data[i + 3];
          rSum += data[i] * a;
          gSum += data[i + 1] * a;
          bSum += data[i + 2] * a;
          aSum += a;
          n++;
        }
      }
      const avgA = aSum / n;
      let r, g, b;
      if (aSum > 0) {
        r = Math.round(rSum / aSum);
        g = Math.round(gSum / aSum);
        b = Math.round(bSum / aSum);
      } else {
        r = g = b = 0;
      }
      // Clear first so the rgba fill replaces the underlying pixels (otherwise a
      // half-transparent block would composite on top of the original opaque image).
      ctx.clearRect(bx, by, bw, bh);
      ctx.fillStyle = `rgba(${r},${g},${b},${(avgA / 255).toFixed(3)})`;
      ctx.fillRect(bx, by, bw, bh);
    }
  }
}

// ============================================
// Editor component
// ============================================
function Editor() {
  const [image, setImage] = useState(null);          // HTMLImageElement
  const [ops, setOps] = useState([]);                // committed operations
  const [redoStack, setRedoStack] = useState([]);    // ops undone, available for redo (LIFO)
  const [mode, setMode] = useState('mosaic');        // 'mosaic' | 'mask'
  const [settings, setSettings] = useState(loadSettings);
  const [drag, setDrag] = useState(null);            // {x0,y0,x1,y1}
  const [result, setResult] = useState(null);        // data URL after confirm
  const [downloadFmt, setDownloadFmt] = useState('png'); // 'png' | 'jpg'
  const [loading, setLoading] = useState(false);
  const [errorMsg, setErrorMsg] = useState('');

  const canvasRef = useRef(null);
  const containerRef = useRef(null);

  // ---- Apply theme persistence (also via Tweaks) ----
  // handled outside this component (Tweaks panel)

  // ---- Re-render canvas whenever something changes ----
  useEffect(() => {
    if (!image || !canvasRef.current) return;
    const preview = drag ? dragToOperation(drag, mode, settings) : null;
    renderToCanvas(canvasRef.current, image, ops, preview);
  }, [image, ops, drag, mode, settings]);

  // ---- File handlers ----
  async function handleFile(file) {
    if (!file) return;
    setErrorMsg('');
    setLoading(true);
    try {
      const img = await loadImageFromFile(file);
      setImage(img);
      setOps([]);
      setRedoStack([]);
      setResult(null);
    } catch (e) {
      setErrorMsg(e.message || '画像の読み込みに失敗しました。');
    } finally {
      setLoading(false);
    }
  }

  function onUploadChange(e) {
    const f = e.target.files && e.target.files[0];
    handleFile(f);
    e.target.value = '';
  }

  // ---- Drag → image coordinate ----
  function pointToImage(e) {
    const c = canvasRef.current;
    if (!c) return { x: 0, y: 0 };
    const rect = c.getBoundingClientRect();
    const x = ((e.clientX - rect.left) / rect.width) * c.width;
    const y = ((e.clientY - rect.top) / rect.height) * c.height;
    return { x, y };
  }

  function onPointerDown(e) {
    if (!image || result) return;
    e.preventDefault();
    e.target.setPointerCapture && e.target.setPointerCapture(e.pointerId);
    const p = pointToImage(e);
    setDrag({ x0: p.x, y0: p.y, x1: p.x, y1: p.y });
  }
  function onPointerMove(e) {
    if (!drag) return;
    const p = pointToImage(e);
    setDrag({ ...drag, x1: p.x, y1: p.y });
  }
  function onPointerUp(e) {
    if (!drag) return;
    const op = dragToOperation(drag, mode, settings);
    if (op && op.w > 4 && op.h > 4) {
      setOps([...ops, op]);
      setRedoStack([]);
    }
    setDrag(null);
  }

  function undo() {
    if (ops.length === 0) return;
    const popped = ops[ops.length - 1];
    setOps(ops.slice(0, -1));
    setRedoStack([...redoStack, popped]);
  }
  function redo() {
    if (redoStack.length === 0) return;
    const popped = redoStack[redoStack.length - 1];
    setRedoStack(redoStack.slice(0, -1));
    setOps([...ops, popped]);
  }
  function clearAll() {
    setOps([]);
    setRedoStack([]);
  }

  // ---- Keyboard shortcuts: Ctrl+Z = undo, Ctrl+Y / Ctrl+Shift+Z = redo (⌘ on Mac) ----
  useEffect(() => {
    function onKeyDown(e) {
      if (!image || result) return;
      const t = e.target;
      if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return;
      const ctrl = e.ctrlKey || e.metaKey;
      if (!ctrl) return;
      const k = e.key.toLowerCase();
      if (k === 'y' || (e.shiftKey && k === 'z')) {
        if (redoStack.length === 0) return;
        e.preventDefault();
        redo();
        return;
      }
      if (!e.shiftKey && k === 'z') {
        if (ops.length === 0) return;
        e.preventDefault();
        undo();
      }
    }
    window.addEventListener('keydown', onKeyDown);
    return () => window.removeEventListener('keydown', onKeyDown);
  }, [image, result, ops, redoStack]);

  function confirmEdits() {
    if (!canvasRef.current) return;
    // Persist settings
    saveSettings(settings);
    // Bake current rendered canvas into a result image
    const url = canvasRef.current.toDataURL('image/png');
    setResult(url);
  }

  function startOver() {
    setResult(null);
  }
  function newImage() {
    setImage(null);
    setOps([]);
    setRedoStack([]);
    setResult(null);
  }

  function download() {
    if (!canvasRef.current) return;
    const mime = downloadFmt === 'jpg' ? 'image/jpeg' : 'image/png';
    const ext = downloadFmt === 'jpg' ? 'jpg' : 'png';
    // For JPG we need a non-transparent background; redraw via temp canvas
    let url;
    if (downloadFmt === 'jpg') {
      const tmp = document.createElement('canvas');
      tmp.width = canvasRef.current.width;
      tmp.height = canvasRef.current.height;
      const ctx = tmp.getContext('2d');
      ctx.fillStyle = '#fff';
      ctx.fillRect(0, 0, tmp.width, tmp.height);
      ctx.drawImage(canvasRef.current, 0, 0);
      url = tmp.toDataURL('image/jpeg', 0.92);
    } else {
      url = canvasRef.current.toDataURL('image/png');
    }
    const a = document.createElement('a');
    a.href = url;
    const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
    a.download = `mozaikunyan-${ts}.${ext}`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
  }

  // ---- Render ----
  if (!image) {
    return <UploadScreen onPick={handleFile} loading={loading} errorMsg={errorMsg} />;
  }

  return (
    <div className="editor">
      <div className="editor__stage" ref={containerRef}>
        <div className="editor__canvas-wrap">
          <canvas
            ref={canvasRef}
            className={`editor__canvas ${result ? 'is-locked' : ''}`}
            onPointerDown={onPointerDown}
            onPointerMove={onPointerMove}
            onPointerUp={onPointerUp}
            onPointerCancel={onPointerUp}
          />
          {!result && ops.length === 0 && !drag && (
            <div className="editor__hint">
              <strong>ドラッグして範囲を囲ってね</strong>
              <span>{mode === 'mosaic' ? 'モザイクをかけたい部分を選択' : 'マスクで隠したい部分を選択'}</span>
            </div>
          )}
        </div>
      </div>

      <aside className="editor__panel">
        {result ? (
          <ResultPanel
            downloadFmt={downloadFmt}
            setDownloadFmt={setDownloadFmt}
            onDownload={download}
            onBack={startOver}
            onNew={newImage}
          />
        ) : (
          <ToolPanel
            mode={mode} setMode={setMode}
            settings={settings} setSettings={setSettings}
            opsCount={ops.length}
            redoCount={redoStack.length}
            onUndo={undo}
            onRedo={redo}
            onClear={clearAll}
            onConfirm={confirmEdits}
            onNew={newImage}
          />
        )}
      </aside>
    </div>
  );
}

function dragToOperation(d, mode, settings) {
  if (!d) return null;
  const x = Math.min(d.x0, d.x1);
  const y = Math.min(d.y0, d.y1);
  const w = Math.abs(d.x1 - d.x0);
  const h = Math.abs(d.y1 - d.y0);
  if (mode === 'mosaic') {
    return { type: 'mosaic', mode: 'mosaic', x, y, w, h, size: settings.mosaicSize };
  } else {
    return {
      type: 'mask', mode: 'mask', x, y, w, h,
      color: settings.maskColor,
      borderEnabled: settings.borderEnabled,
      borderColor: settings.borderColor,
      borderWidth: settings.borderWidth,
    };
  }
}

// ============================================
// Upload screen
// ============================================
function UploadScreen({ onPick, loading, errorMsg }) {
  const [over, setOver] = useState(false);
  const inputRef = useRef(null);

  function onDrop(e) {
    e.preventDefault();
    setOver(false);
    const f = e.dataTransfer.files && e.dataTransfer.files[0];
    if (f) onPick(f);
  }

  return (
    <div className="upload">
      <div className="upload__hero">
        <div className="upload__mascot">
          <img src="images/mascot-about.png" alt="" width="120" height="120" decoding="async" />
        </div>
        <h1>かんたん画像モザイク＆マスク</h1>
        <p className="upload__lede">
          人物にはモザイク、スクショの機密情報にはマスク。<br />
          画像はあなたの端末から外に出ません。
        </p>
      </div>

      <label
        className={`upload__drop ${over ? 'is-over' : ''}`}
        onDragOver={(e) => { e.preventDefault(); setOver(true); }}
        onDragLeave={() => setOver(false)}
        onDrop={onDrop}
      >
        <input
          ref={inputRef}
          type="file"
          accept="image/jpeg,image/png,image/heic,image/heif,.jpg,.jpeg,.png,.heic,.heif"
          onChange={(e) => onPick(e.target.files && e.target.files[0])}
          style={{ display: 'none' }}
        />
        <div className="upload__drop-icon">
          <svg viewBox="0 0 64 64" width="64" height="64" aria-hidden="true">
            <rect x="6" y="14" width="52" height="40" rx="6" fill="#fff" stroke="#2B2030" strokeWidth="2.5"/>
            <path d="M14 46 L26 32 L36 42 L44 34 L54 46" stroke="#FF6B9D" strokeWidth="3" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
            <circle cx="42" cy="24" r="4" fill="#FFB23F"/>
            <path d="M32 6 L32 18 M26 12 L32 6 L38 12" stroke="#2B2030" strokeWidth="2.5" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </div>
        <strong className="upload__drop-title">画像をここにドロップ</strong>
        <span className="upload__drop-sub">または、タップ／クリックでファイルを選択</span>
        <span className="upload__drop-formats">対応形式: JPG / PNG / HEIC</span>
        {loading && <span className="upload__loading">変換中…</span>}
        {errorMsg && <span className="upload__error">{errorMsg}</span>}
      </label>

      <div className="upload__features">
        <FeatureCard
          icon={
            <svg viewBox="0 0 40 40" width="36" height="36"><g fill="#FF6B9D"><rect x="4" y="4" width="8" height="8" rx="1"/><rect x="14" y="4" width="8" height="8" rx="1" fill="#FFB7C9"/><rect x="24" y="4" width="8" height="8" rx="1"/><rect x="4" y="14" width="8" height="8" rx="1" fill="#FFB7C9"/><rect x="14" y="14" width="8" height="8" rx="1"/><rect x="24" y="14" width="8" height="8" rx="1" fill="#FFB7C9"/><rect x="4" y="24" width="8" height="8" rx="1" fill="#FFB7C9"/><rect x="14" y="24" width="8" height="8" rx="1"/><rect x="24" y="24" width="8" height="8" rx="1" fill="#FFB7C9"/></g></svg>
          }
          title="モザイク"
          body="人物の顔や背景の通行人など、写り込みを自然にぼかせます。粗さも自由自在。"
        />
        <FeatureCard
          icon={
            <svg viewBox="0 0 40 40" width="36" height="36"><rect x="4" y="6" width="32" height="22" rx="2" fill="#fff" stroke="#2B2030" strokeWidth="2"/><rect x="10" y="14" width="12" height="3" rx="1.5" fill="#2B2030"/><rect x="10" y="20" width="20" height="3" rx="1.5" fill="#2B2030"/></svg>
          }
          title="マスク"
          body="スクショの個人情報や金額、宛名を一発で塗りつぶし。外枠の色も自由に。"
        />
        <FeatureCard
          icon={
            <svg viewBox="0 0 40 40" width="36" height="36"><path d="M20 4 L4 12 V22 C4 30 12 36 20 38 C28 36 36 30 36 22 V12 Z" fill="#3FB97A"/><path d="M14 21 L18 25 L27 16" stroke="#fff" strokeWidth="3" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
          }
          title="完全クライアント処理"
          body="画像はサーバーに送信されません。すべてあなたのブラウザ内で処理されます。"
        />
      </div>
    </div>
  );
}

function FeatureCard({ icon, title, body }) {
  return (
    <div className="feature">
      <div className="feature__icon">{icon}</div>
      <h3>{title}</h3>
      <p>{body}</p>
    </div>
  );
}

// ============================================
// Tool panel
// ============================================
function ToolPanel({ mode, setMode, settings, setSettings, opsCount, redoCount, onUndo, onRedo, onClear, onConfirm, onNew }) {
  function update(patch) { setSettings({ ...settings, ...patch }); }

  return (
    <div className="panel">
      <div className="panel__mode">
        <button
          className={`mode-tab ${mode === 'mosaic' ? 'is-active' : ''}`}
          onClick={() => setMode('mosaic')}
        >
          <span className="mode-tab__icon" aria-hidden="true">
            <svg viewBox="0 0 24 24" width="20" height="20"><g fill="currentColor"><rect x="2" y="2" width="6" height="6"/><rect x="10" y="2" width="6" height="6" opacity="0.4"/><rect x="2" y="10" width="6" height="6" opacity="0.4"/><rect x="10" y="10" width="6" height="6"/><rect x="16" y="10" width="6" height="6" opacity="0.4"/><rect x="2" y="16" width="6" height="6"/><rect x="10" y="16" width="6" height="6" opacity="0.4"/></g></svg>
          </span>
          モザイク
        </button>
        <button
          className={`mode-tab ${mode === 'mask' ? 'is-active' : ''}`}
          onClick={() => setMode('mask')}
        >
          <span className="mode-tab__icon" aria-hidden="true">
            <svg viewBox="0 0 24 24" width="20" height="20"><rect x="3" y="6" width="18" height="12" rx="1.5" fill="currentColor"/></svg>
          </span>
          マスク
        </button>
      </div>

      {mode === 'mosaic' ? (
        <div className="panel__section">
          <h4 className="panel__title">粗さ</h4>
          <div className="slider-row">
            <input
              type="range"
              min="4"
              max="80"
              step="1"
              value={settings.mosaicSize}
              onChange={(e) => update({ mosaicSize: Number(e.target.value) })}
              aria-label="モザイクの粗さ"
            />
            <span className="slider-value">{settings.mosaicSize}px</span>
          </div>
          <div className="presets">
            {MOSAIC_PRESETS.map(p => (
              <button
                key={p.value}
                className={`chip ${settings.mosaicSize === p.value ? 'is-active' : ''}`}
                onClick={() => update({ mosaicSize: p.value })}
              >{p.label}</button>
            ))}
          </div>
          <p className="panel__hint">大きいほどブロックが粗くなり、元が判別しにくくなります。</p>
        </div>
      ) : (
        <div className="panel__section">
          <h4 className="panel__title">マスクの色</h4>
          <div className="swatches">
            {MASK_PRESETS.map(c => (
              <button
                key={c}
                className={`swatch ${settings.maskColor.toLowerCase() === c.toLowerCase() ? 'is-active' : ''}`}
                style={{ background: c }}
                onClick={() => update({ maskColor: c })}
                aria-label={`マスクの色 ${c}`}
              />
            ))}
            <label className="swatch swatch--picker" title="カラーピッカー">
              <input
                type="color"
                value={settings.maskColor}
                onChange={(e) => update({ maskColor: e.target.value })}
              />
              <svg viewBox="0 0 24 24" width="18" height="18"><path d="M12 3 L19 10 L11 18 H4 V11 Z" stroke="#fff" strokeWidth="2" fill="none"/><circle cx="18" cy="6" r="2.5" fill="#fff"/></svg>
            </label>
          </div>

          <h4 className="panel__title panel__title--mt">外枠</h4>
          <label className="toggle">
            <input
              type="checkbox"
              checked={settings.borderEnabled}
              onChange={(e) => update({ borderEnabled: e.target.checked })}
            />
            <span className="toggle__track"><span className="toggle__thumb"></span></span>
            <span className="toggle__label">外枠を付ける</span>
          </label>

          {settings.borderEnabled && (
            <div className="border-config">
              <div className="swatches">
                {BORDER_PRESETS.map(c => (
                  <button
                    key={c}
                    className={`swatch ${settings.borderColor.toLowerCase() === c.toLowerCase() ? 'is-active' : ''}`}
                    style={{ background: c }}
                    onClick={() => update({ borderColor: c })}
                    aria-label={`外枠の色 ${c}`}
                  />
                ))}
                <label className="swatch swatch--picker">
                  <input
                    type="color"
                    value={settings.borderColor}
                    onChange={(e) => update({ borderColor: e.target.value })}
                  />
                  <svg viewBox="0 0 24 24" width="18" height="18"><path d="M12 3 L19 10 L11 18 H4 V11 Z" stroke="#fff" strokeWidth="2" fill="none"/><circle cx="18" cy="6" r="2.5" fill="#fff"/></svg>
                </label>
              </div>
              <div className="slider-row">
                <span className="slider-label">太さ</span>
                <input
                  type="range" min="1" max="20" step="1"
                  value={settings.borderWidth}
                  onChange={(e) => update({ borderWidth: Number(e.target.value) })}
                />
                <span className="slider-value">{settings.borderWidth}px</span>
              </div>
            </div>
          )}
        </div>
      )}

      <div className="panel__divider"></div>

      <div className="panel__actions">
        <div className="panel__actions-history">
          <button className="btn btn--ghost btn--block" onClick={onUndo} disabled={opsCount === 0}>
            <svg viewBox="0 0 24 24" width="16" height="16"><path d="M9 14 L4 9 L9 4 M4 9 H14 a5 5 0 0 1 0 10 H10" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
            1つ戻る
          </button>
          <button className="btn btn--ghost btn--block" onClick={onRedo} disabled={redoCount === 0}>
            <svg viewBox="0 0 24 24" width="16" height="16"><path d="M15 14 L20 9 L15 4 M20 9 H10 a5 5 0 0 0 0 10 H14" stroke="currentColor" strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/></svg>
            1つ進む
          </button>
        </div>
        <button className="btn btn--ghost btn--sm panel__clear" onClick={onClear} disabled={opsCount === 0}>
          全部消す
        </button>
      </div>

      <div className="panel__status">
        編集中の領域: <strong>{opsCount}</strong> 個
      </div>

      <button className="btn btn--lg btn--block panel__confirm" onClick={onConfirm} disabled={opsCount === 0}>
        ✓ 確定する
      </button>

      <button className="btn btn--ghost btn--block btn--sm panel__new" onClick={onNew}>
        別の画像を選ぶ
      </button>
    </div>
  );
}

// ============================================
// Result panel
// ============================================
function ResultPanel({ downloadFmt, setDownloadFmt, onDownload, onBack, onNew }) {
  return (
    <div className="panel panel--result">
      <div className="panel__done">
        <div className="panel__done-mascot"><img src="images/mascot-header.png" alt="" width="72" height="72" decoding="async" /></div>
        <strong>できあがり！</strong>
        <span>あとは保存形式を選んでダウンロード。</span>
      </div>

      <div className="panel__section">
        <h4 className="panel__title">保存形式</h4>
        <div className="format-tabs">
          <button
            className={`format-tab ${downloadFmt === 'png' ? 'is-active' : ''}`}
            onClick={() => setDownloadFmt('png')}
          >
            <strong>PNG</strong>
            <span>高画質・無圧縮</span>
          </button>
          <button
            className={`format-tab ${downloadFmt === 'jpg' ? 'is-active' : ''}`}
            onClick={() => setDownloadFmt('jpg')}
          >
            <strong>JPG</strong>
            <span>軽量・SNS向き</span>
          </button>
        </div>
      </div>

      <button className="btn btn--lg btn--block panel__confirm" onClick={onDownload}>
        ⬇ ダウンロード
      </button>

      <button className="btn btn--ghost btn--block btn--sm" onClick={onBack}>
        編集に戻る
      </button>
      <button className="btn btn--ghost btn--block btn--sm" onClick={onNew}>
        別の画像を編集する
      </button>
    </div>
  );
}

// ----- Mount -----
const root = ReactDOM.createRoot(document.getElementById('editor-root'));
root.render(<Editor />);
