import { PDFDocument, StandardFonts, rgb, degrees } from '@cantoo/pdf-lib';
import { registerFeature } from '../registry';
import { featureModal, pdfSource, footerButtons, deliver, type LoadedFile } from '../common';
import { field, textInput, numberInput, colorSwatches, slider, checkbox, segmented } from '../../ui/controls';
import { dropzone } from '../../ui/dropzone';
import { toast } from '../../ui/toast';
import { bufferOf, baseName, readBytes } from '../../core/util';
import { h } from '../../core/dom';

interface TextOpts {
  text: string;
  size: number;
  color: string;
  opacity: number;
  rotation: number;
  tile: boolean;
}

interface ImageOpts {
  file: LoadedFile | null;
  opacity: number;
  scale: number; // percent of page width
  tile: boolean;
}

/** Convert a #rrggbb hex string to a pdf-lib rgb color (0..1). */
function hexToRgb(hex: string): { r: number; g: number; b: number } {
  const m = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
  if (!m) return { r: 0.878, g: 0.282, b: 0.239 }; // fallback: #e0483d
  const n = parseInt(m[1], 16);
  return { r: ((n >> 16) & 255) / 255, g: ((n >> 8) & 255) / 255, b: (n & 255) / 255 };
}

/** Best-effort MIME type from a filename extension (createImageBitmap sniffs regardless). */
function mimeFromName(name: string): string {
  const ext = (name.match(/\.(\w+)$/)?.[1] ?? '').toLowerCase();
  switch (ext) {
    case 'jpg': case 'jpeg': return 'image/jpeg';
    case 'gif': return 'image/gif';
    case 'webp': return 'image/webp';
    case 'bmp': return 'image/bmp';
    default: return 'image/png';
  }
}

/** Decode any raster image into PNG bytes via canvas so pdf-lib can embed it. */
async function decodeToPng(bytes: Uint8Array, type: string): Promise<Uint8Array> {
  const bmp = await createImageBitmap(new Blob([bufferOf(bytes)], { type: type || 'image/png' }));
  const canvas = document.createElement('canvas');
  canvas.width = bmp.width;
  canvas.height = bmp.height;
  const cx = canvas.getContext('2d');
  if (!cx) throw new Error('Canvas not available');
  cx.drawImage(bmp, 0, 0);
  bmp.close();
  const blob = await new Promise<Blob | null>((resolve) => canvas.toBlob(resolve, 'image/png'));
  if (!blob) throw new Error('Could not decode image');
  return readBytes(blob);
}

