import { PDFDocument } from '@cantoo/pdf-lib';
import { registerFeature } from '../registry';
import { featureModal, pdfSource, footerButtons, deliver, type LoadedFile } from '../common';
import { bufferOf, formatBytes, suffixName, readBytes } from '../../core/util';
import { loadPdf, renderPageToBlob } from '../../core/pdf';
import { field, select, slider, segmented, resultStat } from '../../ui/controls';
import { toast } from '../../ui/toast';
import { h } from '../../core/dom';

type Mode = 'structure' | 'quality';

registerFeature({
  id: 'compress',
  title: 'Compress PDF',
  desc: 'Shrink file size — structure-only or by reducing image quality.',
  icon: 'compress',
  category: 'optimize',
  run(ctx) {
    let file: LoadedFile | null = null;
    let mode: Mode = 'structure';
    let scale = 1.5;
    let quality = 65;

    const m = featureModal('Compress PDF', 'Make your PDF smaller — nothing leaves your device.', 'compress', 560);

    const qualityOptions = h('div.stack', { style: { display: 'none' } });
    qualityOptions.append(
      field(
        'Rendering resolution',
        select({
          options: [
            { value: '2', label: 'High — 2× (sharpest, largest)' },
            { value: '1.5', label: 'Medium — 1.5× (balanced)' },
            { value: '1', label: 'Low — 1× (smallest)' },
          ],
          value: '1.5',
          onchange: (v) => { scale = parseFloat(v); },
        }),
        'Higher resolution keeps text crisper but makes the file larger.',
      ),
      field(
        'JPEG quality',
        slider({ value: quality, min: 40, max: 90, step: 1, onchange: (v) => { quality = v; } }),
        'Lower quality means a smaller file. Note: quality mode rasterizes pages, so text becomes an image.',
      ),
    );

    const modeStrip = segmented({
      options: [
        { value: 'structure', label: 'Structure (lossless)' },
        { value: 'quality', label: 'Reduce image quality' },
      ],
      value: mode,
      onchange: (v) => {
        mode = v as Mode;
        qualityOptions.style.display = mode === 'quality' ? '' : 'none';
        structureNote.style.display = mode === 'structure' ? '' : 'none';
      },
    });

    const structureNote = h('p.hint', 'Rebuilds the PDF structure and recompresses streams — content stays identical.');

    const stats = h('div.stack');

    m.body.append(
      pdfSource(ctx, {
        multiple: false,
        onChange: (fs) => { file = fs[0] ?? null; primary.disabled = !file; },
      }),
      field('Method', modeStrip),
      structureNote,
      qualityOptions,
      stats,
    );

    const { primary } = footerButtons(m, 'Compress', async () => {
      if (!file) return;
      const src = file;
      const before = src.bytes.length;

      let out: Uint8Array;
      try {
        if (mode === 'structure') {
          m.setBusy(true, 'Optimizing structure…');
          const { optimizeStructure } = await import('../../core/qpdf');
          const res = await optimizeStructure(src.bytes);
          if (!res.ok || !res.output) {
            m.setBusy(false);
            toast('Compression failed — ' + (res.log.trim() || 'the file may be encrypted or damaged.'), { type: 'error' });
            return;
          }
          out = res.output;
          m.setBusy(false);
        } else {
          out = await rasterize(src.bytes, scale, quality);
        }
      } catch (e) {
        m.setBusy(false);
        toast('Compression failed — the file may be damaged.', { type: 'error' });
        console.error(e);
        return;
      }

      const after = out.length;
      showResult(before, after, out, src.name);
    }, 'compress');
    primary.disabled = true;

    function showResult(before: number, after: number, out: Uint8Array, name: string) {
      const smaller = after < before;
      const pct = before > 0 ? Math.round((1 - after / before) * 100) : 0;

      const children: Node[] = [
        h('div.result-stats',
          resultStat('Original', formatBytes(before)),
          resultStat('Compressed', formatBytes(after), smaller ? 'ok' : 'ink'),
          resultStat(smaller ? 'Saved' : 'Change', (smaller ? '' : '+') + Math.abs(pct) + '%', smaller ? 'ok' : 'ink'),
        ),
      ];
      if (!smaller) {
        children.push(h('p.hint', mode === 'quality'
          ? 'This PDF didn’t get smaller — its pages may already be compact, or the resolution/quality is too high. Try a lower setting, or download it anyway.'
          : 'This PDF is already well-optimized — the structure pass couldn’t shrink it further. You can still download the rebuilt copy.'));
      }
      stats.replaceChildren(...children);

      // Switch the primary action to download the produced bytes.
      const fresh = primary.cloneNode(false) as HTMLButtonElement;
      fresh.replaceChildren('Download');
      fresh.onclick = () => {
        m.close();
        deliver(out, suffixName(name, '-compressed'));
      };
      primary.replaceWith(fresh);
    }

    async function rasterize(bytes: Uint8Array, renderScale: number, jpegQuality: number): Promise<Uint8Array> {
      const t = toast('Rasterizing pages…', { type: 'progress' });
      try {
        const doc = await loadPdf(bytes);
        const total = doc.numPages;
        const outDoc = await PDFDocument.create();

        for (let i = 0; i < total; i++) {
          t.update(`Rasterizing page ${i + 1} of ${total}…`);
          const page = await doc.getPage(i + 1);
          const base = page.getViewport({ scale: 1 });
          const blob = await renderPageToBlob(page, renderScale, 'image/jpeg', jpegQuality / 100);
          const imgBytes = await readBytes(blob);
          const jpg = await outDoc.embedJpg(bufferOf(imgBytes));
          const newPage = outDoc.addPage([base.width, base.height]);
          newPage.drawImage(jpg, { x: 0, y: 0, width: base.width, height: base.height });
          t.progress((i + 1) / total);
        }

        const saved = await outDoc.save({ useObjectStreams: true });
        t.close();
        return saved;
      } catch (e) {
        t.close();
        throw e;
      }
    }
  },
});
