import { PDFDocument } from '@cantoo/pdf-lib';
import type { PageViewport, PDFDocumentProxy } from 'pdfjs-dist';
import { h } from '../../core/dom';
import { loadPdf, renderPage } from '../../core/pdf';
import { baseName, bufferOf } from '../../core/util';
import { field, numberInput, segmented } from '../../ui/controls';
import { toast } from '../../ui/toast';
import { deliver, featureModal, footerButtons, type LoadedFile, pdfSource } from '../common';
import { registerFeature } from '../registry';

const PREVIEW_MAX_W = 460;
const PREVIEW_MAX_H = 320;
const MIN_PAGE_SIZE = 10;

/** Crop rect in PDF user space for a page box, mirroring the export math. */
function cropRect(
  box: { x: number; y: number; width: number; height: number },
  insets: { top: number; right: number; bottom: number; left: number },
  unit: 'pt' | 'pct',
): { x: number; y: number; width: number; height: number } {
  const insetTop = unit === 'pct' ? (insets.top / 100) * box.height : insets.top;
  const insetBottom = unit === 'pct' ? (insets.bottom / 100) * box.height : insets.bottom;
  const insetLeft = unit === 'pct' ? (insets.left / 100) * box.width : insets.left;
  const insetRight = unit === 'pct' ? (insets.right / 100) * box.width : insets.right;

  let width = box.width - insetLeft - insetRight;
  let height = box.height - insetTop - insetBottom;
  // Clamp so the page never collapses below a usable size.
  if (width < MIN_PAGE_SIZE) width = MIN_PAGE_SIZE;
  if (height < MIN_PAGE_SIZE) height = MIN_PAGE_SIZE;

  // PDF origin is bottom-left: bottom margin raises y, top margin lowers height.
  return { x: box.x + insetLeft, y: box.y + insetBottom, width, height };
}

