import type { jsPDF } from "jspdf";
export interface PDFGenerationBaseOptions {
  margin?: [number, number, number, number];
  filename: string;
  pageFormat?: "A4" | "letter" | [number, number];
  orientation?: "portrait" | "landscape";
  footerHeight?: number;
  headerHeight?: number;
  headerAndFooterImageWidth?: number;
  image?: {
    type?: "jpeg" | "png";
    quality?: number;
  };
  html2canvas?: {
    scale?: number;
    backgroundColor?: string | null;
  };
  jsPDF?: {
    unit?: "pt" | "mm" | "cm" | "in";
    format?: "A4" | "letter" | [number, number];
    orientation?: "portrait" | "landscape";
  };
  pagebreak?: {
    mode?: Array<"css" | "avoid-all" | "avoid" | "always">;
  };
}
export interface PDFGenerationBaseOptions {
  margin?: [number, number, number, number]; // [top, left, bottom, right]
  filename: string;
  pageFormat?: "A4" | "letter" | [number, number];
  orientation?: "portrait" | "landscape";
  footerHeight?: number;
  headerHeight?: number;
  image?: {
    type?: "jpeg" | "png";
    quality?: number;
  };
  html2canvas?: {
    scale?: number;
    backgroundColor?: string | null;
  };
  jsPDF?: {
    unit?: "pt" | "mm" | "cm" | "in";
    format?: "A4" | "letter" | [number, number];
    orientation?: "portrait" | "landscape";
  };
  pagebreak?: {
    mode?: Array<"css" | "avoid-all" | "avoid" | "always">;
  };
}

export interface PDFContent {
  mainContent: string;
  header: string;
  footer: string;
  seal?: string; // ✅ mark as optional
  additionalContent?: string;
  receivingDetails?: string;
  ccDetails?: string;
  bestRegardsText?: string;
}

class PDFGenerationService {
  async generatePDFBuffer(
    content: PDFContent,
    options: PDFGenerationBaseOptions
  ): Promise<Blob> {
    function mergeOptions(
      options: PDFGenerationBaseOptions
    ): Required<PDFGenerationBaseOptions> {
      return {
        ...defaultOptions,
        ...options,
        image: {
          ...defaultOptions.image,
          ...options.image,
        },
        html2canvas: {
          ...defaultOptions.html2canvas,
          ...options.html2canvas,
        },
        jsPDF: {
          ...defaultOptions.jsPDF,
          ...options.jsPDF,
        },
        pagebreak: {
          ...defaultOptions.pagebreak,
          ...options.pagebreak,
        },
      };
    }
    // Set default options
    const defaultOptions: Required<PDFGenerationBaseOptions> = {
      margin: [90, 30, 93, 30],
      filename: "document.pdf",
      pageFormat: "A4",
      orientation: "portrait",
      footerHeight: 80, // or your default calculation
      headerHeight: 80,
      headerAndFooterImageWidth: 900,
      image: {
        type: "jpeg",
        quality: 0.98,
      },
      html2canvas: {
        scale: 2,
        backgroundColor: null,
      },
      jsPDF: {
        unit: "pt",
        format: "A4",
        orientation: "portrait",
      },
      pagebreak: {
        mode: ["css", "avoid-all"],
      },
    };
    const mergedOptions = mergeOptions(options);

    // Convert HTML elements to images
    const headerImage = await this.convertHtmlToImage(content.header);
    const footerImage = await this.convertHtmlToImage(content.footer);
    const sealImage = content.seal
      ? await this.convertHtmlToImage(content.seal)
      : "";

    const { signImageSrc, teeterImageSrc } = this.extractImageTags(
      content.mainContent
    ); // Prepare main content
    const linespaceHandler = `<p></p>`;
    const constructedContent = `
      <div style="width: 715px">
        ${content.additionalContent || ""}
        ${content.receivingDetails || ""}
        ${linespaceHandler}
        ${this.removeImageTags(content.mainContent)}
      </div>
    `.replace(/<\/?html>|<\/?body>|<head>.*<\/head>/g, "");

    // Prepare bottom content (signature section)
    const bottomContents = `
      <div id="seal-container" style="display: flex; align-items: center; justify-content: end; margin-left: 20px; background: transparent;">
          ${content.seal}
          <div style="display: flex; flex-direction: column;">
            <p id="regards" style="margin-bottom: 15px; margin-left: auto; margin-right: auto;">
              ${content.bestRegardsText ? content.bestRegardsText : ""}
            </p>
            ${
              signImageSrc
                ? `<img src="${signImageSrc}" alt="Sign Image" style="width: 96px; height: 48px; margin-bottom: 10px;">`
                : ""
            }
            ${
              teeterImageSrc
                ? `<img src="${teeterImageSrc}" alt="Teeter Image" style="width: 144px; height: 48px;">`
                : ""
            }
          </div>
      </div>`;

    // Create DOM elements for measurement
    const contentElement = this.createContentElement(constructedContent);
    const bottomContentElement = this.createContentElement(bottomContents);

    // Calculate dimensions
    const pageHeight = this.getPageHeight(
      mergedOptions?.pageFormat || "A4",
      mergedOptions.orientation || "Portrait"
    );
    const topBottomMargin = mergedOptions.margin[0] + mergedOptions.margin[2];
    const availableContentHeight = pageHeight - topBottomMargin;

    // Convert elements to images
    const bottomContentImage = await this.elementToImage(bottomContentElement);
    const bottomContentHeight = this.pixelsToPoints(
      bottomContentElement.offsetHeight
    );

    this.safeRemoveElement(bottomContentElement);

    let ccContentImage: string | null = null;
    let ccContentHeight = 0;

    if (content.ccDetails) {
      const ccContentElement = this.createContentElement(
        `<div>${content.ccDetails}</div>`
      );
      ccContentImage = await this.elementToImage(ccContentElement);
      ccContentHeight = this.pixelsToPoints(ccContentElement.offsetHeight);
      this.safeRemoveElement(ccContentElement);
    }

    // Generate PDF
    const pdf = await this.generatePDF(
      contentElement,
      {
        headerImage,
        footerImage,
        sealImage,
        bottomContentImage,
        bottomContentHeight,
        ccContentImage,
        ccContentHeight,
      },
      mergedOptions,
      pageHeight,
      availableContentHeight
    );
    this.safeRemoveElement(contentElement);

    return pdf;
  }

