// Shared helpers for all tool files. Must load before individual tool scripts.
//
// Each tool file registers its component on window.TOOL_HANDLERS[toolId].
// tool-modal.jsx looks the handler up; if none exists, it falls back to
// window.ComingSoonTool (a friendly placeholder).

window.TOOL_HANDLERS = window.TOOL_HANDLERS || {};

window.fmtBytes = function (n) {
  if (!n && n !== 0) return '…';
  if (n < 1024) return n + ' B';
  if (n < 1048576) return (n / 1024).toFixed(1) + ' KB';
  return (n / 1048576).toFixed(2) + ' MB';
};

window.downloadBlob = function (blob, filename) {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url; a.download = filename; a.click();
  setTimeout(() => URL.revokeObjectURL(url), 1000);
};

window.loadImageFromFile = function (file) {
  return new Promise((resolve, reject) => {
    const url = URL.createObjectURL(file);
    const img = new Image();
    img.onload = () => resolve({ img, url });
    img.onerror = () => { URL.revokeObjectURL(url); reject(new Error('Not an image')); };
    img.src = url;
  });
};

window.Dropzone = function Dropzone({ onFile, title, hint, accept = '*/*', multiple = false }) {
  const [, t] = window.useI18n();
  const [dragOver, setDragOver] = React.useState(false);
  const inputRef = React.useRef(null);
  const finalTitle = title || t('dropzone.default_title');
  const finalHint = hint || t('dropzone.default_hint');
  const handle = (fileList) => {
    if (!fileList || fileList.length === 0) return;
    if (multiple) onFile(Array.from(fileList));
    else onFile(fileList[0]);
  };
  return (
    <div className={`dropzone ${dragOver ? 'drag-over' : ''}`}
         onClick={() => inputRef.current?.click()}
         onDragOver={(e) => { e.preventDefault(); setDragOver(true); }}
         onDragLeave={() => setDragOver(false)}
         onDrop={(e) => { e.preventDefault(); setDragOver(false); handle(e.dataTransfer.files); }}>
      <div className="dz-icon"><window.Icon name="upload" size={28} /></div>
      <h3>{finalTitle}</h3>
      <p>{t('dropzone.or_click_single', { hint: finalHint })}</p>
      <div className="dz-btn">{multiple ? t('dropzone.choose_files') : t('dropzone.choose_file')}</div>
      <input ref={inputRef} type="file" accept={accept} multiple={multiple} hidden
             onChange={(e) => handle(e.target.files)} />
    </div>
  );
};

