import { diffWords } from 'diff';
import pixelmatch from 'pixelmatch';
import { registerFeature } from '../registry';
import { featureModal } from '../common';
import { dropzone } from '../../ui/dropzone';
import { icon } from '../../ui/icons';
import { loadPdf, renderPage, getPageText, type PDFDocumentProxy } from '../../core/pdf';
import { readBytes, isProbablyPdf, formatBytes } from '../../core/util';
import { toast } from '../../ui/toast';
import { h } from '../../core/dom';

interface Slot {
  name: string;
  bytes: Uint8Array;
}

registerFeature({
  id: 'compare',
  title: 'Compare two PDFs',
  desc: 'See what changed between two versions.',
  icon: 'compare',
  category: 'security',
  run() {
    const m = featureModal('Compare two PDFs', 'Spot what changed — both files stay on your device.', 'compare', 640);

    let original: Slot | null = null;
    let revised: Slot | null = null;

    // Concatenate all page text for a document.
    const allText = async (doc: PDFDocumentProxy): Promise<string> => {
      const parts: string[] = [];
      for (let i = 0; i < doc.numPages; i++) {
        const page = await doc.getPage(i + 1);
        parts.push(await getPageText(page));
      }
      return parts.join('\n');
    };

    // Render page 1 to a fresh canvas and return its 2D ImageData.
    const renderFirstPage = async (bytes: Uint8Array): Promise<ImageData> => {
      const doc = await loadPdf(bytes);
      const page = await doc.getPage(1);
      const canvas = document.createElement('canvas');
      await renderPage(page, 1.5, canvas, { dpr: 1 });
      const ctx = canvas.getContext('2d', { alpha: false })!;
      const data = ctx.getImageData(0, 0, canvas.width, canvas.height);
      void doc.cleanup();
      return data;
    };

    const summary = h('div.compare-summary');
    const textReport = h('div.compare-text', {
      style: {
        maxHeight: '260px', overflowY: 'auto', padding: '12px',
        border: '1px solid var(--border, rgba(0,0,0,.12))', borderRadius: '10px',
        whiteSpace: 'pre-wrap', wordBreak: 'break-word', lineHeight: '1.5',
        fontSize: '13px', background: 'var(--surface, rgba(255,255,255,.4))',
      },
    });
    const pixelWrap = h('div.compare-pixel', { style: { marginTop: '14px' } });
    const results = h('div.compare-results', { style: { display: 'none', marginTop: '14px' } },
      summary,
      h('div', { style: { marginTop: '10px', fontWeight: '600', fontSize: '13px' } }, 'Text differences'),
      textReport,
      pixelWrap,
    );

    // Build a single labeled drop slot.
    const makeSlot = (label: string, onSet: (s: Slot | null) => void): HTMLElement => {
      const status = h('div.compare-slot-status', { style: { marginTop: '8px', fontSize: '12px', color: 'var(--muted, #666)' } }, 'No file yet.');
      const dz = dropzone({
        accept: 'application/pdf,.pdf',
        multiple: false,
        compact: true,
        label: `Drop the ${label.toLowerCase()} PDF`,
        onFiles: async (fs) => {
          const f = fs[0];
          if (!f) return;
          const bytes = await readBytes(f);
          if (!isProbablyPdf(bytes)) {
            toast(`${f.name} isn’t a PDF.`, { type: 'error' });
            return;
          }
          onSet({ name: f.name, bytes });
          status.replaceChildren(
            h('span.source-file-icon', { style: { marginRight: '6px' } }, icon('file', 14)),
            `${f.name} · ${formatBytes(bytes.length)}`,
          );
        },
      });
      return h('div.compare-slot', { style: { flex: '1 1 0', minWidth: '0' } },
        h('div', { style: { fontWeight: '600', fontSize: '13px', marginBottom: '6px' } }, label),
        dz,
        status,
      );
    };

    const compareBtn = h('button.btn btn-primary', { onclick: () => runCompare() }, icon('compare', 16), 'Compare') as HTMLButtonElement;
    compareBtn.disabled = true;
    const refreshBtn = () => { compareBtn.disabled = !(original && revised); };

    m.body.append(
      h('div.compare-slots', { style: { display: 'flex', gap: '14px', alignItems: 'flex-start' } },
        makeSlot('Original', (s) => { original = s; refreshBtn(); }),
        makeSlot('Revised', (s) => { revised = s; refreshBtn(); }),
      ),
      h('p.hint', { style: { marginTop: '10px' } }, 'Both files are read in your browser. Nothing is uploaded.'),
      results,
    );

    m.footer.append(
      h('button.btn btn-soft', { onclick: () => m.close() }, 'Close'),
      compareBtn,
    );

    async function runCompare() {
      if (!original || !revised) return;
      const orig = original;
      const rev = revised;
      const t = toast('Comparing…', { type: 'progress' });
      m.setBusy(true, 'Comparing…');
      try {
        // 1) Text diff over all pages.
        t.update('Reading text…');
        t.progress(0.1);
        const docA = await loadPdf(orig.bytes);
        const textA = await allText(docA);
        void docA.cleanup();
        t.progress(0.4);
        const docB = await loadPdf(rev.bytes);
        const textB = await allText(docB);
        void docB.cleanup();
        t.progress(0.6);

        t.update('Diffing…');
        const parts = diffWords(textA, textB);
        let additions = 0;
        let deletions = 0;
        textReport.replaceChildren();
        for (const part of parts) {
          if (part.added) {
            additions++;
            textReport.appendChild(h('span', {
              style: { background: 'rgba(46,160,67,.22)', borderRadius: '3px', padding: '0 1px' },
            }, part.value));
          } else if (part.removed) {
            deletions++;
            textReport.appendChild(h('span', {
              style: { color: 'var(--danger, #d1242f)', textDecoration: 'line-through', opacity: '.85' },
            }, part.value));
          } else {
            textReport.appendChild(document.createTextNode(part.value));
          }
        }
        if (!parts.length || (!additions && !deletions)) {
          textReport.appendChild(h('span', { style: { color: 'var(--muted, #666)' } }, 'No text differences found.'));
        }
        summary.replaceChildren(
          h('span', { style: { color: 'var(--ok, #2ea043)', fontWeight: '600' } }, `${additions} addition${additions === 1 ? '' : 's'}`),
          document.createTextNode('  ·  '),
          h('span', { style: { color: 'var(--danger, #d1242f)', fontWeight: '600' } }, `${deletions} deletion${deletions === 1 ? '' : 's'}`),
        );

        // 2) Pixel diff of page 1.
        t.update('Rendering page 1…');
        t.progress(0.75);
        pixelWrap.replaceChildren();
        try {
          const a = await renderFirstPage(orig.bytes);
          const b = await renderFirstPage(rev.bytes);
          const w = Math.min(a.width, b.width);
          const hgt = Math.min(a.height, b.height);
          if (w > 0 && hgt > 0) {
            // Crop both to the common region so pixelmatch gets equal sizes.
            const cropA = cropImageData(a, w, hgt);
            const cropB = cropImageData(b, w, hgt);
            const out = new ImageData(w, hgt);
            pixelmatch(cropA.data, cropB.data, out.data, w, hgt, { threshold: 0.1 });
            const outCanvas = h('canvas', { width: w, height: hgt, style: { maxWidth: '100%', height: 'auto', border: '1px solid var(--border, rgba(0,0,0,.12))', borderRadius: '8px', display: 'block' } }) as HTMLCanvasElement;
            outCanvas.getContext('2d')!.putImageData(out, 0, 0);
            pixelWrap.append(
              h('div', { style: { fontWeight: '600', fontSize: '13px', margin: '4px 0 8px' } }, 'Page 1 pixel diff'),
              outCanvas,
            );
          }
        } catch (pe) {
          console.error(pe);
          pixelWrap.append(
            h('div', { style: { fontWeight: '600', fontSize: '13px', margin: '4px 0 8px' } }, 'Page 1 pixel diff'),
            h('p.hint', 'Couldn’t overlay page 1 (the pages may differ in size).'),
          );
        }

        t.progress(1);
        results.style.display = '';
      } catch (e) {
        console.error(e);
        toast('Compare failed — one of the files may be damaged.', { type: 'error' });
      } finally {
        m.setBusy(false);
        t.close();
      }
    }
  },
});

/** Copy the top-left w×h region of an ImageData into a fresh ImageData. */
function cropImageData(src: ImageData, w: number, h: number): ImageData {
  if (src.width === w && src.height === h) return src;
  const out = new ImageData(w, h);
  for (let y = 0; y < h; y++) {
    const srcStart = y * src.width * 4;
    const dstStart = y * w * 4;
    out.data.set(src.data.subarray(srcStart, srcStart + w * 4), dstStart);
  }
  return out;
}