  private safeRemoveElement(el: HTMLElement) {
    if (el.parentNode === document.body) {
      document.body.removeChild(el);
    }
  }

  private async generatePDF(
    contentElement: HTMLElement,
    images: {
      headerImage: string;
      footerImage: string;
      sealImage: string;
      bottomContentImage: string;
      bottomContentHeight: number;
      ccContentImage: string | null;
      ccContentHeight: number;
    },
    options: Required<PDFGenerationBaseOptions>,
    pageHeight: number,
    availableContentHeight: number
  ): Promise<Blob> {
    return new Promise(async (resolve, reject) => {
      try {
        const { default: html2pdf } = await import("html2pdf.js");
        const pdf = await html2pdf()
          .from(contentElement)
          .set({
            margin: options.margin,
            filename: options.filename,
            image: { type: "jpeg", quality: 0.98 },
            html2canvas: { scale: 2, ignoreInvalidColors: true },
            jsPDF: {
              unit: "pt",
              format: options.pageFormat,
              orientation: options.orientation,
            },
            pagebreak: { mode: ["css", "avoid-all"] },
          })
          .toPdf()
          .get("pdf")
          .then((pdf: any) => {
            const totalPages = pdf.internal.getNumberOfPages();
            const contentHeight = this.pixelsToPoints(
              contentElement.offsetHeight
            );
            const pageWidth = pdf.internal.pageSize.getWidth();
            for (let i = 1; i <= totalPages; i++) {
              pdf.setPage(i);

              // Add header to every page
              pdf.addImage(
                images.headerImage,
                "png",
                options.margin[1],
                options.margin[0],
                pageWidth - (options.margin[1] + options.margin[3]),
                options.headerHeight
              );

              // Add footer to every page
              this.addFooterToPage(
                pdf,
                images.footerImage,
                pageHeight,
                options.margin[2],
                options.margin[1],
                options.footerHeight,
                pageWidth - (options.margin[1] + options.margin[3])
              );

              // Handle last page specially
              if (i === totalPages) {
                this.handleLastPage(
                  pdf,
                  images,
                  contentHeight,
                  availableContentHeight,
                  options,
                  pageHeight,
                  i
                );
              } else {
                // Add seal to intermediate pages
                if (images.sealImage) {
                  pdf.addImage(
                    images.sealImage,
                    "png",
                    350,
                    pageHeight - options.footerHeight - 100,
                    127.5,
                    127.5
                  );
                }
              }
            }

            return pdf;
          })
          .output("blob");

        resolve(pdf);
      } catch (error) {
        reject(error);
      }
    });
  }