// Shared image-format converter. Loads an image, redraws on a canvas, exports as `outMime`.
// `flatten` fills the canvas with white first (used by PNG→JPG to drop alpha).
// Supports single-file and batch mode via window.BatchMode.
window.ImageConverter = function ImageConverter({ title, hint, accept, outMime, outExt, flatten = false, quality = 0.92 }) {
  const [, t] = window.useI18n();
  const [mode, setMode] = React.useState('single');
  const [file, setFile] = React.useState(null);
  const [src, setSrc] = React.useState('');
  const [srcSize, setSrcSize] = React.useState(0);
  const [outUrl, setOutUrl] = React.useState('');
  const [outSize, setOutSize] = React.useState(0);
  const [dims, setDims] = React.useState({ w: 0, h: 0 });

  const convertOne = (f) => new Promise(async (resolve) => {
    const { img, url } = await window.loadImageFromFile(f);
    const c = document.createElement('canvas');
    c.width = img.width; c.height = img.height;
    const ctx = c.getContext('2d');
    if (flatten) { ctx.fillStyle = '#ffffff'; ctx.fillRect(0, 0, c.width, c.height); }
    ctx.drawImage(img, 0, 0);
    URL.revokeObjectURL(url);
    c.toBlob((blob) => resolve({ blob, ext: outExt }), outMime, quality);
  });

  const handleFile = async (f) => {
    if (!f) return;
    if (src) URL.revokeObjectURL(src);
    const { img, url } = await window.loadImageFromFile(f);
    setFile(f); setSrc(url); setSrcSize(f.size); setDims({ w: img.width, h: img.height });
    const { blob } = await convertOne(f);
    if (blob) {
      if (outUrl) URL.revokeObjectURL(outUrl);
      setOutUrl(URL.createObjectURL(blob)); setOutSize(blob.size);
    }
  };

  const download = () => {
    if (!outUrl) return;
    const a = document.createElement('a'); a.href = outUrl;
    a.download = file.name.replace(/\.[^.]+$/, '') + '.' + outExt; a.click();
  };

  if (mode === 'batch') {
    return (
      <window.BatchMode
        onExit={() => setMode('single')}
        process={convertOne} opts={{}} setOpts={() => {}}
        accept={accept} title={title} hint={hint + ' · multiple at once'}
        defaultExt={outExt} zipName={`${outExt}-batch`}
      />
    );
  }

  if (!file) return (
    <>
      <window.Dropzone onFile={handleFile} title={title} hint={hint} accept={accept} />
      <div style={{ textAlign: 'center', marginTop: 10 }}>
        <button className="filter-pill" onClick={() => setMode('batch')}><window.Icon name="grid" size={12} /> {t('converter.batch_mode')}</button>
      </div>
    </>
  );

  // Use the before/after slider when both before and after are usable images
  // (i.e. browser can display both). HEIC inputs aren't decoded by most
  // browsers so we fall through to a single-pane preview for those.
  const isBrowserPreviewable = !!file && !/heic|heif/i.test(file.type || file.name);
  const reduction = srcSize && outSize ? Math.max(-100, Math.min(100, Math.round((1 - outSize / srcSize) * 100))) : 0;

  return (
    <div>
      {isBrowserPreviewable && window.BeforeAfterSlider ? (
        <window.BeforeAfterSlider
          before={src}
          after={outUrl || src}
          labelBefore={`Original · ${window.fmtBytes(srcSize)}`}
          labelAfter={`${outExt.toUpperCase()} · ${outSize ? window.fmtBytes(outSize) : '…'}`}
          caption={`${dims.w} × ${dims.h}${outSize ? ` · ${reduction > 0 ? '−' : reduction < 0 ? '+' : ''}${Math.abs(reduction)}%` : ''}`}
        />
      ) : (
        <div className="cmp-preview" style={{ gridTemplateColumns: '1fr' }}>
          <div className="cmp-side after">
            <div className="cmp-ttl">{t('converter.output_label', { ext: outExt.toUpperCase() })}</div>
            <img src={outUrl || src} alt="" />
            <div className="cmp-meta">{window.fmtBytes(outSize)}</div>
          </div>
        </div>
      )}
      <div className="cmp-actions">
        <button className="btn btn-secondary" onClick={() => { setFile(null); setSrc(''); setOutUrl(''); setSrcSize(0); setOutSize(0); }}><window.Icon name="upload" size={16} /> {t('converter.another')}</button>
        <button className="filter-pill" onClick={() => setMode('batch')}><window.Icon name="grid" size={14} /> {t('converter.batch_mode')}</button>
        <button className="btn btn-primary" onClick={download}><window.Icon name="download" size={16} /> {t('converter.download', { ext: outExt.toUpperCase() })}</button>
      </div>
    </div>
  );
};

// Lazy-load an external script once and cache the promise on window.
// Use for CDN libraries like pdf-lib, pdf.js, SheetJS, fflate, heic2any, etc.
// Only URLs from trusted CDN hosts are allowed to prevent arbitrary script injection.
const _TRUSTED_SCRIPT_HOSTS = ['cdn.jsdelivr.net', 'unpkg.com', 'cdnjs.cloudflare.com', 'esm.sh', 'pdf-lib.js.org', 'pagead2.googlesyndication.com'];

