// GIF Maker — create animated GIFs from uploaded images using gif.js.

const GIF_JS_URL = 'https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.js';
const GIF_WORKER_URL = 'https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.worker.js';

window.TOOL_HANDLERS['gif-maker'] = function GifMakerTool() {
  const [frames, setFrames] = React.useState([]);    // { file, url, img }
  const [delay, setDelay] = React.useState(500);
  const [outputWidth, setOutputWidth] = React.useState(480);
  const [loop, setLoop] = React.useState(0);          // 0 = infinite
  const [encoding, setEncoding] = React.useState(false);
  const [progress, setProgress] = React.useState(0);
  const [resultBlob, setResultBlob] = React.useState(null);
  const [resultUrl, setResultUrl] = React.useState('');
  const [err, setErr] = React.useState('');
  const [previewIdx, setPreviewIdx] = React.useState(0);

  // CSS animation preview — cycle through frames
  React.useEffect(() => {
    if (frames.length < 2) return;
    const id = setInterval(() => {
      setPreviewIdx((prev) => (prev + 1) % frames.length);
    }, delay);
    return () => clearInterval(id);
  }, [frames.length, delay]);

  // Reset preview index when frames change
  React.useEffect(() => {
    if (previewIdx >= frames.length) setPreviewIdx(0);
  }, [frames.length]);

  const addImages = async (picked) => {
    const list = Array.isArray(picked) ? picked : [picked];
    const imageFiles = list.filter((f) => f.type.startsWith('image/'));
    if (imageFiles.length === 0) return;
    const loaded = await Promise.all(
      imageFiles.map(async (f) => {
        const { img, url } = await window.loadImageFromFile(f);
        return { file: f, url, img };
      })
    );
    setFrames((prev) => [...prev, ...loaded]);
    // Clear previous result when frames change
    if (resultUrl) { URL.revokeObjectURL(resultUrl); setResultUrl(''); setResultBlob(null); }
  };

  const removeFrame = (i) => {
    setFrames((prev) => {
      const next = [...prev];
      URL.revokeObjectURL(next[i].url);
      next.splice(i, 1);
      return next;
    });
    if (resultUrl) { URL.revokeObjectURL(resultUrl); setResultUrl(''); setResultBlob(null); }
  };

  const moveFrame = (i, dir) => {
    setFrames((prev) => {
      const next = [...prev];
      const j = i + dir;
      if (j < 0 || j >= next.length) return prev;
      [next[i], next[j]] = [next[j], next[i]];
      return next;
    });
    if (resultUrl) { URL.revokeObjectURL(resultUrl); setResultUrl(''); setResultBlob(null); }
  };

  const drawFrameToCanvas = (img, width) => {
    const w = width === 'original' ? img.width : width;
    const scale = w / img.width;
    const h = Math.round(img.height * scale);
    const c = document.createElement('canvas');
    c.width = w;
    c.height = h;
    const ctx = c.getContext('2d');
    ctx.drawImage(img, 0, 0, w, h);
    return c;
  };

  const createGif = async () => {
    if (frames.length < 2) return;
    setEncoding(true);
    setProgress(0);
    setErr('');
    if (resultUrl) { URL.revokeObjectURL(resultUrl); setResultUrl(''); setResultBlob(null); }

    try {
      await window.loadScript(GIF_JS_URL);
      const workerBlob = await window.getGifWorkerBlob();

      // Determine consistent dimensions from first frame
      const firstCanvas = drawFrameToCanvas(frames[0].img, outputWidth);
      const gifWidth = firstCanvas.width;
      const gifHeight = firstCanvas.height;

      const gif = new GIF({
        workers: 2,
        quality: 10,
        width: gifWidth,
        height: gifHeight,
        workerScript: workerBlob,
        repeat: loop,
      });

      frames.forEach((frame) => {
        const canvas = drawFrameToCanvas(frame.img, outputWidth);
        // If frames have different aspect ratios, fit them onto a common canvas
        const fitCanvas = document.createElement('canvas');
        fitCanvas.width = gifWidth;
        fitCanvas.height = gifHeight;
        const ctx = fitCanvas.getContext('2d');
        ctx.fillStyle = '#ffffff';
        ctx.fillRect(0, 0, gifWidth, gifHeight);
        // Center the resized frame
        const dx = Math.round((gifWidth - canvas.width) / 2);
        const dy = Math.round((gifHeight - canvas.height) / 2);
        ctx.drawImage(canvas, dx, dy);
        gif.addFrame(fitCanvas, { delay: delay, copy: true });
      });

      gif.on('progress', (p) => setProgress(Math.round(p * 100)));

      const blob = await new Promise((resolve, reject) => {
        gif.on('finished', (blob) => resolve(blob));
        gif.on('error', (e) => reject(e));
        gif.render();
      });

      const url = URL.createObjectURL(blob);
      setResultBlob(blob);
      setResultUrl(url);
    } catch (e) {
      setErr(e.message || 'Failed to create GIF');
    } finally {
      setEncoding(false);
    }
  };

  const download = () => {
    if (!resultBlob) return;
    window.downloadBlob(resultBlob, 'animation.gif');
  };

  const reset = () => {
    frames.forEach((f) => URL.revokeObjectURL(f.url));
    if (resultUrl) URL.revokeObjectURL(resultUrl);
    setFrames([]);
    setResultBlob(null);
    setResultUrl('');
    setProgress(0);
    setErr('');
    setPreviewIdx(0);
  };

  const loopOptions = [
    { value: 0, label: 'Infinite' },
    { value: 1, label: '1x' },
    { value: 3, label: '3x' },
    { value: 5, label: '5x' },
  ];

  const widthOptions = [
    { value: 320, label: '320px' },
    { value: 480, label: '480px' },
    { value: 640, label: '640px' },
    { value: 800, label: '800px' },
    { value: 'original', label: 'Original' },
  ];

  // Initial dropzone — no frames yet
  if (frames.length === 0) {
    return (
      <window.Dropzone
        onFile={addImages}
        multiple
        title="Drop images here"
        hint="PNG, JPG, WebP — we'll turn them into an animated GIF"
        accept="image/*"
      />
    );
  }

  return (
    <div className="mini-tool">
      {/* Frame strip */}
      <div className="mini-label">{frames.length} frame{frames.length !== 1 ? 's' : ''}</div>
      <div style={{
        display: 'flex', gap: 8, overflowX: 'auto', padding: '8px 0',
        scrollBehavior: 'smooth',
      }}>
        {frames.map((frame, i) => (
          <div key={i} style={{
            flex: '0 0 auto', width: 90, textAlign: 'center',
            background: previewIdx === i ? 'var(--id-primary-soft, #e8f0fe)' : 'var(--id-surface-alt)',
            borderRadius: 10, padding: 6, position: 'relative',
            border: previewIdx === i ? '2px solid var(--id-primary, #4285f4)' : '2px solid transparent',
            transition: 'border-color 0.15s, background 0.15s',
          }}>
            <img src={frame.url} alt={`Frame ${i + 1}`} style={{
              width: '100%', height: 56, objectFit: 'cover', borderRadius: 6,
              display: 'block',
            }} />
            <div style={{ fontSize: 11, color: 'var(--id-text-muted)', marginTop: 4, fontWeight: 600 }}>
              #{i + 1}
            </div>
            <div style={{ display: 'flex', justifyContent: 'center', gap: 2, marginTop: 2 }}>
              <button className="icon-btn" onClick={() => moveFrame(i, -1)} disabled={i === 0}
                      aria-label="Move up" style={{ fontSize: 12, padding: '1px 4px' }}>
                <window.Icon name="arrow" size={10} style={{ transform: 'rotate(-90deg)' }} />
              </button>
              <button className="icon-btn" onClick={() => moveFrame(i, 1)} disabled={i === frames.length - 1}
                      aria-label="Move down" style={{ fontSize: 12, padding: '1px 4px' }}>
                <window.Icon name="arrow" size={10} style={{ transform: 'rotate(90deg)' }} />
              </button>
              <button className="icon-btn" onClick={() => removeFrame(i)}
                      aria-label="Remove" style={{ fontSize: 12, padding: '1px 4px' }}>
                <window.Icon name="x" size={10} />
              </button>
            </div>
          </div>
        ))}
      </div>

      {/* Add more */}
      <div style={{ marginTop: 4 }}>
        <label className="btn btn-secondary" style={{ cursor: 'pointer' }}>
          <window.Icon name="plus" size={14} /> Add more
          <input type="file" accept="image/*" multiple hidden
                 onChange={(e) => addImages(Array.from(e.target.files))} />
        </label>
      </div>

      {/* Live preview */}
      {frames.length >= 2 && (
        <div className="cmp-preview" style={{ gridTemplateColumns: '1fr', marginTop: 16 }}>
          <div className="cmp-side after">
            <div className="cmp-ttl">Preview · {delay}ms per frame</div>
            <img
              src={frames[previewIdx] ? frames[previewIdx].url : ''}
              alt="Preview"
              style={{ maxHeight: 300, objectFit: 'contain' }}
            />
          </div>
        </div>
      )}

      {/* Settings */}
      <div style={{ marginTop: 16 }}>
        <div className="cmp-slider-row">
          <div className="cmp-label"><span>Frame delay</span><span className="val">{delay}ms</span></div>
          <input type="range" min="50" max="2000" step="50" value={delay}
                 onChange={(e) => setDelay(+e.target.value)} className="cmp-slider" />
        </div>

        <div className="mini-row" style={{ marginTop: 12 }}>
          <div className="mini-field">
            <label className="mini-label">Output width</label>
            <select className="mini-input" value={outputWidth}
                    onChange={(e) => setOutputWidth(e.target.value === 'original' ? 'original' : +e.target.value)}>
              {widthOptions.map((o) => (
                <option key={o.value} value={o.value}>{o.label}</option>
              ))}
            </select>
          </div>
          <div className="mini-field">
            <label className="mini-label">Loop</label>
            <select className="mini-input" value={loop}
                    onChange={(e) => setLoop(+e.target.value)}>
              {loopOptions.map((o) => (
                <option key={o.value} value={o.value}>{o.label}</option>
              ))}
            </select>
          </div>
        </div>
      </div>

      {/* Progress bar during encoding */}
      {encoding && (
        <div style={{ marginTop: 16 }}>
          <div className="cmp-label"><span>Encoding GIF…</span><span className="val">{progress}%</span></div>
          <div style={{
            width: '100%', height: 8, borderRadius: 4,
            background: 'var(--id-surface-alt)', overflow: 'hidden', marginTop: 4,
          }}>
            <div style={{
              width: progress + '%', height: '100%', borderRadius: 4,
              background: 'var(--id-primary, #4285f4)',
              transition: 'width 0.2s ease',
            }} />
          </div>
        </div>
      )}

      {/* Error */}
      {err && <div style={{ color: '#c8321f', marginTop: 10 }}>{err}</div>}

      {/* Result */}
      {resultUrl && (
        <div style={{ marginTop: 16 }}>
          <div className="cmp-preview" style={{ gridTemplateColumns: '1fr' }}>
            <div className="cmp-side after">
              <div className="cmp-ttl">Result GIF · {window.fmtBytes(resultBlob.size)}</div>
              <img src={resultUrl} alt="Output GIF" style={{ maxHeight: 300, objectFit: 'contain' }} />
            </div>
          </div>
        </div>
      )}

      {/* Action buttons */}
      <div className="cmp-actions" style={{ marginTop: 12 }}>
        <button className="btn btn-secondary" onClick={reset}>
          <window.Icon name="rotate" size={16} /> Start over
        </button>
        {!resultUrl ? (
          <button className="btn btn-primary" onClick={createGif}
                  disabled={encoding || frames.length < 2}>
            <window.Icon name="sparkle" size={16} /> {encoding ? 'Creating…' : 'Create GIF'}
          </button>
        ) : (
          <button className="btn btn-primary" onClick={download}>
            <window.Icon name="download" size={16} /> Download GIF
          </button>
        )}
      </div>
    </div>
  );
};