  private handleLastPage(
    pdf: jsPDF,
    images: {
      sealImage: string;
      bottomContentImage: string;
      bottomContentHeight: number;
      ccContentImage: string | null;
      ccContentHeight: number;
      headerImage: string;
      footerImage: string;
    },
    contentHeight: number,
    availableContentHeight: number,
    options: Required<PDFGenerationBaseOptions>,
    pageHeight: number,
    currentPage: number
  ) {
    const currentYPosition = contentHeight % availableContentHeight;
    const remainingSpace = availableContentHeight - currentYPosition;

    // Check if bottom content fits in remaining space
    if (remainingSpace >= images.bottomContentHeight) {
      this.addContentToPage(
        pdf,
        images.bottomContentImage,
        options.margin[1],
        options.margin[0] + currentYPosition + 5,
        535,
        images.bottomContentHeight
      );

      // Check if CC content fits after bottom content
      const remainingAfterBottom = remainingSpace - images.bottomContentHeight;
      if (
        images.ccContentImage &&
        remainingAfterBottom >= images.ccContentHeight + 5
      ) {
        this.addContentToPage(
          pdf,
          images.ccContentImage,
          options.margin[1],
          options.margin[0] +
            currentYPosition +
            20 +
            images.bottomContentHeight +
            10,
          535,
          images.ccContentHeight
        );
      } else if (images.ccContentImage) {
        // Add new page for CC content if it doesn't fit
        this.addNewPage(pdf, images, options, pageHeight, currentPage, true);
      }
    } else {
      // Add new page for bottom content if it doesn't fit
      this.addNewPage(pdf, images, options, pageHeight, currentPage, false);
    }
  }

  private addNewPage(
    pdf: jsPDF,
    images: {
      sealImage: string;
      bottomContentImage: string;
      bottomContentHeight: number;
      ccContentImage: string | null;
      ccContentHeight: number;
      headerImage: string;
      footerImage: string;
    },
    options: Required<PDFGenerationBaseOptions>,
    pageHeight: number,
    currentPage: number,
    ccOnly: boolean
  ) {
    pdf.addPage();
    pdf.setPage(currentPage + 1);
    const pageWidth = pdf.internal.pageSize.getWidth();
    // Add header to new page
    pdf.addImage(
      images.headerImage,
      "png",
      30,
      10,
      pageWidth - (options.margin[1] + options.margin[3]),
      options.headerHeight
    );

    // Add footer to new page
    this.addFooterToPage(
      pdf,
      images.footerImage,
      pageHeight,
      options.margin[2],
      options.margin[1],
      options.footerHeight,
      pageWidth - (options.margin[1] + options.margin[3])
    );

    // Add seal to new page
    if (images.sealImage) {
      pdf.addImage(
        images.sealImage,
        "png",
        350,
        pageHeight - options.footerHeight - 100,
        127.5,
        127.5
      );
    }

    if (!ccOnly) {
      // Add bottom content to new page
      this.addContentToPage(
        pdf,
        images.bottomContentImage,
        options.margin[1],
        options.margin[0] + 10,
        535,
        images.bottomContentHeight
      );
    }

    if (images.ccContentImage) {
      // Add CC content to new page
      this.addContentToPage(
        pdf,
        images.ccContentImage,
        options.margin[1],
        options.margin[0] + (ccOnly ? 10 : images.bottomContentHeight + 20),
        535,
        images.ccContentHeight
      );
    }
  }

  private addContentToPage(
    pdf: jsPDF,
    image: string,
    x: number,
    y: number,
    width: number,
    height: number
  ) {
    pdf.addImage(image, "PNG", x, y, width, height);
  }

