import * as plugins from './plugins.js';
import * as interfaces from './interfaces.js';
import {
  PDFDocument,
  PDFDict,
  PDFName,
  PDFRawStream,
  PDFArray,
  PDFString,
} from 'pdf-lib';
import { ZugferdXmlEncoder } from './classes.encoder.js';

export class XInvoice {
  private xmlString: string;
  private letterData: plugins.tsclass.business.ILetter;
  private pdfUint8Array: Uint8Array;

  private encoderInstance = new ZugferdXmlEncoder(); 
  private decoderInstance

  constructor() {
  }

  public async addPdfBuffer(pdfBuffer: Uint8Array | Buffer): Promise<void> {
    this.pdfUint8Array = Uint8Array.from(pdfBuffer);
  }

  public async addXmlString(xmlString: string): Promise<void> {
    this.xmlString = xmlString;
  }

  public async addLetterData(letterData: plugins.tsclass.business.ILetter): Promise<void> {
    this.letterData = letterData;
  }

  public async getXInvoice(): Promise<void> {
    // lets check requirements
    if (!this.pdfUint8Array) {
      throw new Error('No PDF buffer provided!');
    }
    if (!this.xmlString || !this.letterData) {
      // TODO: check if document already has xml

      throw new Error('No XML string or letter data provided!');
    }


    try {
      const pdfDoc = await PDFDocument.load(this.pdfUint8Array);

      // Convert the XML string to a Uint8Array
      const xmlBuffer = new TextEncoder().encode(this.xmlString);

      // Use pdf-lib's .attach() to embed the XML
      pdfDoc.attach(xmlBuffer, plugins.path.basename('invoice.xml'), {
        mimeType: 'application/xml',
        description: 'XRechnung XML Invoice',
      });

      // Save back into this.pdfUint8Array
      const modifiedPdfBytes = await pdfDoc.save();
      this.pdfUint8Array = modifiedPdfBytes;
      console.log(`PDF Buffer updated with new XML attachment!`);
    } catch (error) {
      console.error('Error embedding XML into PDF:', error);
      throw error;
    }
  }

  /**
   * Reads only the raw XML part from the PDF and returns it as a string.
   */
  public async getXmlData(): Promise<string> {
    try {
      const pdfDoc = await PDFDocument.load(this.pdfUint8Array);

      const namesDictObj = pdfDoc.catalog.lookup(PDFName.of('Names'));
      if (!(namesDictObj instanceof PDFDict)) {
        throw new Error('No Names dictionary found in PDF!');
      }

      const embeddedFilesDictObj = namesDictObj.lookup(PDFName.of('EmbeddedFiles'));
      if (!(embeddedFilesDictObj instanceof PDFDict)) {
        throw new Error('No EmbeddedFiles dictionary found!');
      }

      const filesSpecObj = embeddedFilesDictObj.lookup(PDFName.of('Names'));
      if (!(filesSpecObj instanceof PDFArray)) {
        throw new Error('No files specified in EmbeddedFiles dictionary!');
      }

      let xmlFile: PDFRawStream | undefined;

      for (let i = 0; i < filesSpecObj.size(); i += 2) {
        const fileNameObj = filesSpecObj.lookup(i);
        const fileSpecObj = filesSpecObj.lookup(i + 1);

        if (!(fileNameObj instanceof PDFString)) {
          continue;
        }
        if (!(fileSpecObj instanceof PDFDict)) {
          continue;
        }

        const efDictObj = fileSpecObj.lookup(PDFName.of('EF'));
        if (!(efDictObj instanceof PDFDict)) {
          continue;
        }

        const maybeStream = efDictObj.lookup(PDFName.of('F'));
        if (maybeStream instanceof PDFRawStream) {
          // If you only want a file named 'invoice.xml':
          // if (fileNameObj.value() === 'invoice.xml') { ... }
          xmlFile = maybeStream;
          break;
        }
      }

      if (!xmlFile) {
        throw new Error('XML file stream not found!');
      }

      const xmlCompressedBytes = xmlFile.getContents().buffer;
      const xmlBytes = plugins.pako.inflate(xmlCompressedBytes);
      const xmlContent = new TextDecoder('utf-8').decode(xmlBytes);

      return xmlContent;
    } catch (error) {
      console.error('Error extracting or parsing embedded XML from PDF:', error);
      throw error;
    }
  }

  public async getParsedXmlData(): Promise<interfaces.IXInvoice> {
    const smartxmlInstance = new plugins.smartxml.SmartXml();
    if (!this.xmlString && !this.pdfUint8Array) {
      throw new Error('No XML string or PDF buffer provided!');
    }
    let localXmlString = this.xmlString;
    if (!localXmlString) {
      localXmlString = await this.getXmlData();
    }
    return smartxmlInstance.parseXmlToObject(localXmlString);
  }

  /**
   * Example method to parse the embedded XML into a structured IInvoice.
   * Right now, it just returns mock data. 
   * Replace with your own XML parsing.
   */
  private parseXmlToInvoice(xmlContent: string): interfaces.IXInvoice {
    // e.g. parse using DOMParser, xml2js, fast-xml-parser, etc.
    // For now, returning placeholder data:
    return {
      InvoiceNumber: '12345',
      DateIssued: '2023-04-01',
      Seller: {
        Name: 'Seller Co',
        Address: {
          Street: '1234 Market St',
          City: 'Sample City',
          PostalCode: '12345',
          Country: 'DE',
        },
        Contact: {
          Email: 'contact@sellerco.com',
          Phone: '123-456-7890',
        },
      },
      Buyer: {
        Name: 'Buyer Inc',
        Address: {
          Street: '5678 Trade Rd',
          City: 'Trade City',
          PostalCode: '67890',
          Country: 'DE',
        },
        Contact: {
          Email: 'info@buyerinc.com',
          Phone: '987-654-3210',
        },
      },
      Items: [
        {
          Description: 'Item 1',
          Quantity: 10,
          UnitPrice: 9.99,
          TotalPrice: 99.9,
        },
      ],
      TotalAmount: 99.9,
    };
  }
}