import JSZip from 'jszip';

const parseXml = (content) => {
  const parser = new DOMParser();
  return parser.parseFromString(content, 'application/xml');
};

const cleanText = (value = '') =>
  value
    .replace(/\r/g, '')
    .replace(/\t/g, ' ')
    .replace(/\s+\n/g, '\n')
    .replace(/\n{3,}/g, '\n\n')
    .replace(/[ \u00A0]{2,}/g, ' ')
    .trim();

const resolvePath = (basePath, targetPath = '') => {
  if (!basePath) return targetPath;
  const baseParts = basePath.split('/').filter(Boolean);
  const targetParts = targetPath.split('/');
  for (const part of targetParts) {
    if (!part || part === '.') continue;
    if (part === '..') {
      baseParts.pop();
    } else {
      baseParts.push(part);
    }
  }
  return baseParts.join('/');
};

const TEXT_MEDIA_TYPES = new Set([
  'application/xhtml+xml',
  'application/x-dtbook+xml',
  'text/html',
  'text/x-oeb1-document',
  'application/xml',
  'text/xml',
]);

const looksLikeTextFile = (href = '') =>
  /\.(x?html?|xml)$/i.test(href) || /\.(xhtml|html|htm)$/i.test(href);

const parseMetadata = (metadataNode) => {
  if (!metadataNode) return {};
  const getFirst = (selector) => metadataNode.querySelector(selector)?.textContent?.trim();
  const authors = Array.from(metadataNode.querySelectorAll('creator, dc\\:creator')).map((node) =>
    node.textContent?.trim(),
  );
  const identifiers = Array.from(metadataNode.querySelectorAll('identifier, dc\\:identifier')).map((node) => ({
    identifier: node.textContent?.trim(),
    type: node.getAttribute('opf:scheme') || node.getAttribute('id'),
  }));
  const categories = Array.from(metadataNode.querySelectorAll('subject, dc\\:subject'))
    .map((node) => node.textContent?.trim())
    .filter(Boolean);
  return {
    title: getFirst('title, dc\\:title'),
    language: getFirst('language, dc\\:language'),
    publisher: getFirst('publisher, dc\\:publisher'),
    description: getFirst('description, dc\\:description'),
    publishedDate: getFirst('date, dc\\:date'),
    authors: authors.filter(Boolean),
    identifiers: identifiers.filter((item) => item.identifier),
    categories,
  };
};

export const extractTextFromEpub = async (file) => {
  const zip = await JSZip.loadAsync(file);
  const containerEntry = zip.file('META-INF/container.xml');
  if (!containerEntry) {
    throw new Error('EPUB container bilgileri bulunamadı.');
  }
  const containerXml = await containerEntry.async('string');
  const containerDoc = parseXml(containerXml);
  const rootPath = containerDoc.querySelector('rootfile')?.getAttribute('full-path');
  if (!rootPath) {
    throw new Error('EPUB manifest dosyası bulunamadı.');
  }

  const opfEntry = zip.file(rootPath);
  if (!opfEntry) {
    throw new Error('EPUB manifesti okunamadı.');
  }
  const opfXml = await opfEntry.async('string');
  const opfDoc = parseXml(opfXml);
  const metadata = parseMetadata(opfDoc.querySelector('metadata'));
  const manifest = {};
  opfDoc.querySelectorAll('manifest > item').forEach((item) => {
    manifest[item.getAttribute('id')] = {
      href: item.getAttribute('href'),
      mediaType: item.getAttribute('media-type'),
    };
  });
  const baseDir = rootPath.includes('/') ? rootPath.split('/').slice(0, -1).join('/') : '';

  const spineRefs = Array.from(opfDoc.querySelectorAll('spine > itemref'))
    .map((item) => manifest[item.getAttribute('idref')])
    .filter(Boolean);

  const parser = new DOMParser();
  const collectedTexts = [];
  for (const ref of spineRefs) {
    if (!ref.href) continue;
    const shouldRead =
      (ref.mediaType && TEXT_MEDIA_TYPES.has(ref.mediaType)) || looksLikeTextFile(ref.href);
    if (!shouldRead) continue;
    const targetPath = resolvePath(baseDir, ref.href);
    const entry = zip.file(targetPath);
    if (!entry) continue;
    // eslint-disable-next-line no-await-in-loop
    const markup = await entry.async('string');
    const doc = parser.parseFromString(markup, 'text/html');
    const text = doc.body?.textContent || doc.documentElement?.textContent || '';
    const cleaned = cleanText(text);
    if (cleaned) {
      collectedTexts.push(cleaned);
    }
  }

  return {
    text: collectedTexts.join('\n\n').trim(),
    metadata: {
      ...metadata,
      filename: file.name,
      fileSize: file.size,
    },
  };
};