  private addFooterToPage(
    pdf: jsPDF,
    footerImage: string,
    pageHeight: number,
    bottomMargin: number,
    leftMargin: number,
    footerHeight: number,
    width: number
  ) {
    const isSpecialFooter = this.isSpecialFooterRequired();
    const yPosition = isSpecialFooter
      ? pageHeight - footerHeight - 10
      : pageHeight - (bottomMargin - 10);

    pdf.addImage(
      footerImage,
      "png",
      leftMargin,
      yPosition,
      width,
      footerHeight - 5
    );
  }
  private async convertHtmlToImage(html: string): Promise<string> {
    const element = document.createElement("div");

    if (html.indexOf("<img") !== -1) {
      element.innerHTML = html;
    } else {
      element.innerHTML = `<img src="${html}" />`;
    }
    document.body.appendChild(element);

    try {
      const { default: html2canvas } = await import("html2canvas");
      const canvas = await html2canvas(element, { backgroundColor: null });
      return canvas.toDataURL("image/png");
    } finally {
      document.body.removeChild(element);
    }
  }

  private createContentElement(html: string): HTMLElement {
    const element = document.createElement("div");
    element.style.width = "715px";
    element.innerHTML = html;
    document.body.appendChild(element);
    return element;
  }

  private removeImageTags(html: string): string {
    // Implement your image tag removal logic here
    return html.replace(/<img[^>]*>/g, "");
  }

  private pixelsToPoints(pixels: number): number {
    return (pixels * 72) / 96;
  }

  private getPageHeight(
    format: "A4" | "letter" | [number, number] | string,
    orientation: "portrait" | "landscape" | string
  ): number {
    if (format === "A4") {
      return orientation === "portrait" ? 841.89 : 595.28;
    } else if (format === "letter") {
      return orientation === "portrait" ? 792 : 612;
    } else {
      return orientation === "portrait" ? 841.89 : 595.28;
    }
  }

  private isSpecialFooterRequired(): boolean {
    return (
      window.location.href.toLowerCase().indexOf("itdb") !== -1 ||
      window.location.href.toLowerCase().indexOf("publicservice") !== -1
    );
  }
  private async elementToImage(element: HTMLElement): Promise<string> {
    const originalStyles = {
      visibility: element.style.visibility,
      position: element.style.position,
      left: element.style.left,
      top: element.style.top,
      width: element.style.width,
    };

    element.style.visibility = "visible";
    element.style.position = "absolute";
    element.style.left = "0";
    element.style.top = "0";
    element.style.width = "715px";

    document.body.appendChild(element); // Add here if not already added

    try {
      const { default: html2canvas } = await import("html2canvas");
      const canvas = await html2canvas(element, {
        backgroundColor: null,
        scale: 2,
        logging: false,
      });
      return canvas.toDataURL("image/png");
    } finally {
      // Restore styles (if needed again)
      element.style.visibility = originalStyles.visibility;
      element.style.position = originalStyles.position;
      element.style.left = originalStyles.left;
      element.style.top = originalStyles.top;
      element.style.width = originalStyles.width;

      // ✅ Remove from DOM
      if (element.parentNode) {
        document.body.removeChild(element);
      }
    }
  }

  extractImageTags(content: string): {
    length: number;
    imageSrcs: string[];
    signImageSrc: string;
    teeterImageSrc: string;
  } {
    const parser = new DOMParser();
    const doc = parser.parseFromString(content, "text/html");
    const images = Array.from(doc.querySelectorAll("img"));

    // Extract all image sources
    const imageSrcs = images.map((img) => img.src);

    // Try to identify sign and teeter images (last two images by default)
    let signImageSrc = "";
    let teeterImageSrc = "";

    if (images.length >= 2) {
      // Assuming sign is second last and teeter is last image
      signImageSrc = images[images.length - 2].src;
      teeterImageSrc = images[images.length - 1].src;
    } else if (images.length === 1) {
      // If only one image exists, assume it's the sign
      signImageSrc = images[0].src;
    }

    // Alternative: Identify by image attributes if they have specific classes/ids/alt text
    images.forEach((img) => {
      if (img.alt.toLowerCase().includes("sign")) {
        signImageSrc = img.src;
      }
      if (img.alt.toLowerCase().includes("teeter")) {
        teeterImageSrc = img.src;
      }
    });

    return {
      length: images.length,
      imageSrcs: imageSrcs,
      signImageSrc: signImageSrc,
      teeterImageSrc: teeterImageSrc,
    };
  }
}

export default PDFGenerationService;
