import * as fabric from 'fabric';
import { PDFDocument } from 'pdf-lib';

import { CANVAS_HEIGHT, CANVAS_WIDTH } from './schema';
import { FabricRenderOptions, renderFabricLayout } from './DesignerCanvas';

const PDF_PAGE_SIZES: Record<string, { width: number; height: number }> = {
  a4: { width: 595.28, height: 841.89 },
  a5: { width: 419.53, height: 595.28 },
  letter: { width: 612, height: 792 },
};

export async function withFabricCanvas<T>(
  options: FabricRenderOptions,
  handler: (canvas: fabric.Canvas, element: HTMLCanvasElement) => Promise<T>,
): Promise<T> {
  const canvasElement = document.createElement('canvas');
  canvasElement.width = CANVAS_WIDTH;
  canvasElement.height = CANVAS_HEIGHT;

  const canvas = new fabric.Canvas(canvasElement, {
    selection: false,
  });

  try {
    await renderFabricLayout(canvas, {
      ...options,
      readOnly: true,
    });
    return await handler(canvas, canvasElement);
  } finally {
    canvas.dispose();
    canvasElement.remove();
  }
}

export async function generatePngDataUrl(
  options: FabricRenderOptions,
  multiplier = 2,
): Promise<string> {
  return withFabricCanvas(options, async (canvas) =>
    canvas.toDataURL({ format: 'png', multiplier }),
  );
}

export async function generatePdfBytes(
  options: FabricRenderOptions,
  paper: string,
  orientation: string,
  multiplier = 2,
): Promise<Uint8Array> {
  const dataUrl = await generatePngDataUrl(options, multiplier);
  return createPdfFromPng(dataUrl, paper, orientation);
}

export async function createPdfFromPng(
  dataUrl: string,
  paper: string,
  orientation: string,
): Promise<Uint8Array> {
  const pdfDoc = await PDFDocument.create();
  const baseSize = PDF_PAGE_SIZES[paper.toLowerCase()] ?? PDF_PAGE_SIZES.a4;
  const landscape = orientation === 'landscape';
  const pageWidth = landscape ? baseSize.height : baseSize.width;
  const pageHeight = landscape ? baseSize.width : baseSize.height;

  const page = pdfDoc.addPage([pageWidth, pageHeight]);
  const pngBytes = dataUrlToUint8Array(dataUrl);
  const pngImage = await pdfDoc.embedPng(pngBytes);

  const imageWidth = pngImage.width;
  const imageHeight = pngImage.height;
  const scale = Math.min(pageWidth / imageWidth, pageHeight / imageHeight);
  const drawWidth = imageWidth * scale;
  const drawHeight = imageHeight * scale;

  page.drawImage(pngImage, {
    x: (pageWidth - drawWidth) / 2,
    y: (pageHeight - drawHeight) / 2,
    width: drawWidth,
    height: drawHeight,
  });

  return pdfDoc.save();
}

export function triggerDownloadFromDataUrl(dataUrl: string, filename: string): Promise<void> {
  return fetch(dataUrl)
    .then((response) => response.blob())
    .then((blob) => triggerDownloadFromBlob(blob, filename));
}

export function triggerDownloadFromBlob(blob: Blob, filename: string): void {
  const objectUrl = URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = objectUrl;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  setTimeout(() => URL.revokeObjectURL(objectUrl), 1000);
}

export async function openPdfInNewTab(pdfBytes: Uint8Array): Promise<void> {
  const arrayBuffer = pdfBytes.buffer.slice(pdfBytes.byteOffset, pdfBytes.byteOffset + pdfBytes.byteLength) as ArrayBuffer;
  const blob = new Blob([arrayBuffer], { type: 'application/pdf' });
  const blobUrl = URL.createObjectURL(blob);
  const printWindow = window.open(blobUrl, '_blank', 'noopener,noreferrer');

  if (!printWindow) {
    URL.revokeObjectURL(blobUrl);
    throw new Error('window-blocked');
  }

  printWindow.onload = () => {
    try {
      printWindow.focus();
      printWindow.print();
    } catch (error) {
      console.error('[FabricExport] Browser print failed', error);
    }
  };

  setTimeout(() => URL.revokeObjectURL(blobUrl), 60_000);
}

function dataUrlToUint8Array(dataUrl: string): Uint8Array {
  const [, base64] = dataUrl.split(',');
  const decoded = atob(base64 ?? '');
  const bytes = new Uint8Array(decoded.length);
  for (let index = 0; index < decoded.length; index += 1) {
    bytes[index] = decoded.charCodeAt(index);
  }
  return bytes;
}
