// EPUB Parser - Converts EPUB 2/3 to Common Intermediate Format
// Procedural style: Functions, not classes

// ============================================================
// Main Parse Function
// ============================================================

export async function parseEPUB(epubBlob: Blob): Promise<EbookCIF> {
  const JSZip = (await import('jszip')).default;
  const zip = await JSZip.loadAsync(epubBlob);

  // Parse container.xml to find OPF file
  const containerXml = await getZipFileContent(zip, 'META-INF/container.xml');
  const opfPath = extractOPFPath(containerXml);

  if (!opfPath) {
    throw new Error('Invalid EPUB: no OPF file found');
  }

  const opfXml = await getZipFileContent(zip, opfPath);
  const packageDoc = parseXML(opfXml);

  const metadata = extractMetadata(packageDoc);
  const spine = parseSpine(packageDoc);
  const toc = await parseTOC(zip, packageDoc, opfPath);
  const resources = await loadResources(zip);
  const coverImage = await extractCover(zip, packageDoc);

  // Calculate locations (minimal - backend handles detailed tracking)
  const totalCharacters = await calculateTotalCharacters(spine, resources);

  return {
    metadata,
    toc,
    spine,
    resources,
    locations: {
      totalCharacters,
      estimatedPages: Math.ceil(totalCharacters / 1500),
    },
  };
}

// ============================================================
// Helper Functions
// ============================================================

async function getZipFileContent(zip: any, path: string): Promise<string> {
  const file = zip.file(path);
  if (!file) {
    throw new Error(`File not found: ${path}`);
  }
  return await file.async('text');
}

function parseXML(xmlString: string): XMLDocument {
  const parser = new DOMParser();
  return parser.parseFromString(xmlString, 'text/xml');
}

function extractOPFPath(containerXml: string): string | null {
  const containerDoc = parseXML(containerXml);
  return containerDoc.querySelector('rootfile')?.getAttribute('full-path') || null;
}

function extractMetadata(packageDoc: XMLDocument): EbookCIF['metadata'] {
  const metadata = packageDoc.querySelector('metadata');
  if (!metadata) {
    throw new Error('No metadata found in OPF');
  }

  return {
    title: metadata.querySelector('title')?.textContent || '',
    author: metadata.querySelector('creator')?.textContent || '',
    language: metadata.querySelector('language')?.textContent || 'en',
    publisher: metadata.querySelector('publisher')?.textContent || undefined,
    isbn: metadata.querySelector('identifier')?.textContent || undefined,
  };
}

function parseSpine(packageDoc: XMLDocument): EbookCIF['spine'] {
  const spine = packageDoc.querySelector('spine');
  const manifest = packageDoc.querySelector('manifest');

  if (!spine || !manifest) {
    throw new Error('No spine or manifest found in OPF');
  }

  const spineItems = spine.querySelectorAll('itemref');
  const result: EbookCIF['spine'] = [];

  spineItems.forEach((itemref) => {
    const idref = itemref.getAttribute('idref');
    if (!idref) return;

    const manifestItem = manifest.querySelector(`[id="${idref}"]`);
    if (!manifestItem) return;

    const href = manifestItem.getAttribute('href');
    if (!href) return;

    result.push({
      id: idref,
      type: 'html',
      content: href,
      properties: itemref.getAttribute('properties') || undefined,
    });
  });

  return result;
}

async function parseTOC(zip: any, packageDoc: XMLDocument, opfPath: string): Promise<EbookCIF['toc']> {
  // Try EPUB 3.0 navigation document first
  const navItem = packageDoc.querySelector('manifest item[properties~="nav"]');
  if (navItem) {
    const navHref = navItem.getAttribute('href');
    if (navHref) {
      const navPath = resolvePath(opfPath, navHref);
      return parseNavTOC(zip, navPath);
    }
  }

  // Fallback to EPUB 2.0 NCX
  const ncxId = spine?.getAttribute('toc');
  if (ncxId) {
    const ncxItem = packageDoc.querySelector(`manifest [id="${ncxId}"]`);
    if (ncxItem) {
      const ncxHref = ncxItem.getAttribute('href');
      if (ncxHref) {
        const ncxPath = resolvePath(opfPath, ncxHref);
        return parseNCXTOC(zip, ncxPath);
      }
    }
  }

  return [];
}

