import jsPDF from 'jspdf';
import {
  AlignmentType,
  Document,
  HeadingLevel,
  Paragraph,
  TextRun,
  Packer,
} from 'docx';
import JSZip from 'jszip';

const BLOCK_TAGS = new Set(['P', 'DIV', 'BLOCKQUOTE', 'UL', 'OL', 'LI', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6']);
const INLINE_TAGS = new Set(['STRONG', 'B', 'EM', 'I', 'U', 'A', 'SPAN']);
const ALLOWED_TAGS = new Set(['BR', ...BLOCK_TAGS, ...INLINE_TAGS]);

function getDomParser() {
  return typeof DOMParser !== 'undefined' ? new DOMParser() : null;
}

export function sanitizeRichTextHtml(input = '') {
  const parser = getDomParser();
  if (!parser) return String(input || '');

  const doc = parser.parseFromString(`<div>${String(input || '')}</div>`, 'text/html');
  const root = doc.body.firstElementChild || doc.body;

  const cleanseNode = (node) => {
    if (node.nodeType === Node.TEXT_NODE) {
      return;
    }

    if (node.nodeType !== Node.ELEMENT_NODE) {
      node.remove();
      return;
    }

    const tagName = node.tagName.toUpperCase();
    if (!ALLOWED_TAGS.has(tagName)) {
      const parent = node.parentNode;
      const children = Array.from(node.childNodes);
      children.forEach((child) => parent.insertBefore(child, node));
      node.remove();
      children.forEach(cleanseNode);
      return;
    }

    [...node.attributes].forEach((attribute) => {
      const name = attribute.name.toLowerCase();
      if (tagName === 'A' && name === 'href') {
        const value = String(attribute.value || '').trim();
        const allowed = /^(https?:|mailto:|tel:|#|\/)/i.test(value);
        if (!allowed) {
          node.removeAttribute(attribute.name);
        }
        return;
      }
      node.removeAttribute(attribute.name);
    });

    if (tagName === 'A') {
      const href = node.getAttribute('href');
      if (href) {
        node.setAttribute('rel', 'noreferrer noopener');
        node.setAttribute('target', '_blank');
      }
    }

    Array.from(node.childNodes).forEach(cleanseNode);
  };

  Array.from(root.childNodes).forEach(cleanseNode);
  return root.innerHTML;
}

export function stripRichTextToText(input = '') {
  const parser = getDomParser();
  if (!parser) {
    return String(input || '')
      .replace(/<br\s*\/?>/gi, '\n')
      .replace(/<\/p>\s*<p>/gi, '\n\n')
      .replace(/<[^>]+>/g, '')
      .trim();
  }

  const doc = parser.parseFromString(`<div>${sanitizeRichTextHtml(input)}</div>`, 'text/html');
  const root = doc.body.firstElementChild || doc.body;

  const collect = (node) => {
    if (node.nodeType === Node.TEXT_NODE) {
      return node.textContent || '';
    }
    if (node.nodeType !== Node.ELEMENT_NODE) {
      return '';
    }

    const tagName = node.tagName.toUpperCase();
    if (tagName === 'BR') return '\n';
    if (tagName === 'LI') return `- ${Array.from(node.childNodes).map(collect).join('').trim()}\n`;
    const text = Array.from(node.childNodes).map(collect).join('');
    if (BLOCK_TAGS.has(tagName)) {
      return `\n${text.trim()}\n`;
    }
    return text;
  };

  return collect(root)
    .replace(/\n{3,}/g, '\n\n')
    .replace(/[ \t]+\n/g, '\n')
    .trim();
}

function parseInlineRuns(node, style = {}) {
  if (node.nodeType === Node.TEXT_NODE) {
    const text = node.textContent || '';
    return text ? [{ text, ...style }] : [];
  }

  if (node.nodeType !== Node.ELEMENT_NODE) {
    return [];
  }

  const tagName = node.tagName.toUpperCase();
  if (tagName === 'BR') {
    return [{ text: '\n', ...style }];
  }

  const nextStyle = { ...style };
  if (tagName === 'STRONG' || tagName === 'B') nextStyle.bold = true;
  if (tagName === 'EM' || tagName === 'I') nextStyle.italics = true;
  if (tagName === 'U') nextStyle.underline = {};
  if (tagName === 'A') {
    nextStyle.color = '0563C1';
    nextStyle.underline = {};
  }

  return Array.from(node.childNodes).flatMap((child) => parseInlineRuns(child, nextStyle));
}

function htmlToDocxParagraphs(input = '') {
  const parser = getDomParser();
  if (!parser) {
    return [new Paragraph(String(input || '').trim())];
  }

  const doc = parser.parseFromString(`<div>${sanitizeRichTextHtml(input)}</div>`, 'text/html');
  const root = doc.body.firstElementChild || doc.body;
  const paragraphs = [];

  const pushParagraph = (node, options = {}) => {
    const runs = Array.from(node.childNodes).flatMap((child) => parseInlineRuns(child));
    const textRuns = runs.length ? runs : [{ text: node.textContent || '' }];
    paragraphs.push(new Paragraph({
      children: textRuns.map((run) => new TextRun(run)),
      spacing: { after: 180 },
      bullet: options.bullet ? { level: 0 } : undefined,
      heading: options.heading || undefined,
      alignment: options.alignment || AlignmentType.LEFT,
    }));
  };

  const walk = (node) => {
    if (!node) return;
    if (node.nodeType === Node.TEXT_NODE) {
      const text = (node.textContent || '').trim();
      if (text) {
        paragraphs.push(new Paragraph({ children: [new TextRun(text)], spacing: { after: 180 } }));
      }
      return;
    }
    if (node.nodeType !== Node.ELEMENT_NODE) return;

    const tagName = node.tagName.toUpperCase();
    if (tagName === 'UL' || tagName === 'OL') {
      Array.from(node.children).forEach((child, index) => {
        if (child.tagName?.toUpperCase() !== 'LI') return;
        const runs = Array.from(child.childNodes).flatMap((grandChild) => parseInlineRuns(grandChild));
        const textRuns = runs.length ? runs : [{ text: child.textContent || '' }];
        paragraphs.push(new Paragraph({
          children: textRuns.map((run) => new TextRun(run)),
          bullet: tagName === 'UL' ? { level: 0 } : undefined,
          numbering: tagName === 'OL' ? { reference: 'number-list', level: 0 } : undefined,
          spacing: { after: 120 },
        }));
      });
      return;
    }

    if (tagName === 'H1' || tagName === 'H2' || tagName === 'H3' || tagName === 'H4' || tagName === 'H5' || tagName === 'H6') {
      const headingMap = {
        H1: HeadingLevel.HEADING_1,
        H2: HeadingLevel.HEADING_2,
        H3: HeadingLevel.HEADING_3,
        H4: HeadingLevel.HEADING_4,
        H5: HeadingLevel.HEADING_5,
        H6: HeadingLevel.HEADING_6,
      };
      pushParagraph(node, { heading: headingMap[tagName] });
      return;
    }

    if (tagName === 'BLOCKQUOTE') {
      pushParagraph(node);
      return;
    }

    if (tagName === 'LI') {
      pushParagraph(node, { bullet: true });
      return;
    }

    if (tagName === 'P' || tagName === 'DIV') {
      pushParagraph(node);
      return;
    }

    Array.from(node.children).forEach(walk);
  };

  Array.from(root.children).forEach(walk);
  if (paragraphs.length === 0) {
    paragraphs.push(new Paragraph({ children: [new TextRun(stripRichTextToText(input) || '')], spacing: { after: 180 } }));
  }
  return paragraphs;
}

function escapeXml(value = '') {
  return String(value || '')
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

function htmlToOdtParagraphs(input = '') {
  const parser = getDomParser();
  if (!parser) {
    return [escapeXml(String(input || '').trim())];
  }

  const doc = parser.parseFromString(`<div>${sanitizeRichTextHtml(input)}</div>`, 'text/html');
  const root = doc.body.firstElementChild || doc.body;
  const paragraphs = [];

  const inlineText = (node) => {
    if (node.nodeType === Node.TEXT_NODE) {
      return escapeXml(node.textContent || '');
    }
    if (node.nodeType !== Node.ELEMENT_NODE) {
      return '';
    }
    const tagName = node.tagName.toUpperCase();
    if (tagName === 'BR') return '<text:line-break/>';
    const text = Array.from(node.childNodes).map(inlineText).join('');
    if (tagName === 'LI') {
      return `• ${text}`;
    }
    return text;
  };

  const walk = (node) => {
    if (node.nodeType === Node.TEXT_NODE) {
      const text = escapeXml((node.textContent || '').trim());
      if (text) paragraphs.push(`<text:p>${text}</text:p>`);
      return;
    }
    if (node.nodeType !== Node.ELEMENT_NODE) return;

    const tagName = node.tagName.toUpperCase();
    if (tagName === 'UL' || tagName === 'OL') {
      Array.from(node.children).forEach((child, index) => {
        if (child.tagName?.toUpperCase() !== 'LI') return;
        const prefix = tagName === 'OL' ? `${index + 1}. ` : '• ';
        paragraphs.push(`<text:p>${prefix}${Array.from(child.childNodes).map(inlineText).join('')}</text:p>`);
      });
      return;
    }

    if (tagName === 'H1' || tagName === 'H2' || tagName === 'H3' || tagName === 'H4' || tagName === 'H5' || tagName === 'H6') {
      paragraphs.push(`<text:p>${Array.from(node.childNodes).map(inlineText).join('')}</text:p>`);
      return;
    }

    if (tagName === 'P' || tagName === 'DIV' || tagName === 'BLOCKQUOTE' || tagName === 'LI') {
      paragraphs.push(`<text:p>${Array.from(node.childNodes).map(inlineText).join('')}</text:p>`);
      return;
    }

    Array.from(node.children).forEach(walk);
  };

  Array.from(root.children).forEach(walk);
  if (paragraphs.length === 0) {
    paragraphs.push(`<text:p>${escapeXml(stripRichTextToText(input) || '')}</text:p>`);
  }
  return paragraphs;
}

async function downloadBlob(blob, filename) {
  const url = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = url;
  link.download = filename;
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
  window.URL.revokeObjectURL(url);
}

export async function exportRichTextPdf({ title, subject = '', bodyHtml = '', filename = 'schreiben.pdf' }) {
  const html = sanitizeRichTextHtml(bodyHtml);
  const doc = new jsPDF({ unit: 'pt', format: 'a4' });
  const container = document.createElement('div');
  container.style.width = '540pt';
  container.style.padding = '24pt';
  container.innerHTML = `
    <div style="font-family: Arial, sans-serif; color: #1f2937;">
      <h1 style="font-size: 20pt; margin: 0 0 8pt;">${escapeXml(title || 'Schreiben')}</h1>
      ${subject ? `<p style="margin: 0 0 12pt; color: #4b5563;"><strong>Betreff:</strong> ${escapeXml(subject)}</p>` : ''}
      <div style="font-size: 11.5pt; line-height: 1.55;">${html}</div>
    </div>
  `;

  return new Promise((resolve, reject) => {
    doc.html(container, {
      callback: async (pdf) => {
        try {
          await downloadBlob(pdf.output('blob'), filename);
          resolve();
        } catch (error) {
          reject(error);
        }
      },
      x: 24,
      y: 24,
      width: 540,
      windowWidth: 960,
    });
  });
}

export async function exportRichTextDocx({ title, subject = '', bodyHtml = '', filename = 'schreiben.docx' }) {
  const paragraphs = [
    new Paragraph({
      children: [new TextRun({ text: title || 'Schreiben', bold: true, size: 28 })],
      spacing: { after: 180 },
    }),
  ];

  if (subject) {
    paragraphs.push(new Paragraph({
      children: [new TextRun({ text: `Betreff: ${subject}`, bold: true })],
      spacing: { after: 180 },
    }));
  }

  paragraphs.push(...htmlToDocxParagraphs(bodyHtml));

  const document = new Document({
    numbering: {
      config: [{
        reference: 'number-list',
        levels: [{
          level: 0,
          format: 'decimal',
          text: '%1.',
          alignment: AlignmentType.START,
        }],
      }],
    },
    sections: [{
      children: paragraphs,
    }],
  });

  const blob = await Packer.toBlob(document);
  await downloadBlob(blob, filename);
}

export async function exportRichTextOdt({ title, subject = '', bodyHtml = '', filename = 'schreiben.odt' }) {
  const zip = new JSZip();
  zip.file('mimetype', 'application/vnd.oasis.opendocument.text', { compression: 'STORE' });
  zip.file('content.xml', `<?xml version="1.0" encoding="UTF-8"?>
<office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
  xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0"
  xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
  xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0"
  office:version="1.2">
  <office:body>
    <office:text>
      <text:h text:outline-level="1">${escapeXml(title || 'Schreiben')}</text:h>
      ${subject ? `<text:p>Betreff: ${escapeXml(subject)}</text:p>` : ''}
      ${htmlToOdtParagraphs(bodyHtml).join('\n')}
    </office:text>
  </office:body>
</office:document-content>`);
  zip.file('styles.xml', `<?xml version="1.0" encoding="UTF-8"?>
<office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
  xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0"
  office:version="1.2">
  <office:styles/>
</office:document-styles>`);
  zip.file('meta.xml', `<?xml version="1.0" encoding="UTF-8"?>
<office:document-meta xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0"
  office:version="1.2">
  <office:meta/>
</office:document-meta>`);
  zip.folder('META-INF').file('manifest.xml', `<?xml version="1.0" encoding="UTF-8"?>
<manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.2">
  <manifest:file-entry manifest:full-path="/" manifest:media-type="application/vnd.oasis.opendocument.text"/>
  <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/>
  <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/>
</manifest:manifest>`);

  const blob = await zip.generateAsync({ type: 'blob', mimeType: 'application/vnd.oasis.opendocument.text' });
  await downloadBlob(blob, filename);
}

export function exportRichTextPlainText({ title, subject = '', bodyHtml = '', filename = 'schreiben.txt' }) {
  const lines = [
    title || 'Schreiben',
    subject ? `Betreff: ${subject}` : '',
    '',
    stripRichTextToText(bodyHtml),
  ].filter(Boolean);
  const blob = new Blob([lines.join('\n')], { type: 'text/plain;charset=utf-8' });
  return downloadBlob(blob, filename);
}
