// Slack/Discord Emoji Maker — resize, shape, adjust, and animate images for chat platforms.
// Supports static PNG export and animated GIF via gif.js (CDN lazy-loaded).

window.TOOL_HANDLERS['slack-emoji'] = function SlackEmojiTool() {
  const { useState, useRef, useEffect, useCallback, useMemo } = React;

  // --- state ---
  const [file, setFile] = useState(null);
  const [src, setSrc] = useState('');
  const imgRef = useRef(null);

  // preset
  const PRESETS = useMemo(() => [
    { id: 'slack',   label: 'Slack',   size: 128, maxKB: 128 },
    { id: 'discord', label: 'Discord', size: 256, maxKB: 256 },
    { id: 'twitch',  label: 'Twitch',  size: 112, maxKB: null },
    { id: 'custom',  label: 'Custom',  size: null, maxKB: null },
  ], []);
  const [presetId, setPresetId] = useState('slack');
  const [customSize, setCustomSize] = useState(128);

  // static effects
  const [roundCorners, setRoundCorners] = useState(false);
  const [circleCrop, setCircleCrop] = useState(false);
  const [flipH, setFlipH] = useState(false);
  const [flipV, setFlipV] = useState(false);
  const [borderOn, setBorderOn] = useState(false);
  const [borderColor, setBorderColor] = useState('#2563eb');
  const [borderWidth, setBorderWidth] = useState(4);
  const [brightness, setBrightness] = useState(0);
  const [contrast, setContrast] = useState(0);

  // animated effect
  const ANIM_EFFECTS = useMemo(() => [
    { id: 'none',   label: 'None',   frames: 0,  icon: 'x' },
    { id: 'shake',  label: 'Shake',  frames: 6,  icon: 'swap' },
    { id: 'bounce', label: 'Bounce', frames: 8,  icon: 'arrow' },
    { id: 'spin',   label: 'Spin',   frames: 12, icon: 'rotate' },
    { id: 'pulse',  label: 'Pulse',  frames: 8,  icon: 'heart' },
    { id: 'party',  label: 'Party',  frames: 10, icon: 'palette' },
  ], []);
  const [animEffect, setAnimEffect] = useState('none');
  const [speed, setSpeed] = useState('normal');

  // quality / output
  const [quality, setQuality] = useState(90);
  const [outUrl, setOutUrl] = useState('');
  const [outSize, setOutSize] = useState(0);
  const [generating, setGenerating] = useState(false);

  const isAnimated = animEffect !== 'none';
  const preset = PRESETS.find(p => p.id === presetId);
  const outDim = preset.size || customSize;

  // speed to frame delay in ms
  const speedDelays = { slow: 120, normal: 70, fast: 35 };
  const frameDelay = speedDelays[speed] || 70;

  // --- file handling ---
  const handleFile = async (f) => {
    if (!f || !f.type.startsWith('image/')) return;
    const { img, url } = await window.loadImageFromFile(f);
    imgRef.current = img;
    setFile(f);
    setSrc(url);
    setOutUrl('');
    setOutSize(0);
  };

  // --- draw a single frame on a canvas ---
  const drawFrame = useCallback((frameIndex, totalFrames) => {
    const img = imgRef.current;
    if (!img) return null;

    const size = outDim;
    const c = document.createElement('canvas');
    c.width = size;
    c.height = size;
    const ctx = c.getContext('2d');

    // clipping
    if (circleCrop) {
      ctx.beginPath();
      ctx.arc(size / 2, size / 2, size / 2 - (borderOn ? borderWidth : 0), 0, Math.PI * 2);
      ctx.closePath();
      ctx.clip();
    } else if (roundCorners) {
      const r = size * 0.18;
      const inset = borderOn ? borderWidth : 0;
      roundRect(ctx, inset, inset, size - inset * 2, size - inset * 2, r);
      ctx.clip();
    }

    // filter
    const filters = [];
    if (brightness !== 0) filters.push('brightness(' + (100 + brightness) + '%)');
    if (contrast !== 0) filters.push('contrast(' + (100 + contrast) + '%)');

    // party hue rotate
    if (animEffect === 'party' && totalFrames > 0) {
      const hue = Math.round((frameIndex / totalFrames) * 360);
      filters.push('hue-rotate(' + hue + 'deg)');
    }
    if (filters.length) ctx.filter = filters.join(' ');

    ctx.save();
    ctx.translate(size / 2, size / 2);

    // flip
    const sx = flipH ? -1 : 1;
    const sy = flipV ? -1 : 1;
    ctx.scale(sx, sy);

    // animation transforms
    let dx = 0, dy = 0, rot = 0, sc = 1;
    if (totalFrames > 0) {
      const t = frameIndex / totalFrames;
      if (animEffect === 'shake') {
        dx = Math.sin(t * Math.PI * 2) * size * 0.08;
      } else if (animEffect === 'bounce') {
        dy = -Math.abs(Math.sin(t * Math.PI * 2)) * size * 0.15;
      } else if (animEffect === 'spin') {
        rot = t * Math.PI * 2;
      } else if (animEffect === 'pulse') {
        sc = 1 + 0.15 * Math.sin(t * Math.PI * 2);
      }
      // party: hue handled via filter above
    }

    ctx.translate(dx, dy);
    ctx.rotate(rot);
    ctx.scale(sc, sc);

    // draw image centered and cover the square
    const iw = img.width, ih = img.height;
    const ratio = Math.max(size / iw, size / ih);
    const dw = iw * ratio, dh = ih * ratio;
    ctx.drawImage(img, -dw / 2, -dh / 2, dw, dh);

    ctx.restore();

    // reset filter for border drawing
    ctx.filter = 'none';

    // border
    if (borderOn && borderWidth > 0) {
      ctx.strokeStyle = borderColor;
      ctx.lineWidth = borderWidth;
      if (circleCrop) {
        ctx.beginPath();
        ctx.arc(size / 2, size / 2, size / 2 - borderWidth / 2, 0, Math.PI * 2);
        ctx.stroke();
      } else if (roundCorners) {
        const r = size * 0.18;
        roundRect(ctx, borderWidth / 2, borderWidth / 2, size - borderWidth, size - borderWidth, r);
        ctx.stroke();
      } else {
        ctx.strokeRect(borderWidth / 2, borderWidth / 2, size - borderWidth, size - borderWidth);
      }
    }

    return c;
  }, [outDim, circleCrop, roundCorners, flipH, flipV, borderOn, borderColor, borderWidth, brightness, contrast, animEffect]);

  // rounded-rect helper
  function roundRect(ctx, x, y, w, h, r) {
    ctx.beginPath();
    ctx.moveTo(x + r, y);
    ctx.lineTo(x + w - r, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + r);
    ctx.lineTo(x + w, y + h - r);
    ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    ctx.lineTo(x + r, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - r);
    ctx.lineTo(x, y + r);
    ctx.quadraticCurveTo(x, y, x + r, y);
    ctx.closePath();
  }

  // --- static PNG preview (debounced) ---
  useEffect(() => {
    if (!imgRef.current || isAnimated) return;
    const id = setTimeout(() => {
      const c = drawFrame(0, 0);
      if (!c) return;
      c.toBlob((blob) => {
        if (!blob) return;
        if (outUrl) URL.revokeObjectURL(outUrl);
        setOutUrl(URL.createObjectURL(blob));
        setOutSize(blob.size);
      }, 'image/png');
    }, 80);
    return () => clearTimeout(id);
  }, [file, outDim, circleCrop, roundCorners, flipH, flipV, borderOn, borderColor, borderWidth, brightness, contrast, isAnimated, drawFrame]);

  // --- generate GIF ---
  const generateGif = async () => {
    if (!imgRef.current) return;
    setGenerating(true);
    try {
      await window.loadScript('https://cdn.jsdelivr.net/npm/gif.js@0.2.0/dist/gif.js');
      const workerBlob = await window.getGifWorkerBlob();
      const eff = ANIM_EFFECTS.find(e => e.id === animEffect);
      const numFrames = eff ? eff.frames : 6;

      const gif = new GIF({
        workers: 2,
        quality: Math.max(1, Math.round((110 - quality) / 5)),
        workerScript: workerBlob,
        width: outDim,
        height: outDim,
        transparent: null,
      });

      for (let i = 0; i < numFrames; i++) {
        const c = drawFrame(i, numFrames);
        if (c) gif.addFrame(c, { delay: frameDelay, copy: true });
      }

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

      if (outUrl) URL.revokeObjectURL(outUrl);
      setOutUrl(URL.createObjectURL(blob));
      setOutSize(blob.size);
    } catch (e) {
      console.error('GIF generation failed:', e);
    } finally {
      setGenerating(false);
    }
  };

  // re-generate GIF whenever animated params change
  useEffect(() => {
    if (!imgRef.current || !isAnimated) return;
    generateGif();
  }, [animEffect, speed, outDim, circleCrop, roundCorners, flipH, flipV, borderOn, borderColor, borderWidth, brightness, contrast, quality, file]);

  // --- download ---
  const download = () => {
    if (!outUrl) return;
    const base = file ? file.name.replace(/\.[^.]+$/, '') : 'emoji';
    const ext = isAnimated ? 'gif' : 'png';
    fetch(outUrl).then(r => r.blob()).then(blob => {
      window.downloadBlob(blob, base + '-emoji.' + ext);
    });
  };

  // --- size warning ---
  const sizeWarning = useMemo(() => {
    if (!outSize || !preset.maxKB) return null;
    const maxBytes = preset.maxKB * 1024;
    if (outSize > maxBytes) {
      return 'File is ' + window.fmtBytes(outSize) + ' — exceeds ' + preset.label + ' limit of ' + preset.maxKB + ' KB. Try lowering quality.';
    }
    return null;
  }, [outSize, preset]);

  // --- CSS animation class for live preview ---
  const previewAnimStyle = useMemo(() => {
    if (!isAnimated) return {};
    const dur = speed === 'slow' ? '1.2s' : speed === 'fast' ? '0.35s' : '0.7s';
    const common = { animationDuration: dur, animationIterationCount: 'infinite', animationTimingFunction: 'ease-in-out' };
    switch (animEffect) {
      case 'shake':  return { ...common, animationName: 'se-shake' };
      case 'bounce': return { ...common, animationName: 'se-bounce' };
      case 'spin':   return { ...common, animationName: 'se-spin', animationTimingFunction: 'linear' };
      case 'pulse':  return { ...common, animationName: 'se-pulse' };
      case 'party':  return { ...common, animationName: 'se-party' };
      default:       return {};
    }
  }, [isAnimated, animEffect, speed]);

  const reset = () => {
    if (outUrl) URL.revokeObjectURL(outUrl);
    if (src) URL.revokeObjectURL(src);
    setFile(null); setSrc(''); setOutUrl(''); setOutSize(0);
  };

  // --- render ---
  if (!file) {
    return React.createElement(window.Dropzone, {
      onFile: handleFile,
      title: 'Drop an image here',
      hint: 'PNG, JPG, WebP, GIF',
      accept: 'image/*',
    });
  }

  return (
    <div className="mini-tool">
      <style>{`
        @keyframes se-shake  { 0%,100%{transform:translateX(0)} 25%{transform:translateX(-6px)} 75%{transform:translateX(6px)} }
        @keyframes se-bounce { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-14px)} }
        @keyframes se-spin   { 0%{transform:rotate(0)} 100%{transform:rotate(360deg)} }
        @keyframes se-pulse  { 0%,100%{transform:scale(1)} 50%{transform:scale(1.18)} }
        @keyframes se-party  { 0%{filter:hue-rotate(0)} 100%{filter:hue-rotate(360deg)} }

        .se-presets { display:flex; gap:8px; flex-wrap:wrap; }
        .se-preset {
          display:inline-flex; align-items:center; gap:6px;
          height:38px; padding:0 16px; border-radius:10px;
          font-weight:600; font-size:13px; cursor:pointer;
          background:var(--id-surface-alt); border:1.5px solid var(--id-border);
          color:var(--id-text); transition:all 180ms var(--id-ease);
        }
        .se-preset:hover { border-color:var(--id-border-strong); background:var(--id-surface-sunken); }
        .se-preset.active {
          background:var(--id-brand-blue); color:white; border-color:var(--id-brand-blue);
          box-shadow:0 4px 12px -4px color-mix(in oklab, var(--id-brand-blue) 50%, transparent);
        }
        .se-preset .se-dim { font-size:11px; opacity:0.75; font-weight:500; }

        .se-effects { display:grid; grid-template-columns:repeat(auto-fill, minmax(100px, 1fr)); gap:8px; }
        .se-fx-card {
          display:flex; flex-direction:column; align-items:center; justify-content:center;
          gap:6px; padding:14px 8px; border-radius:12px; cursor:pointer;
          background:var(--id-surface-alt); border:1.5px solid var(--id-border);
          color:var(--id-text); font-weight:600; font-size:12px;
          transition:all 180ms var(--id-ease); text-align:center;
        }
        .se-fx-card:hover { border-color:var(--id-border-strong); background:var(--id-surface-sunken); }
        .se-fx-card.active {
          background:var(--id-brand-blue-soft); border-color:var(--id-brand-blue); color:var(--id-brand-blue);
          box-shadow:0 2px 8px -2px color-mix(in oklab, var(--id-brand-blue) 30%, transparent);
        }
        .se-fx-card .se-fx-icon {
          width:32px; height:32px; border-radius:8px;
          display:grid; place-items:center;
          background:var(--id-surface-sunken);
        }
        .se-fx-card.active .se-fx-icon { background:color-mix(in oklab, var(--id-brand-blue) 15%, transparent); }

        .se-toggle-row { display:flex; flex-wrap:wrap; gap:8px; }
        .se-toggle {
          display:inline-flex; align-items:center; gap:6px;
          height:34px; padding:0 14px; border-radius:8px;
          font-weight:600; font-size:12px; cursor:pointer;
          background:var(--id-surface-alt); border:1.5px solid var(--id-border);
          color:var(--id-text-muted); transition:all 180ms var(--id-ease);
        }
        .se-toggle:hover { border-color:var(--id-border-strong); color:var(--id-text); }
        .se-toggle.on {
          background:var(--id-success-soft); border-color:var(--id-success); color:var(--id-success);
        }

        .se-preview-wrap {
          display:flex; align-items:center; justify-content:center;
          padding:24px; margin-bottom:20px;
          background: repeating-conic-gradient(var(--id-surface-alt) 0 25%, var(--id-surface) 0 50%) 50%/16px 16px;
          border:1px solid var(--id-border); border-radius:14px;
          min-height:200px;
        }
        .se-preview-wrap img {
          max-width:200px; max-height:200px; border-radius:4px;
          image-rendering: pixelated;
        }
        .se-section { margin-top:20px; }
        .se-section-title {
          font-size:11px; font-weight:700; text-transform:uppercase;
          letter-spacing:0.06em; color:var(--id-text-muted); margin-bottom:8px;
        }

        .se-warn {
          display:flex; align-items:center; gap:8px;
          padding:10px 14px; margin-top:12px; border-radius:10px;
          background:var(--id-warning-soft); color:var(--id-warning);
          font-size:12px; font-weight:600;
        }

        .se-speed-row { display:flex; gap:6px; margin-top:8px; }
        .se-speed-btn {
          height:30px; padding:0 14px; border-radius:8px;
          font-weight:600; font-size:12px; cursor:pointer;
          background:var(--id-surface-alt); border:1.5px solid var(--id-border);
          color:var(--id-text-muted); transition:all 180ms var(--id-ease);
        }
        .se-speed-btn:hover { border-color:var(--id-border-strong); color:var(--id-text); }
        .se-speed-btn.active { background:var(--id-brand-blue); color:white; border-color:var(--id-brand-blue); }

        .se-border-controls {
          display:flex; align-items:center; gap:12px; margin-top:8px;
          padding:10px 14px; border-radius:10px;
          background:var(--id-surface-alt); border:1px solid var(--id-border);
        }
        .se-border-controls input[type="color"] {
          width:32px; height:32px; border:1px solid var(--id-border-strong);
          border-radius:8px; cursor:pointer; padding:2px; background:none;
        }

        .se-info-row {
          display:flex; align-items:center; gap:8px; margin-top:10px;
          font-size:12px; color:var(--id-text-muted);
        }
        .se-info-row .se-badge {
          display:inline-flex; align-items:center; gap:4px;
          height:24px; padding:0 10px; border-radius:999px;
          font-weight:700; font-size:11px;
          background:var(--id-brand-blue-soft); color:var(--id-brand-blue);
        }
      `}</style>

      {/* Preview */}
      <div className="se-preview-wrap">
        <img
          src={isAnimated && outUrl ? outUrl : (outUrl || src)}
          alt="Emoji preview"
          style={isAnimated && !outUrl ? previewAnimStyle : {}}
        />
      </div>

      <div className="se-info-row">
        <span className="se-badge">{outDim} x {outDim}</span>
        {outSize > 0 && (
          <span className="se-badge" style={sizeWarning ? { background: 'var(--id-warning-soft)', color: 'var(--id-warning)' } : {}}>
            {window.fmtBytes(outSize)}
          </span>
        )}
        <span className="se-badge" style={{ background: 'var(--id-surface-alt)', color: 'var(--id-text-muted)' }}>
          {isAnimated ? 'GIF' : 'PNG'}
        </span>
        {generating && (
          <span className="se-badge" style={{ background: 'var(--id-info-soft)', color: 'var(--id-info)' }}>
            <window.Icon name="bolt" size={10} /> Generating...
          </span>
        )}
      </div>

      {sizeWarning && (
        <div className="se-warn">
          <window.Icon name="bolt" size={14} />
          {sizeWarning}
        </div>
      )}

      {/* Platform presets */}
      <div className="se-section">
        <div className="se-section-title">Platform</div>
        <div className="se-presets">
          {PRESETS.map(p => (
            <button
              key={p.id}
              className={'se-preset' + (presetId === p.id ? ' active' : '')}
              onClick={() => setPresetId(p.id)}
            >
              {p.label}
              {p.size && <span className="se-dim">{p.size}px</span>}
              {p.maxKB && <span className="se-dim">&le;{p.maxKB}KB</span>}
            </button>
          ))}
        </div>
        {presetId === 'custom' && (
          <div className="cmp-slider-row" style={{ marginTop: 12 }}>
            <div className="cmp-label"><span>Size</span><span className="val">{customSize}px</span></div>
            <input type="range" min="16" max="512" step="1" value={customSize}
                   onChange={(e) => setCustomSize(+e.target.value)} className="cmp-slider" />
          </div>
        )}
      </div>

      {/* Static effects */}
      <div className="se-section">
        <div className="se-section-title">Shape &amp; Adjustments</div>
        <div className="se-toggle-row">
          <button className={'se-toggle' + (roundCorners ? ' on' : '')} onClick={() => { setRoundCorners(!roundCorners); if (!roundCorners) setCircleCrop(false); }}>
            <window.Icon name="square" size={13} /> Round
          </button>
          <button className={'se-toggle' + (circleCrop ? ' on' : '')} onClick={() => { setCircleCrop(!circleCrop); if (!circleCrop) setRoundCorners(false); }}>
            <window.Icon name="target" size={13} /> Circle
          </button>
          <button className={'se-toggle' + (flipH ? ' on' : '')} onClick={() => setFlipH(!flipH)}>
            <window.Icon name="swap" size={13} /> Flip H
          </button>
          <button className={'se-toggle' + (flipV ? ' on' : '')} onClick={() => setFlipV(!flipV)}>
            <window.Icon name="swap" size={13} /> Flip V
          </button>
          <button className={'se-toggle' + (borderOn ? ' on' : '')} onClick={() => setBorderOn(!borderOn)}>
            <window.Icon name="square" size={13} /> Border
          </button>
        </div>

        {borderOn && (
          <div className="se-border-controls">
            <input type="color" value={borderColor} onChange={(e) => setBorderColor(e.target.value)} title="Border color" />
            <div style={{ flex: 1 }}>
              <div className="cmp-label" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, fontWeight: 600 }}>
                <span>Width</span><span className="val">{borderWidth}px</span>
              </div>
              <input type="range" min="1" max="16" step="1" value={borderWidth}
                     onChange={(e) => setBorderWidth(+e.target.value)} className="cmp-slider" />
            </div>
          </div>
        )}

        <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12, marginTop: 12 }}>
          <div>
            <div className="cmp-label" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, fontWeight: 600 }}>
              <span>Brightness</span><span className="val">{brightness > 0 ? '+' : ''}{brightness}</span>
            </div>
            <input type="range" min="-50" max="50" step="1" value={brightness}
                   onChange={(e) => setBrightness(+e.target.value)} className="cmp-slider" />
          </div>
          <div>
            <div className="cmp-label" style={{ display: 'flex', justifyContent: 'space-between', fontSize: 11, fontWeight: 600 }}>
              <span>Contrast</span><span className="val">{contrast > 0 ? '+' : ''}{contrast}</span>
            </div>
            <input type="range" min="-50" max="50" step="1" value={contrast}
                   onChange={(e) => setContrast(+e.target.value)} className="cmp-slider" />
          </div>
        </div>
      </div>

      {/* Animated effects */}
      <div className="se-section">
        <div className="se-section-title">Animation</div>
        <div className="se-effects">
          {ANIM_EFFECTS.map(eff => (
            <div
              key={eff.id}
              className={'se-fx-card' + (animEffect === eff.id ? ' active' : '')}
              onClick={() => setAnimEffect(eff.id)}
            >
              <div className="se-fx-icon">
                <window.Icon name={eff.icon} size={16} />
              </div>
              {eff.label}
            </div>
          ))}
        </div>

        {isAnimated && (
          <div>
            <div className="se-section-title" style={{ marginTop: 14 }}>Speed</div>
            <div className="se-speed-row">
              {['slow', 'normal', 'fast'].map(s => (
                <button key={s} className={'se-speed-btn' + (speed === s ? ' active' : '')} onClick={() => setSpeed(s)}>
                  {s.charAt(0).toUpperCase() + s.slice(1)}
                </button>
              ))}
            </div>
          </div>
        )}
      </div>

      {/* Quality */}
      {isAnimated && (
        <div className="se-section">
          <div className="cmp-slider-row">
            <div className="cmp-label"><span>GIF Quality</span><span className="val">{quality}%</span></div>
            <input type="range" min="10" max="100" step="1" value={quality}
                   onChange={(e) => setQuality(+e.target.value)} className="cmp-slider" />
          </div>
        </div>
      )}

      {/* Actions */}
      <div className="cmp-actions">
        <button className="btn btn-secondary" onClick={reset}>
          <window.Icon name="upload" size={16} /> Another
        </button>
        <button className="btn btn-primary" onClick={download} disabled={!outUrl || generating}>
          <window.Icon name="download" size={16} /> Download {isAnimated ? 'GIF' : 'PNG'}
        </button>
      </div>
    </div>
  );
};