async function parseNavTOC(zip: any, navPath: string): Promise<EbookCIF['toc']> {
  const navXml = await getZipFileContent(zip, navPath);
  const navDoc = parseXML(navXml);
  const nav = navDoc.querySelector('nav');

  if (!nav) return [];

  const ol = nav.querySelector('ol');
  if (!ol) return [];

  const items = ol.querySelectorAll(':scope > li');
  const result: EbookCIF['toc'] = [];

  for (const li of items) {
    const link = li.querySelector('a');
    if (link) {
      result.push({
        id: link.getAttribute('href') || '',
        title: link.textContent || '',
        href: link.getAttribute('href') || '',
        children: [],
      });
    }
  }

  return result;
}

async function parseNCXTOC(zip: any, ncxPath: string): Promise<EbookCIF['toc']> {
  const ncxXml = await getZipFileContent(zip, ncxPath);
  const ncxDoc = parseXML(ncxXml);
  const navMap = ncxDoc.querySelector('navMap');

  if (!navMap) return [];

  return parseNCXNode(navMap);
}

function parseNCXNode(node: Element): EbookCIF['toc'] {
  const navPoints = node.querySelectorAll(':scope > navPoint');
  const result: EbookCIF['toc'] = [];

  navPoints.forEach((navPoint) => {
    const label = navPoint.querySelector('navLabel text')?.textContent || '';
    const content = navPoint.querySelector('content');
    const href = content?.getAttribute('src') || '';

    result.push({
      id: href,
      title: label,
      href,
      children: parseNCXNode(navPoint),
    });
  });

  return result;
}

async function loadResources(zip: any): Promise<Map<string, Blob>> {
  const resources = new Map<string, Blob>();
  const files = Object.keys(zip.files);

  for (const path of files) {
    const file = zip.file(path);
    if (file && !file.dir) {
      const blob = await file.async('blob');
      resources.set(path, blob);
    }
  }

  return resources;
}

async function extractCover(zip: any, packageDoc: XMLDocument): Promise<Blob | undefined> {
  // Try cover-id metadata
  const coverId = packageDoc.querySelector('meta[name="cover"]')?.getAttribute('content');
  if (coverId) {
    const coverItem = packageDoc.querySelector(`manifest [id="${coverId}"]`);
    if (coverItem) {
      const coverHref = coverItem.getAttribute('href');
      if (coverHref) {
        const coverFile = zip.file(coverHref);
        if (coverFile) {
          return await coverFile.async('blob');
        }
      }
    }
  }

  // Fallback: look for cover image in manifest
  const coverItem = packageDoc.querySelector('manifest item[properties~="cover-image"]');
  if (coverItem) {
    const coverHref = coverItem.getAttribute('href');
    if (coverHref) {
      const coverFile = zip.file(coverHref);
      if (coverFile) {
        return await coverFile.async('blob');
      }
    }
  }

  return undefined;
}

function resolvePath(basePath: string, relativePath: string): string {
  const baseDir = basePath.substring(0, basePath.lastIndexOf('/') + 1);
  return baseDir + relativePath;
}

async function calculateTotalCharacters(spine: EbookCIF['spine'], resources: Map<string, Blob>): Promise<number> {
  let total = 0;

  for (const item of spine) {
    if (item.type === 'html') {
      const content = resources.get(item.content);
      if (content) {
        const text = await content.text();
        total += text.length;
      }
    }
  }

  return total;
}

function generatePageBreaks(totalCharacters: number): number[] {
  const breaks: number[] = [];
  const charsPerPage = 1000; // Rough estimate

  for (let i = charsPerPage; i < totalCharacters; i += charsPerPage) {
    breaks.push(i);
  }

  return breaks;
}

// ============================================================
// Metadata Quick Extract (for library view)
// ============================================================

export async function extractEPUBMetadata(epubBlob: Blob): Promise<Partial<EbookCIF['metadata']>> {
  const zip = await JSZip.loadAsync(epubBlob);

  const containerXml = await getZipFileContent(zip, 'META-INF/container.xml');
  const opfPath = extractOPFPath(containerXml);

  if (!opfPath) {
    return {};
  }

  const opfXml = await getZipFileContent(zip, opfPath);
  const packageDoc = parseXML(opfXml);

  return extractMetadata(packageDoc);
}