registerFeature({
  id: 'crop-pages',
  title: 'Crop margins',
  desc: 'Trim even margins off every page.',
  icon: 'crop',
  category: 'organize',
  run(ctx) {
    let file: LoadedFile | null = null;
    const insets = { top: 0, right: 0, bottom: 0, left: 0 };
    let unit: 'pt' | 'pct' = 'pt';
    let mode: 'soft' | 'true' = 'soft';

    const m = featureModal('Crop margins', 'Trim even margins off every page — nothing is uploaded.', 'crop', 560);

    // --- Live preview -----------------------------------------------------
    let doc: PDFDocumentProxy | null = null;
    let pageNum = 1;
    let renderToken = 0;
    let pageView: number[] | null = null;
    let viewport: PageViewport | null = null;

    const canvas = h('canvas.crop-preview-canvas') as HTMLCanvasElement;
    const mask = h('div.crop-preview-mask');
    const stage = h('div.crop-preview-stage', canvas, mask);
    const pageLabel = h('span.crop-preview-label');
    const prevBtn = h('button.crop-page-btn', { type: 'button', onclick: () => { void showPage(pageNum - 1); } }, '‹') as HTMLButtonElement;
    const nextBtn = h('button.crop-page-btn', { type: 'button', onclick: () => { void showPage(pageNum + 1); } }, '›') as HTMLButtonElement;
    const previewWrap = h('div.crop-preview', stage, h('div.crop-preview-pager', prevBtn, pageLabel, nextBtn));
    previewWrap.hidden = true;

    // The mask shades everything outside the kept region, so the overlay
    // shows exactly what the export math will keep — clamps included.
    function updateMask() {
      if (!pageView || !viewport) return;
      const [x0, y0, x1, y1] = pageView;
      const rect = cropRect({ x: x0, y: y0, width: x1 - x0, height: y1 - y0 }, insets, unit);
      // Map both corners through the viewport so page /Rotate is honoured.
      const [ax, ay] = viewport.convertToViewportPoint(rect.x, rect.y);
      const [bx, by] = viewport.convertToViewportPoint(rect.x + rect.width, rect.y + rect.height);
      mask.style.left = `${Math.min(ax, bx)}px`;
      mask.style.top = `${Math.min(ay, by)}px`;
      mask.style.width = `${Math.abs(bx - ax)}px`;
      mask.style.height = `${Math.abs(by - ay)}px`;
    }

    async function showPage(n: number) {
      if (!doc) return;
      pageNum = Math.min(Math.max(1, n), doc.numPages);
      const token = ++renderToken;
      const page = await doc.getPage(pageNum);
      if (token !== renderToken) return;
      const base = page.getViewport({ scale: 1 });
      const scale = Math.min(PREVIEW_MAX_W / base.width, PREVIEW_MAX_H / base.height);
      await renderPage(page, scale, canvas);
      if (token !== renderToken) return;
      pageView = page.view;
      viewport = page.getViewport({ scale });
      pageLabel.textContent = `Page ${pageNum} / ${doc.numPages}`;
      prevBtn.disabled = pageNum <= 1;
      nextBtn.disabled = pageNum >= doc.numPages;
      previewWrap.hidden = false;
      updateMask();
    }

    m.onClose(() => {
      renderToken++;
      void doc?.loadingTask.destroy();
      doc = null;
    });

    // --- Controls ---------------------------------------------------------
    // Percent insets are clamped to 0–100; point insets stay unbounded.
    const marginInputs = {} as Record<keyof typeof insets, HTMLInputElement>;
    const marginInput = (side: keyof typeof insets) => {
      const el = numberInput({
        value: insets[side],
        min: 0,
        step: 1,
        onchange: (v) => {
          let val = v || 0;
          if (unit === 'pct' && val > 100) {
            val = 100;
            el.value = '100';
          }
          insets[side] = val;
          updateMask();
        },
      });
      marginInputs[side] = el;
      return el;
    };

    m.body.append(
      pdfSource(ctx, {
        multiple: false,
        onChange: (f) => {
          file = f[0] ?? null;
          primary.disabled = !file;
          const token = ++renderToken;
          void doc?.loadingTask.destroy();
          doc = null;
          pageView = null;
          viewport = null;
          previewWrap.hidden = true;
          if (!file) return;
          // Preview is best-effort: if pdf.js can't render it, cropping still works.
          void loadPdf(file.bytes)
            .then((d) => {
              if (token !== renderToken) return void d.loadingTask.destroy();
              doc = d;
              return showPage(1);
            })
            .catch(() => {});
        },
      }),
      previewWrap,
      field('Crop type', segmented({
        options: [
          { value: 'soft', label: 'Soft crop (reversible)' },
          { value: 'true', label: 'True crop (for printing)' },
        ],
        value: mode,
        onchange: (v) => { mode = v as 'soft' | 'true'; },
      }), 'Soft crop hides the margins but keeps the full page underneath. True crop resizes the page itself so printing and booklet layouts center correctly.'),
      field('Units', segmented({
        options: [
          { value: 'pt', label: 'Points' },
          { value: 'pct', label: 'Percent' },
        ],
        value: unit,
        onchange: (v) => {
          unit = v as 'pt' | 'pct';
          for (const side of Object.keys(marginInputs) as (keyof typeof insets)[]) {
            const el = marginInputs[side];
            if (unit === 'pct') {
              el.max = '100';
              if (insets[side] > 100) {
                insets[side] = 100;
                el.value = '100';
              }
            } else {
              el.removeAttribute('max');
            }
          }
          updateMask();
        },
      }), 'Percent trims a fraction of each side’s length.'),
      h('div.grid-2',
        field('Top', marginInput('top')),
        field('Right', marginInput('right')),
        field('Bottom', marginInput('bottom')),
        field('Left', marginInput('left')),
      ),
    );

    const { primary } = footerButtons(m, 'Crop & download', async () => {
      if (!file) return;
      m.setBusy(true, 'Cropping…');
      try {
        const pdfDoc = await PDFDocument.load(bufferOf(file.bytes), { ignoreEncryption: true });
        for (const page of pdfDoc.getPages()) {
          const rect = cropRect(page.getCropBox(), insets, unit);
          page.setCropBox(rect.x, rect.y, rect.width, rect.height);
          if (mode === 'true') {
            // Set every page box to the same rect so print drivers and
            // imposition tools center on the cropped page, and no viewer
            // sees a CropBox outside the MediaBox.
            page.setMediaBox(rect.x, rect.y, rect.width, rect.height);
            page.setBleedBox(rect.x, rect.y, rect.width, rect.height);
            page.setTrimBox(rect.x, rect.y, rect.width, rect.height);
            page.setArtBox(rect.x, rect.y, rect.width, rect.height);
          }
        }
        const bytes = await pdfDoc.save({ useObjectStreams: true });
        m.close();
        deliver(bytes, `${baseName(file.name)}-cropped.pdf`);
      } catch (e) {
        m.setBusy(false);
        toast('Crop failed — the file may be damaged or encrypted.', { type: 'error' });
        console.error(e);
      }
    }, 'crop');
    primary.disabled = true;
  },
});