window.loadScript = function (url) {
  window.__loadedScripts = window.__loadedScripts || {};
  if (window.__loadedScripts[url]) return window.__loadedScripts[url];
  try {
    const u = new URL(url);
    if (u.protocol !== 'https:' || !_TRUSTED_SCRIPT_HOSTS.some((h) => u.hostname === h || u.hostname.endsWith('.' + h))) {
      return Promise.reject(new Error('Blocked script load from untrusted host: ' + u.hostname));
    }
  } catch (e) {
    return Promise.reject(new Error('Invalid script URL: ' + url));
  }
  const p = new Promise((resolve, reject) => {
    const s = document.createElement('script');
    s.src = url;
    s.onload = () => resolve();
    s.onerror = () => { delete window.__loadedScripts[url]; reject(new Error('Failed to load ' + url)); };
    document.head.appendChild(s);
  });
  window.__loadedScripts[url] = p;
  return p;
};

// Support contact, surfaced from every error UI so users always have a way out.
window.MM_SUPPORT_EMAIL = window.MM_SUPPORT_EMAIL || 'support@magictools.com';

// Standardised error card. Use this in any tool that catches an exception so
// the user always sees the support email + an actionable message.
window.ToolError = function ToolError({ error, hint, onRetry }) {
  const msg = (error && (error.message || String(error))) || 'Something went wrong.';
  const mailHref = `mailto:${window.MM_SUPPORT_EMAIL}?subject=${encodeURIComponent('MiniMagics error')}&body=${encodeURIComponent('What I was doing:\n\nError shown:\n' + msg)}`;
  return (
    <div className="tool-error">
      <div className="te-icon"><window.Icon name="x" size={20} strokeWidth={2.2} /></div>
      <div className="te-body">
        <div className="te-title">Something went wrong</div>
        <div className="te-msg">{msg}</div>
        {hint && <div className="te-hint">{hint}</div>}
        <div className="te-actions">
          {onRetry && <button className="btn btn-secondary" onClick={onRetry}><window.Icon name="rotate" size={14} /> Try again</button>}
          <a className="btn btn-secondary" href={mailHref}>
            <window.Icon name="share" size={14} /> Email support
          </a>
        </div>
        <div className="te-foot">
          Need help? <a href={`mailto:${window.MM_SUPPORT_EMAIL}`}>{window.MM_SUPPORT_EMAIL}</a>
        </div>
      </div>
    </div>
  );
};

// Lightweight status component for tool files that lazy-load heavy libraries.
window.LoadingCard = function LoadingCard({ label, sub }) {
  const [, t] = window.useI18n();
  return (
    <div style={{ textAlign: 'center', padding: '40px 20px' }}>
      <div className="dz-icon" style={{ opacity: .6 }}><window.Icon name="bolt" size={28} /></div>
      <div style={{ fontWeight: 700, marginBottom: 4, color: 'var(--id-text)' }}>{label || t('loading.default')}</div>
      {sub && <div className="cmp-meta">{sub}</div>}
    </div>
  );
};

// Convenience: register a "coming soon" handler for a given tool id.
window.comingSoon = function (id) {
  window.TOOL_HANDLERS[id] = (props) => <window.ComingSoonTool {...props} />;
};

window.ComingSoonTool = function ComingSoonTool({ tool, cat }) {
  const [, t] = window.useI18n();
  const toolName = (window.tTool(tool, 'name') || tool.name).toLowerCase();
  const toolDesc = window.tTool(tool, 'desc') || tool.desc;
  const demos = ['compress-image','resize-image','rotate-image','meme-generator','qr-generator','password-gen','word-counter','file-hash','md-to-html']
    .map((id) => { const x = (window.TOOLS || []).find((q) => q.id === id); return x ? window.tTool(x, 'name') : null; })
    .filter(Boolean)
    .join(', ');
  return (
    <div className="coming-soon">
      <div className="cs-icon" style={{ background: cat.soft, color: cat.tint }}>
        <window.Icon name={tool.icon} size={36} strokeWidth={1.5} />
      </div>
      <h3>{t('coming_soon.heading', { tool: toolName })}</h3>
      <p>{toolDesc}{t('paywall.desc_suffix')} {t('coming_soon.body')}</p>
      <div className="cs-hint">
        <window.Icon name="sparkle" size={14} /> {t('coming_soon.hint_prefix')} {demos}
      </div>
    </div>
  );
};