registerFeature({
  id: 'watermark',
  title: 'Add watermark',
  desc: 'Overlay text or an image across pages.',
  icon: 'watermark',
  category: 'organize',
  run(ctx) {
    let files: LoadedFile[] = [];
    let mode: 'text' | 'image' = 'text';

    const textOpts: TextOpts = {
      text: 'CONFIDENTIAL',
      size: 48,
      color: '#e0483d',
      opacity: 25,
      rotation: 45,
      tile: false,
    };
    const imageOpts: ImageOpts = { file: null, opacity: 25, scale: 40, tile: false };

    const m = featureModal('Add watermark', 'Overlay text or an image on every page — all in your browser.', 'watermark', 560);

    // ---- Text controls ----
    const textPane = h('div.wm-pane',
      field('Watermark text', textInput({ value: textOpts.text, oninput: (v) => { textOpts.text = v; } })),
      field('Font size (pt)', numberInput({ value: textOpts.size, min: 6, max: 400, onchange: (v) => { if (!Number.isNaN(v)) textOpts.size = v; } })),
      field('Color', colorSwatches({
        colors: ['#e0483d', '#111827', '#2563eb', '#16a34a', '#f59e0b'],
        value: textOpts.color,
        allowCustom: true,
        onpick: (c) => { textOpts.color = c; },
      })),
      field('Opacity', slider({ value: textOpts.opacity, min: 5, max: 100, onchange: (v) => { textOpts.opacity = v; } }), 'Lower is more transparent.'),
      field('Rotation', slider({ value: textOpts.rotation, min: -90, max: 90, onchange: (v) => { textOpts.rotation = v; } })),
      checkbox('Tile across page', textOpts.tile, (v) => { textOpts.tile = v; }),
    );

    // ---- Image controls ----
    const imgName = h('p.hint', 'No image chosen yet.');
    const setImage = async (fs: File[]) => {
      const f = fs[0];
      if (!f) return;
      const bytes = await readBytes(f);
      imageOpts.file = { name: f.name, bytes };
      imgName.textContent = `Selected: ${f.name}`;
      updatePrimary();
    };
    const imgDrop = dropzone({
      accept: 'image/*',
      multiple: false,
      compact: true,
      label: 'Drop an image or click to browse',
      onFiles: (fs) => { void setImage(fs); },
    });
    const imagePane = h('div.wm-pane',
      field('Watermark image', h('div', imgDrop, imgName)),
      field('Opacity', slider({ value: imageOpts.opacity, min: 5, max: 100, onchange: (v) => { imageOpts.opacity = v; } })),
      field('Size (% of page width)', slider({ value: imageOpts.scale, min: 10, max: 100, onchange: (v) => { imageOpts.scale = v; } })),
      checkbox('Tile across page', imageOpts.tile, (v) => { imageOpts.tile = v; }),
    );
    imagePane.style.display = 'none';

    const seg = segmented({
      options: [{ value: 'text', label: 'Text' }, { value: 'image', label: 'Image' }],
      value: mode,
      onchange: (v) => {
        mode = v as 'text' | 'image';
        textPane.style.display = mode === 'text' ? '' : 'none';
        imagePane.style.display = mode === 'image' ? '' : 'none';
        updatePrimary();
      },
    });

    m.body.append(
      pdfSource(ctx, { multiple: false, onChange: (f) => { files = f; updatePrimary(); } }),
      h('div.wm-type', field('Watermark type', seg)),
      textPane,
      imagePane,
    );

    const { primary } = footerButtons(m, 'Apply & download', async () => {
      const file = files[0];
      if (!file) return;
      if (mode === 'image' && !imageOpts.file) return;
      m.setBusy(true, 'Applying watermark…');
      try {
        const doc = await PDFDocument.load(bufferOf(file.bytes), { ignoreEncryption: true });
        const pages = doc.getPages();

        if (mode === 'text') {
          const font = await doc.embedFont(StandardFonts.Helvetica);
          const c = hexToRgb(textOpts.color);
          const color = rgb(c.r, c.g, c.b);
          const op = textOpts.opacity / 100;
          const rot = textOpts.rotation;
          const size = textOpts.size;
          const text = textOpts.text || ' ';
          const textW = font.widthOfTextAtSize(text, size);
          const textH = font.heightAtSize(size);

          for (const page of pages) {
            const { width, height } = page.getSize();
            if (textOpts.tile) {
              const stepX = Math.max(textW + size, 40) * 1.6;
              const stepY = Math.max(textH + size, 40) * 2.4;
              for (let y = 0; y < height + stepY; y += stepY) {
                for (let x = 0; x < width + stepX; x += stepX) {
                  page.drawText(text, {
                    x, y, size, font, color, opacity: op, rotate: degrees(rot),
                  });
                }
              }
            } else {
              page.drawText(text, {
                x: (width - textW) / 2,
                y: (height - textH) / 2,
                size, font, color, opacity: op, rotate: degrees(rot),
              });
            }
          }
        } else {
          const src = imageOpts.file!;
          const png = await decodeToPng(src.bytes, mimeFromName(src.name));
          const img = await doc.embedPng(png);
          const op = imageOpts.opacity / 100;

          for (const page of pages) {
            const { width, height } = page.getSize();
            const drawW = (width * imageOpts.scale) / 100;
            const drawH = drawW * (img.height / img.width);
            if (imageOpts.tile) {
              const stepX = drawW * 1.15;
              const stepY = drawH * 1.15;
              for (let y = 0; y < height; y += stepY) {
                for (let x = 0; x < width; x += stepX) {
                  page.drawImage(img, { x, y, width: drawW, height: drawH, opacity: op });
                }
              }
            } else {
              page.drawImage(img, {
                x: (width - drawW) / 2,
                y: (height - drawH) / 2,
                width: drawW, height: drawH, opacity: op,
              });
            }
          }
        }

        const bytes = await doc.save({ useObjectStreams: true });
        m.close();
        deliver(bytes, baseName(file.name) + '-watermarked.pdf');
      } catch (e) {
        m.setBusy(false);
        toast('Could not apply the watermark — the file or image may be damaged.', { type: 'error' });
        console.error(e);
      }
    }, 'watermark');

    function updatePrimary() {
      const hasPdf = files.length > 0;
      const ok = hasPdf && (mode === 'text' || !!imageOpts.file);
      primary.disabled = !ok;
    }
    updatePrimary();
  },
});
