// Comprehensive DOCX converter with template-derived styling
import {
  Document, Paragraph, TextRun, AlignmentType, HeadingLevel, BorderStyle,
  UnderlineType, ShadingType, LevelFormat,
  Packer, Table, TableCell, TableRow, WidthType, VerticalAlign,
  ExternalHyperlink, TableBorders
} from 'docx';
import { DocxStyleConfig, PaperSize, TemplateElementStyle } from '../types';
import { resolveColor, resolveFont } from '../services/templateRenderer';

const pt = (points: number) => points * 2;
const inchesToTwips = (inches: number) => Math.round(inches * 1440);
const mmToTwips = (mm: number) => Math.round(mm * (1440 / 25.4));

// Precise line height calculation (20 twips = 1 point)
const getTwipLineHeight = (multiplier: number, fontSizePt: number) => Math.round(multiplier * fontSizePt * 20);

// Helper to format color for docx - always returns valid 6-digit hex or undefined
const formatColor = (color?: string): string | undefined => {
  if (!color) return undefined;
  // If it's already 6 hex chars, return as-is
  if (/^[a-fA-F0-9]{6}$/.test(color)) return color;
  // If it has #, strip it
  if (color.startsWith('#')) {
    const hex = color.slice(1);
    if (/^[a-fA-F0-9]{6}$/.test(hex)) return hex;
  }
  // Invalid color - return undefined
  return undefined;
};

export interface ConversionOptions {
  paperSize: PaperSize;
  heading1: DocxStyleConfig;
  heading2: DocxStyleConfig;
  body: DocxStyleConfig;
  accentColor: string;
  pageBackground?: string;
  codeFont?: string;
  isDarkTheme?: boolean;
  elements?: Record<string, TemplateElementStyle>;
  fonts?: {
    heading?: string;
    body?: string;
    code?: string;
  };
  palette?: Record<string, string>;
  id?: string;
  page?: any;
  useTableHeaders?: boolean;
}

// Transform text to all caps if needed
const transformText = (text: string, allCaps?: boolean): string => {
  if (!allCaps) return text;
  return text.toUpperCase();
};

// Derive a subtle background color based on the template's body color
const deriveSubtleBg = (bodyColor: string, isDark: boolean): string => {
  if (isDark) return '2D2D2D';
  return 'F5F5F5';
};

// Check if a template is dark theme based on body background
const detectDarkTheme = (pageBg?: string): boolean => {
  if (!pageBg) return false;
  const bgVal = parseInt(pageBg, 16);
  return bgVal < 0x444444;
};

const mapBorderStyle = (style?: string) => {
  switch (style) {
    case 'double': return BorderStyle.DOUBLE;
    case 'dotted': return BorderStyle.DOTTED;
    case 'dashed': return BorderStyle.DASHED;
    case 'single':
    default: return BorderStyle.SINGLE;
  }
};

const mapAlignment = (align?: string) => {
  switch (align) {
    case 'center': return AlignmentType.CENTER;
    case 'right': return AlignmentType.RIGHT;
    case 'both': return AlignmentType.JUSTIFIED;
    case 'left':
    default: return AlignmentType.LEFT;
  }
};

// Detect OS-specific emoji font
const getEmojiFont = (): string => {
  const platform = navigator.platform.toLowerCase();
  const userAgent = navigator.userAgent.toLowerCase();

  if (platform.includes('win') || userAgent.includes('windows')) {
    return 'Segoe UI Emoji';
  } else if (platform.includes('mac') || userAgent.includes('mac')) {
    return 'Apple Color Emoji';
  } else {
    // Linux and others
    return 'Noto Color Emoji';
  }
};

// Check if a character is an emoji
// Covers most common emoji ranges in Unicode
const isEmoji = (char: string): boolean => {
  const code = char.codePointAt(0);
  if (!code) return false;

  return (
    (code >= 0x1F600 && code <= 0x1F64F) || // Emoticons
    (code >= 0x1F300 && code <= 0x1F5FF) || // Misc Symbols and Pictographs
    (code >= 0x1F680 && code <= 0x1F6FF) || // Transport and Map
    (code >= 0x1F1E0 && code <= 0x1F1FF) || // Regional Indicators (flags)
    (code >= 0x2600 && code <= 0x26FF) ||   // Misc symbols
    (code >= 0x2700 && code <= 0x27BF) ||   // Dingbats
    (code >= 0xFE00 && code <= 0xFE0F) ||   // Variation Selectors
    (code >= 0x1F900 && code <= 0x1F9FF) || // Supplemental Symbols and Pictographs
    (code >= 0x1FA00 && code <= 0x1FA6F) || // Chess Symbols
    (code >= 0x1FA70 && code <= 0x1FAFF) || // Symbols and Pictographs Extended-A
    (code >= 0x200D)                        // Zero Width Joiner (for multi-char emojis)
  );
};

// Split text into segments with regular text and emoji
interface TextSegment {
  text: string;
  isEmoji: boolean;
}

const segmentText = (text: string): TextSegment[] => {
  const segments: TextSegment[] = [];
  let currentSegment = '';
  let currentIsEmoji = false;

  // Use Array.from to properly handle surrogate pairs (emoji characters)
  const chars = Array.from(text);

  for (const char of chars) {
    const charIsEmoji = isEmoji(char);

    if (segments.length === 0) {
      currentSegment = char;
      currentIsEmoji = charIsEmoji;
    } else if (charIsEmoji === currentIsEmoji) {
      currentSegment += char;
    } else {
      segments.push({ text: currentSegment, isEmoji: currentIsEmoji });
      currentSegment = char;
      currentIsEmoji = charIsEmoji;
    }
  }

  if (currentSegment) {
    segments.push({ text: currentSegment, isEmoji: currentIsEmoji });
  }

  return segments;
};

export const generateDocxDocument = async (
  htmlContent: string,
  options: ConversionOptions
): Promise<Blob> => {
  const { paperSize, heading1, heading2, body, accentColor, pageBackground, codeFont, isDarkTheme, elements, fonts } = options;

  // DEBUG: Specific markdown constructs for DOCX
  console.log('=== DOCX DEBUG (h3, strong, table, ul/ol, li) ===');
  console.log('elements h3:', JSON.stringify(elements?.h3));
  console.log('elements strong:', JSON.stringify(elements?.strong));
  console.log('elements table:', JSON.stringify(elements?.table));
  console.log('elements th:', JSON.stringify(elements?.th));
  console.log('elements td:', JSON.stringify(elements?.td));
  console.log('elements ul:', JSON.stringify(elements?.ul));
  console.log('elements ol:', JSON.stringify(elements?.ol));
  console.log('elements li:', JSON.stringify(elements?.li));
  console.log('=== END DOCX OPTIONS ===');

  const visitedTags = new Set<string>();

  const parser = new DOMParser();
  const doc = parser.parseFromString(htmlContent, 'text/html');

  const children: (Paragraph | Table)[] = [];

  // Track separate ordered lists for independent numbering
  let listCounter = 0;
  let unorderedListCounter = 0;
  const orderedListRefs: Map<HTMLElement, string> = new Map();
  const unorderedListRefs: Map<HTMLElement, string> = new Map();

  // Store numbering definitions for the document
  const numberingDefinitions: any[] = [];

  const isDark = isDarkTheme ?? detectDarkTheme(pageBackground);

  // Build color palette from options for resolving color references
  // Start with passed palette (from typography.colors), then add/override with resolved values
  const palette: Record<string, string> = { ...options.palette };
  if (pageBackground) palette.background = pageBackground;
  if (accentColor) palette.accent = accentColor;
  // Don't overwrite text/textSecondary if they exist in the palette
  if (body.color && !palette.text) palette.text = body.color;
  if (body.color && !palette.textSecondary) palette.textSecondary = body.color;

  // Debug flags to avoid flooding console
  let strongDebugged = false;

  // Helper to resolve a color value - handles hex, palette refs, and undefined
  const resolveColorToHex = (color?: string): string | undefined => {
    if (!color) return undefined;
    // If it's a hex value with #, return as-is
    if (color.startsWith('#')) return color;
    // If it's already a 6-digit hex value without #, return as-is
    if (/^[a-fA-F0-9]{6}$/.test(color)) return color;
    // It's a palette reference like 'accent', 'background', etc.
    // Look it up
    const resolved = palette[color];
    if (resolved) return resolved;
    return undefined;
  };

  // Helper to get element color with fallback
  const getElementColor = (element?: TemplateElementStyle, fallback?: string): string | undefined => {
    if (!element?.color) return fallback ? resolveColorToHex(fallback) : undefined;
    return resolveColorToHex(element.color) || resolveColorToHex(fallback);
  };

  // Handle code block colors
  if (elements?.code?.background) {
    palette.codeBg = resolveColorToHex(elements.code.background);
  }
  if (elements?.pre?.background) {
    palette.codeBg = resolveColorToHex(elements.pre.background) || palette.codeBg;
  }
  if (elements?.code?.color) {
    palette.codeText = resolveColorToHex(elements.code.color);
  }
  if (elements?.pre?.color) {
    palette.codeText = resolveColorToHex(elements.pre.color) || palette.codeText;
  }

  // Handle blockquote border
  if (elements?.blockquote?.borderLeft?.color) {
    palette.blockquoteBorder = resolveColorToHex(elements.blockquote.borderLeft.color);
  }
  if (elements?.blockquote?.background) {
    palette.blockquoteBg = resolveColorToHex(elements.blockquote.background);
  }

  // Handle table colors
  if (elements?.th?.background) {
    palette.tableHeaderBg = resolveColorToHex(elements.th.background);
  }
  if (elements?.th?.color) {
    palette.tableHeaderText = resolveColorToHex(elements.th.color);
  }
  if (elements?.td?.background) {
    palette.tableCellBg = resolveColorToHex(elements.td.background);
  }



  // Get code styling from elements (resolve color references)
  const codeFontResolved = codeFont || (fonts?.code ? resolveFont('code', fonts) : body.font);
  const codeBgColor = resolveColorToHex(elements?.pre?.background) || deriveSubtleBg(body.color || '111111', isDark);
  const codeTextColor = resolveColorToHex(elements?.pre?.color) || (isDark ? 'E0E0E0' : '333333');

  // Get link color and font from elements (resolve color/font references)
  const linkColor = resolveColorToHex(elements?.a?.color) || '0563C1';
  const linkFont = elements?.a?.font
    ? (fonts?.[elements.a.font as keyof typeof fonts] || fonts?.heading || fonts?.body || body.font)
    : undefined;
  const linkBold = elements?.a?.bold || false;

  // Helper to get allCaps from element config
  const getAllCapsConfig = (tagName: string): boolean => {
    if (!elements) return false;
    const elementConfig = elements[tagName.toLowerCase()];
    return elementConfig?.allCaps || false;
  };

  // Process text runs with support for links and formatting
  const processTextRuns = (element: HTMLElement, baseFormatting: any = {}, elementType?: string): (TextRun | ExternalHyperlink)[] => {
    const runs: (TextRun | ExternalHyperlink)[] = [];
    const elementAllCaps = elementType ? getAllCapsConfig(elementType) : false;
    const formattingWithCaps = { ...baseFormatting, allCaps: baseFormatting.allCaps || elementAllCaps };

    // Handle case where the root element itself is a link
    if (element.tagName.toLowerCase() === 'a') {
      const href = element.getAttribute('href');
      if (href && href.trim()) {
        const linkRuns: TextRun[] = [];
        const collectRuns = (node: Node, fmt: any) => {
          if (node.nodeType === Node.TEXT_NODE) {
            const text = node.textContent || '';
            if (text) {
              const { allCaps: _, ...runProps } = fmt;
              const transformedText = transformText(text, fmt.allCaps);

              // Emoji regex using Unicode properties
              // Matches single extended pictographs and ZWJ sequences (e.g., 👨‍👩‍👧‍👦)
              const emojiRegex = /\p{Extended_Pictographic}(\u200D\p{Extended_Pictographic})*/gu;
              const segments: Array<{ text: string, isEmoji: boolean }> = [];
              let lastIndex = 0;

              for (const match of transformedText.matchAll(emojiRegex)) {
                // Add text before emoji
                if (match.index! > lastIndex) {
                  segments.push({ text: transformedText.substring(lastIndex, match.index!), isEmoji: false });
                }
                // Add emoji
                segments.push({ text: match[0], isEmoji: true });
                lastIndex = match.index! + match[0].length;
              }
              // Add remaining text
              if (lastIndex < transformedText.length) {
                segments.push({ text: transformedText.substring(lastIndex), isEmoji: false });
              }
              if (segments.length === 0) {
                segments.push({ text: transformedText, isEmoji: false });
              }

              // Create runs with appropriate fonts
              for (const seg of segments) {
                const emojiFont = getEmojiFont();
                if (seg.isEmoji) {
                  // Emojis should not have color/bold/etc applied to preserve their native color
                  // BUT they need size to match surrounding text and color="auto" to force native rendering
                  linkRuns.push(new TextRun({
                    text: seg.text,
                    size: runProps.size,
                    color: "auto",
                    font: {
                      ascii: emojiFont,
                      hAnsi: emojiFont,
                      cs: emojiFont,
                      eastAsia: emojiFont,
                      hint: "cs"
                    },
                    // Links still need underline to look clickable
                    underline: { type: UnderlineType.SINGLE }
                  }));
                } else {
                  linkRuns.push(new TextRun({
                    text: seg.text,
                    ...runProps,
                    font: linkFont || fmt.font,
                    bold: linkBold || fmt.bold,
                    color: linkColor,
                    underline: { type: UnderlineType.SINGLE }
                  }));
                }
              }
            }
          } else if (node.nodeType === Node.ELEMENT_NODE) {
            const childEl = node as HTMLElement;
            const childTag = childEl.tagName.toLowerCase();
            const childFmt = { ...fmt };
            if (childTag === 'strong' || childTag === 'b') childFmt.bold = true;
            if (childTag === 'em' || childTag === 'i') childFmt.italics = true;
            if (childTag === 'code') {
              childFmt.font = codeFontResolved;
              childFmt.color = codeTextColor;
            }
            for (const child of Array.from(childEl.childNodes)) {
              collectRuns(child, childFmt);
            }
          }
        };

        for (const child of Array.from(element.childNodes)) {
          collectRuns(child, formattingWithCaps);
        }

        if (linkRuns.length > 0) {
          runs.push(new ExternalHyperlink({ children: linkRuns, link: href }));
        }
        return runs;
      }
    }

    const walk = (node: Node, formatting: any) => {
      if (node.nodeType === Node.TEXT_NODE) {
        const text = node.textContent || '';
        if (text) {
          const transformedText = transformText(text, formatting.allCaps);
          const { allCaps: _, ...runProps } = formatting;

          // Emoji regex using Unicode properties
          // Matches single extended pictographs and ZWJ sequences (e.g., 👨‍👩‍👧‍👦)
          const emojiRegex = /\p{Extended_Pictographic}(\u200D\p{Extended_Pictographic})*/gu;
          const segments: Array<{ text: string, isEmoji: boolean }> = [];
          let lastIndex = 0;

          for (const match of transformedText.matchAll(emojiRegex)) {
            // Add text before emoji
            if (match.index! > lastIndex) {
              segments.push({ text: transformedText.substring(lastIndex, match.index!), isEmoji: false });
            }
            // Add emoji
            segments.push({ text: match[0], isEmoji: true });
            lastIndex = match.index! + match[0].length;
          }
          // Add remaining text
          if (lastIndex < transformedText.length) {
            segments.push({ text: transformedText.substring(lastIndex), isEmoji: false });
          }
          if (segments.length === 0) {
            segments.push({ text: transformedText, isEmoji: false });
          }

          // Create runs with appropriate fonts
          for (const seg of segments) {
            const emojiFont = getEmojiFont();
            if (seg.isEmoji) {
              // Emojis should not have color/bold/etc applied to preserve their native color
              // BUT they need size to match surrounding text and color="auto" to force native rendering
              runs.push(new TextRun({
                text: seg.text,
                size: runProps.size,
                color: "auto",
                font: {
                  ascii: emojiFont,
                  hAnsi: emojiFont,
                  cs: emojiFont,
                  eastAsia: emojiFont,
                  hint: "cs"
                }
              }));
            } else {
              runs.push(new TextRun({
                text: seg.text,
                ...runProps,
                font: runProps.font
              }));
            }
          }
        }
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        const el = node as HTMLElement;
        const tag = el.tagName.toLowerCase();
        const fmt = { ...formatting };

        if (tag === 'strong' || tag === 'b') {
          fmt.bold = true;
          // Apply strong element config if available
          if (elements?.strong) {
            const strongFont = elements.strong.font
              ? (fonts?.[elements.strong.font as keyof typeof fonts] || fonts?.heading || fonts?.body || body.font)
              : fmt.font;
            const strongColor = elements.strong.color ? resolveColorToHex(elements.strong.color) : fmt.color;
            if (elements.strong.font) fmt.font = strongFont;
            if (elements.strong.color) fmt.color = formatColor(strongColor);
            if (elements.strong.bold !== undefined) fmt.bold = elements.strong.bold;
          }
          console.log('STRONG TEXTRUN DOCX:', {
            text: el.textContent?.substring(0, 30) + (el.textContent && el.textContent.length > 30 ? '...' : ''),
            font: fmt.font,
            color: fmt.color,
            bold: fmt.bold,
            strongConfig: elements?.strong
          });
        }
        if (tag === 'em' || tag === 'i') fmt.italics = true;
        if (tag === 'u') fmt.underline = { type: UnderlineType.SINGLE };
        if (tag === 's' || tag === 'strike') fmt.strike = true;
        if (tag === 'sub') fmt.subScript = true;
        if (tag === 'sup') fmt.superScript = true;
        if (tag === 'code') {
          fmt.font = codeFontResolved;
          fmt.color = codeTextColor;
        }

        // Handle links
        if (tag === 'a') {
          const href = el.getAttribute('href');
          if (href && href.trim()) {
            const linkRuns: TextRun[] = [];
            const collectRuns = (node: Node, fmt: any) => {
              if (node.nodeType === Node.TEXT_NODE) {
                const text = node.textContent || '';
                if (text) {
                  const { allCaps: _, ...runProps } = fmt;
                  linkRuns.push(new TextRun({
                    text: transformText(text, fmt.allCaps),
                    ...runProps,
                    font: linkFont || fmt.font,
                    bold: linkBold || fmt.bold,
                    color: linkColor,
                    underline: { type: UnderlineType.SINGLE }
                  }));
                }
              } else if (node.nodeType === Node.ELEMENT_NODE) {
                const childEl = node as HTMLElement;
                const childTag = childEl.tagName.toLowerCase();
                const childFmt = { ...fmt };
                if (childTag === 'strong' || childTag === 'b') childFmt.bold = true;
                if (childTag === 'em' || childTag === 'i') childFmt.italics = true;
                if (childTag === 'code') {
                  childFmt.font = codeFontResolved;
                  childFmt.color = codeTextColor;
                }
                for (const child of Array.from(childEl.childNodes)) {
                  collectRuns(child, childFmt);
                }
              }
            };

            for (const child of Array.from(el.childNodes)) {
              collectRuns(child, fmt);
            }

            if (linkRuns.length > 0) {
              runs.push(new ExternalHyperlink({ children: linkRuns, link: href }));
            }
            return;
          }
        }

        const style = el.getAttribute('style') || '';
        const colorMatch = style.match(/color:\s*#?([a-fA-F0-9]{6})/);
        if (colorMatch) fmt.color = colorMatch[1];

        for (const child of Array.from(el.childNodes)) {
          walk(child, fmt);
        }
      }
    };

    // Start walking from the element itself to capture its own tag's formatting
    walk(element, formattingWithCaps);
    return runs;
  };

  const processCodeBlock = (preEl: HTMLElement): (Paragraph | Table)[] => {
    const codeEl = preEl.querySelector('code');
    const text = codeEl ? codeEl.textContent : preEl.textContent;
    const rawLines = (text || '').split('\n');
    if (rawLines.length > 0 && rawLines[rawLines.length - 1].trim() === '') {
      rawLines.pop();
    }

    const cfg = elements?.pre;
    const font = codeFontResolved;
    const preSize = cfg?.size || Math.max(8, body.size - 1);
    const size = pt(preSize);
    const padding = cfg?.padding || 12;

    const paragraphs = rawLines.map((line) => new Paragraph({
      children: [new TextRun({ text: line, font, size, color: codeTextColor })],
      spacing: {
        before: 0,
        after: 0,
        line: Math.round((cfg?.spacing?.line || 1.2) * 240),
      }
    }));

    const cellBorders: any = {};
    if (cfg?.border) {
      const b = { color: resolveColorToHex(cfg.border.color) || '000000', style: mapBorderStyle(cfg.border.style), size: cfg.border.width * 8 };
      cellBorders.top = b;
      cellBorders.bottom = b;
      cellBorders.left = b;
      cellBorders.right = b;
    }
    if (cfg?.borderTop) cellBorders.top = { color: resolveColorToHex(cfg.borderTop.color) || '000000', style: mapBorderStyle(cfg.borderTop.style), size: cfg.borderTop.width * 8 };
    if (cfg?.borderBottom) cellBorders.bottom = { color: resolveColorToHex(cfg.borderBottom.color) || '000000', style: mapBorderStyle(cfg.borderBottom.style), size: cfg.borderBottom.width * 8 };
    if (cfg?.borderLeft) cellBorders.left = { color: resolveColorToHex(cfg.borderLeft.color) || '000000', style: mapBorderStyle(cfg.borderLeft.style), size: cfg.borderLeft.width * 8 };
    if (cfg?.borderRight) cellBorders.right = { color: resolveColorToHex(cfg.borderRight.color) || '000000', style: mapBorderStyle(cfg.borderRight.style), size: cfg.borderRight.width * 8 };

    const table = new Table({
      rows: [
        new TableRow({
          children: [
            new TableCell({
              children: paragraphs,
              shading: { fill: codeBgColor, type: ShadingType.CLEAR },
              margins: {
                top: padding * 20,
                bottom: padding * 20,
                left: padding * 20,
                right: padding * 20,
              },
              borders: cellBorders
            })
          ]
        })
      ],
      width: { size: 100, type: WidthType.PERCENTAGE }
    });

    return [
      new Paragraph({ spacing: { before: (cfg?.spacing?.before || 12) * 20, after: 0 }, children: [] }),
      table,
      new Paragraph({ spacing: { before: 0, after: (cfg?.spacing?.after || 12) * 20 }, children: [] })
    ];
  };

  const processHeaderAsTable = (headerEl: HTMLElement, level: number, cfg: DocxStyleConfig): (Paragraph | Table)[] => {
    // Get element config for this header level
    const elementKey = `h${level}` as const;
    const elementCfg = elements?.[elementKey];

    // Resolve font
    const headerFont = cfg.font || body.font;
    const headerSize = pt(cfg.size || body.size);
    const headerColor = formatColor(resolveColorToHex(cfg.color || body.color));

    // Process text runs for the header content
    const runs = processTextRuns(headerEl, {
      font: headerFont,
      size: headerSize,
      color: headerColor,
      bold: cfg.bold || (level <= 2),
      allCaps: elementCfg?.allCaps
    }, elementKey);

    // Create paragraph for table cell
    const paragraph = new Paragraph({
      children: runs.length > 0 ? runs : [new TextRun({ text: headerEl.textContent || '', font: headerFont, size: headerSize, color: headerColor, bold: true })],
      alignment: mapAlignment(cfg.align),
      spacing: {
        before: 0,
        after: 0,
        line: Math.round((elementCfg?.spacing?.line || body.spacing?.line || 1.2) * 240)
      }
    });

    // Resolve background color
    const bgColor = resolveColorToHex(elementCfg?.background);
    const padding = elementCfg?.padding || 8;

    // Resolve borders - START WITH NIL (no borders) by default
    // Word tables have default borders, so we explicitly disable them unless configured
    const cellBorders: any = {
      top: { style: BorderStyle.NIL, size: 0, color: '000000' },
      bottom: { style: BorderStyle.NIL, size: 0, color: '000000' },
      left: { style: BorderStyle.NIL, size: 0, color: '000000' },
      right: { style: BorderStyle.NIL, size: 0, color: '000000' }
    };

    // Only apply borders if explicitly configured
    if (elementCfg?.border) {
      const b = { color: resolveColorToHex(elementCfg.border.color) || '000000', style: mapBorderStyle(elementCfg.border.style), size: (elementCfg.border.width || 1) * 8 };
      cellBorders.top = b;
      cellBorders.bottom = b;
      cellBorders.left = b;
      cellBorders.right = b;
    }
    if (elementCfg?.borderTop) cellBorders.top = { color: resolveColorToHex(elementCfg.borderTop.color) || '000000', style: mapBorderStyle(elementCfg.borderTop.style), size: (elementCfg.borderTop.width || 1) * 8 };
    if (elementCfg?.borderBottom) cellBorders.bottom = { color: resolveColorToHex(elementCfg.borderBottom.color) || '000000', style: mapBorderStyle(elementCfg.borderBottom.style), size: (elementCfg.borderBottom.width || 1) * 8 };
    if (elementCfg?.borderLeft) cellBorders.left = { color: resolveColorToHex(elementCfg.borderLeft.color) || '000000', style: mapBorderStyle(elementCfg.borderLeft.style), size: (elementCfg.borderLeft.width || 1) * 8 };
    if (elementCfg?.borderRight) cellBorders.right = { color: resolveColorToHex(elementCfg.borderRight.color) || '000000', style: mapBorderStyle(elementCfg.borderRight.style), size: (elementCfg.borderRight.width || 1) * 8 };

    const table = new Table({
      rows: [
        new TableRow({
          children: [
            new TableCell({
              children: [paragraph],
              shading: bgColor ? { fill: bgColor, type: ShadingType.CLEAR } : undefined,
              margins: {
                top: padding * 20,
                bottom: padding * 20,
                left: padding * 20,
                right: padding * 20,
              },
              borders: cellBorders
            })
          ]
        })
      ],
      width: { size: 100, type: WidthType.PERCENTAGE }
    });

    return [
      new Paragraph({ spacing: { before: (elementCfg?.spacing?.before || 16) * 20, after: 0 }, children: [] }),
      table,
      new Paragraph({ spacing: { before: 0, after: (elementCfg?.spacing?.after || 12) * 20 }, children: [] })
    ];
  };

  const processTable = (tableEl: HTMLElement): Table => {
    const rows: TableRow[] = [];

    console.log('TABLE DOCX: Processing table');

    // Get table-level border config
    const tableBorderConfig = elements?.table?.border;
    const tableBorderColor = resolveColorToHex(tableBorderConfig?.color) || (isDark ? '444444' : 'CCCCCC');
    const tableBorderWidth = tableBorderConfig?.width || 1;
    const tableBorderStyle = mapBorderStyle(tableBorderConfig?.style || 'single');

    // Create table-level borders (outer border only by default)
    const tableBorders = {
      top: { color: tableBorderColor, size: tableBorderWidth * 8, style: tableBorderStyle },
      bottom: { color: tableBorderColor, size: tableBorderWidth * 8, style: tableBorderStyle },
      left: { color: tableBorderColor, size: tableBorderWidth * 8, style: tableBorderStyle },
      right: { color: tableBorderColor, size: tableBorderWidth * 8, style: tableBorderStyle },
      insideHorizontal: { style: BorderStyle.NIL, size: 0 },
      insideVertical: { style: BorderStyle.NIL, size: 0 }
    };

    for (const rowEl of Array.from(tableEl.querySelectorAll('tr'))) {
      const cells: TableCell[] = [];

      for (const cellEl of Array.from(rowEl.querySelectorAll('th, td'))) {
        const cell = cellEl as HTMLElement;
        const isHeader = cell.tagName.toLowerCase() === 'th';

        // Get config from elements
        const thConfig = elements?.th;
        const tdConfig = elements?.td;
        const cellConfig = isHeader ? thConfig : tdConfig;

        // Resolve font
        const cellFont = cellConfig?.font
          ? resolveFont(cellConfig.font, fonts || {})
          : (isHeader ? (thConfig?.font ? resolveFont(thConfig.font, fonts || {}) : body.font) : body.font);

        const cellTextColor = formatColor(resolveColorToHex(cellConfig?.color || body.color));
        const cellRuns = processTextRuns(cell, {
          font: cellFont,
          size: pt(cellConfig?.size || body.size),
          color: cellTextColor,
          bold: isHeader || undefined
        });

        // Get background from config
        const cellBg = resolveColorToHex(cellConfig?.background);

        console.log(`TABLE CELL DOCX [${isHeader ? 'TH' : 'TD'}]:`, {
          text: cell.textContent?.substring(0, 30) + (cell.textContent && cell.textContent.length > 30 ? '...' : ''),
          font: cellFont,
          size: cellConfig?.size || body.size,
          color: cellTextColor,
          background: cellBg || 'none',
          bold: isHeader || undefined
        });

        // Resolve cell-specific borders from template
        const cellBorders: any = {};
        if (cellConfig?.border) {
          const b = { color: resolveColorToHex(cellConfig.border.color) || '000000', style: mapBorderStyle(cellConfig.border.style), size: (cellConfig.border.width || 1) * 8 };
          cellBorders.top = b;
          cellBorders.bottom = b;
          cellBorders.left = b;
          cellBorders.right = b;
        }
        if (cellConfig?.borderTop) cellBorders.top = { color: resolveColorToHex(cellConfig.borderTop.color) || '000000', style: mapBorderStyle(cellConfig.borderTop.style), size: (cellConfig.borderTop.width || 1) * 8 };
        if (cellConfig?.borderBottom) cellBorders.bottom = { color: resolveColorToHex(cellConfig.borderBottom.color) || '000000', style: mapBorderStyle(cellConfig.borderBottom.style), size: (cellConfig.borderBottom.width || 1) * 8 };
        if (cellConfig?.borderLeft) cellBorders.left = { color: resolveColorToHex(cellConfig.borderLeft.color) || '000000', style: mapBorderStyle(cellConfig.borderLeft.style), size: (cellConfig.borderLeft.width || 1) * 8 };
        if (cellConfig?.borderRight) cellBorders.right = { color: resolveColorToHex(cellConfig.borderRight.color) || '000000', style: mapBorderStyle(cellConfig.borderRight.style), size: (cellConfig.borderRight.width || 1) * 8 };

        cells.push(new TableCell({
          children: [new Paragraph({
            children: cellRuns.length > 0 ? cellRuns : [new TextRun({ text: cell.textContent || '' })],
            alignment: isHeader ? AlignmentType.CENTER : mapAlignment(cellConfig?.align),
            spacing: {
              after: 0,
              line: Math.round((body.spacing?.line || 1.2) * 240)
            }
          })],
          shading: cellBg ? { fill: cellBg, type: ShadingType.CLEAR } : undefined,
          verticalAlign: VerticalAlign.CENTER,
          margins: cellConfig?.padding ? {
            top: cellConfig.padding * 20,
            bottom: cellConfig.padding * 20,
            left: cellConfig.padding * 20,
            right: cellConfig.padding * 20
          } : undefined,
          borders: Object.keys(cellBorders).length > 0 ? cellBorders : undefined
        }));
      }

      rows.push(new TableRow({ children: cells }));
    }

    return new Table({
      rows,
      width: { size: 100, type: WidthType.PERCENTAGE },
      borders: tableBorders
    });
  };

  const processList = (listEl: HTMLElement, isOrdered: boolean, level: number = 0, listRef?: string): Paragraph[] => {
    const items: Paragraph[] = [];

    console.log('LIST DOCX:', {
      isOrdered,
      level,
      ulSpacing: elements?.ul?.spacing,
      liSpacing: elements?.li?.spacing,
      liFont: elements?.li?.font,
      liSize: elements?.li?.size,
      liColor: elements?.li?.color
    });

    let currentListRef = listRef;
    // We only inherit listRef if the target list matches the parent's type (Ordered vs Unordered)
    // This fixed the "lost numbering/bullets" when nesting UL inside OL
    const inheritedRef = listRef && ((listRef.startsWith('ordered') && isOrdered) || (listRef.startsWith('unordered') && !isOrdered));

    if (!inheritedRef) {
      if (isOrdered) {
        currentListRef = orderedListRefs.get(listEl);
        if (!currentListRef) {
          listCounter++;
          currentListRef = `ordered-${listCounter}`;
          orderedListRefs.set(listEl, currentListRef);

          const startVal = parseInt(listEl.getAttribute('start') || '1');
          const levels: any[] = [];
          for (let i = 0; i < 9; i++) {
            let format: any = LevelFormat.DECIMAL;
            let text = `%${i + 1}.`;
            if (i % 3 === 1) { format = LevelFormat.LOWER_LETTER; text = `%${i + 1}.`; }
            else if (i % 3 === 2) { format = LevelFormat.LOWER_ROMAN; text = `%${i + 1}.`; }
            levels.push({
              level: i, format, text, alignment: AlignmentType.LEFT,
              start: i === 0 ? startVal : 1,
              style: { paragraph: { indent: { left: 720 + (i * 360), hanging: 360 } } }
            });
          }
          numberingDefinitions.push({ reference: currentListRef, levels });
        }
      } else {
        currentListRef = unorderedListRefs.get(listEl);
        if (!currentListRef) {
          unorderedListCounter++;
          currentListRef = `unordered-${unorderedListCounter}`;
          unorderedListRefs.set(listEl, currentListRef);

          const levels: any[] = [];
          for (let i = 0; i < 9; i++) {
            let char = "\uf0b7"; // Level 0, 3, 6...
            let font = "Symbol";
            if (i % 3 === 1) { char = "\u006f"; font = "Courier New"; } // Level 1, 4, 7...
            else if (i % 3 === 2) { char = "\uf0a7"; font = "Wingdings"; } // Level 2, 5, 8...

            levels.push({
              level: i,
              format: LevelFormat.BULLET,
              text: char,
              alignment: AlignmentType.LEFT,
              style: {
                paragraph: { indent: { left: 720 + (i * 360), hanging: 360 } },
                run: { font, size: 18 } // 9pt in half-points
              }
            });
          }
          numberingDefinitions.push({ reference: currentListRef, levels });
        }
      }
    }

    let liIndex = 0;
    for (const li of Array.from(listEl.children)) {
      if (li.tagName.toLowerCase() !== 'li') continue;

      const liEl = li as HTMLElement;
      const liRuns: (TextRun | ExternalHyperlink)[] = [];
      const nested: Paragraph[] = [];

      // Base formatting for list items, can be overridden by inline elements
      const liBaseFormatting = {
        font: elements?.li?.font ? resolveFont(elements.li.font, fonts || {}) : body.font,
        size: pt(elements?.li?.size || body.size),
        color: formatColor(resolveColorToHex(elements?.li?.color || body.color)),
        bold: elements?.li?.bold,
        italics: elements?.li?.italic,
        allCaps: elements?.li?.allCaps
      };

      // Process all child nodes of the <li> element
      for (const child of Array.from(liEl.childNodes)) {
        const childEl = child as HTMLElement;
        const tagName = childEl.tagName?.toLowerCase();

        if (tagName === 'ul' || tagName === 'ol') {
          nested.push(...processList(childEl, tagName === 'ol', level + 1, currentListRef));
        } else if (child.nodeType === Node.ELEMENT_NODE) {
          // Pass liBaseFormatting to processTextRuns so it can be inherited/overridden
          const runs = processTextRuns(childEl, liBaseFormatting);
          liRuns.push(...runs);
        } else if (child.nodeType === Node.TEXT_NODE) {
          const text = child.textContent || '';
          if (text) {
            // Apply liBaseFormatting to text nodes, preserving spaces
            liRuns.push(new TextRun({
              text: transformText(text, liBaseFormatting.allCaps),
              font: liBaseFormatting.font,
              size: liBaseFormatting.size,
              color: liBaseFormatting.color,
              bold: liBaseFormatting.bold,
              italics: liBaseFormatting.italics
            }));
          }
        }
      }

      const liSpacingBefore = (elements?.li?.spacing?.before || 4) * 20;
      const liSpacingAfter = (elements?.li?.spacing?.after || 4) * 20;
      const liLineHeight = (elements?.li?.spacing?.line || body.spacing?.line || 1.2) * 240;

      // Log the actual text runs and their styling
      const liTextPreview = liEl.textContent?.trim().substring(0, 40) + (liEl.textContent && liEl.textContent.length > 40 ? '...' : '');
      const firstRunProps = liRuns.length > 0 ? {
        font: (liRuns[0] as any).font,
        size: (liRuns[0] as any).size,
        color: (liRuns[0] as any).color,
        bold: (liRuns[0] as any).bold
      } : null;

      console.log('LIST ITEM DOCX:', {
        isOrdered,
        level,
        hasListRef: !!currentListRef,
        listRef: currentListRef,
        text: liTextPreview,
        textRuns: liRuns.length,
        firstRunFont: firstRunProps?.font || body.font,
        firstRunColor: firstRunProps?.color || formatColor(resolveColorToHex(elements?.li?.color || body.color)),
        firstRunBold: firstRunProps?.bold,
        liColorFromConfig: elements?.li?.color,
        resolvedLiColor: formatColor(resolveColorToHex(elements?.li?.color || body.color)),
        spacingBefore: liSpacingBefore,
        spacingAfter: liSpacingAfter,
        line: liLineHeight,
        indentLeft: 720 + (level * 360)
      });

      items.push(new Paragraph({
        children: liRuns.length > 0 ? liRuns : [new TextRun({ text: liEl.textContent?.trim() || '' })],
        numbering: currentListRef ? { reference: currentListRef, level } : undefined,
        spacing: {
          before: liIndex === 0 ? liSpacingBefore : 0,
          after: liSpacingAfter,
          line: Math.round((elements?.li?.spacing?.line || body.spacing?.line || 1.2) * 240)
        },
        indent: { left: 720 + (level * 360), hanging: 360 }
      }));

      items.push(...nested);
      liIndex++;
    }

    return items;
  };

  const processNode = (node: Node): (Paragraph | Table)[] => {
    if (node.nodeType !== Node.ELEMENT_NODE) return [];

    const el = node as HTMLElement;
    const tag = el.tagName.toLowerCase();

    // DEBUG: Log first occurrence of each tag type
    if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'table', 'ul', 'ol', 'pre', 'blockquote', 'code'].includes(tag)) {
      if (!visitedTags.has(tag)) {
        visitedTags.add(tag);
        // We'll log the resolved config in the specific handlers below, or just log here what we found
        console.log(`DOCX GENERATOR FOUND First <${tag}>`);
      }
    }

    const results: (Paragraph | Table)[] = [];

    // Headings - use elements config directly
    if (tag.match(/^h[1-6]$/)) {
      const level = parseInt(tag.replace('h', ''));
      const elementKey = `h${level}` as keyof typeof elements;
      const elementCfg = elements?.[elementKey];

      // Build config from wordConfig (resolved colors) with element config for other properties
      // Priority: wordConfig colors > element config > defaults
      const baseCfg = level <= 2 ? (level === 1 ? heading1 : heading2) : body;

      // Use element config for font/size/color if available (for h3-h6)
      const fontName = elementCfg?.font
        ? (fonts?.[elementCfg.font as keyof typeof fonts] || (elementCfg.font === 'heading' ? fonts?.heading : elementCfg.font === 'body' ? fonts?.body : elementCfg.font))
        : baseCfg.font;
      const fontSize = elementCfg?.size ?? baseCfg.size;
      const fontColor = elementCfg?.color ? resolveColorToHex(elementCfg.color) : baseCfg.color;

      const cfg = {
        font: fontName,
        size: fontSize,
        color: fontColor,
        bold: elementCfg?.bold ?? baseCfg.bold,
        italic: elementCfg?.italic ?? baseCfg.italic,
        allCaps: elementCfg?.allCaps ?? baseCfg.allCaps,
        align: elementCfg?.align || baseCfg.align,
        spacing: elementCfg?.spacing || baseCfg.spacing
      };

      const finalColor = formatColor(resolveColorToHex(cfg.color));
      const bgColor = elementCfg?.background ? resolveColorToHex(elementCfg.background) : undefined;

      let runs = processTextRuns(el, {
        font: cfg.font,
        size: pt(cfg.size),
        color: finalColor,
        bold: cfg.bold,
        italics: cfg.italic,
        allCaps: cfg.allCaps
      }, tag);


      if (runs.length === 0) {
        const headingText = transformText(el.textContent || '', cfg.allCaps);
        runs.push(new TextRun({
          text: headingText,
          font: cfg.font,
          size: pt(cfg.size),
          color: finalColor,
          bold: cfg.bold
        }));
      }

      // Build borders from element config
      const border: any = {};
      let borderDebug: any = null;
      if (elementCfg?.border) {
        const borderColor = resolveColorToHex(elementCfg.border.color);
        border.top = { color: borderColor, space: 4, style: mapBorderStyle(elementCfg.border.style), size: elementCfg.border.width * 8 };
        border.bottom = { color: borderColor, space: 4, style: mapBorderStyle(elementCfg.border.style), size: elementCfg.border.width * 8 };
        border.left = { color: borderColor, space: 4, style: mapBorderStyle(elementCfg.border.style), size: elementCfg.border.width * 8 };
        border.right = { color: borderColor, space: 4, style: mapBorderStyle(elementCfg.border.style), size: elementCfg.border.width * 8 };
        borderDebug = { color: borderColor, width: elementCfg.border.width, style: elementCfg.border.style, sides: 'all' };
      }
      if (elementCfg?.borderLeft) {
        const borderLeftColor = resolveColorToHex(elementCfg.borderLeft.color) || '000000';
        border.left = { color: borderLeftColor, space: 4, style: mapBorderStyle(elementCfg.borderLeft.style), size: elementCfg.borderLeft.width * 8 };
      }
      if (elementCfg?.borderTop) {
        const borderTopColor = resolveColorToHex(elementCfg.borderTop.color) || '000000';
        border.top = { color: borderTopColor, space: 4, style: mapBorderStyle(elementCfg.borderTop.style), size: elementCfg.borderTop.width * 8 };
      }
      if (elementCfg?.borderBottom) {
        const borderBottomColor = resolveColorToHex(elementCfg.borderBottom.color) || '000000';
        border.bottom = { color: borderBottomColor, space: 4, style: mapBorderStyle(elementCfg.borderBottom.style), size: elementCfg.borderBottom.width * 8 };
      }
      if (elementCfg?.borderRight) {
        const borderRightColor = resolveColorToHex(elementCfg.borderRight.color) || '000000';
        border.right = { color: borderRightColor, space: 4, style: mapBorderStyle(elementCfg.borderRight.style), size: elementCfg.borderRight.width * 8 };
      }

      // Log configuration if this is the first time seeing this heading level
      const debugKey = `h${level} -debug`;
      if (!visitedTags.has(debugKey)) {
        visitedTags.add(debugKey);
        console.log(`DOCX H${level} CONFIG:`, {
          font: cfg.font,
          size: cfg.size,
          color: finalColor,
          background: bgColor,
          border: borderDebug,
          bold: cfg.bold,
          allCaps: cfg.allCaps,
          spacing: cfg.spacing
        });
      }

      if (borderDebug && level === 3) {
        console.log('h3 BORDER DOCX:', borderDebug);
      }

      // Use table-cell rendering if useTableHeaders is true
      if (options.useTableHeaders) {
        results.push(...processHeaderAsTable(el, level, cfg));
        return results;
      }

      // Otherwise use native Word heading styling
      // Map heading levels
      const headingLevels: Record<number, any> = {
        1: HeadingLevel.HEADING_1,
        2: HeadingLevel.HEADING_2,
        3: HeadingLevel.HEADING_3,
        4: HeadingLevel.HEADING_4,
        5: HeadingLevel.HEADING_5,
        6: HeadingLevel.HEADING_6
      };

      results.push(new Paragraph({
        children: runs,
        heading: headingLevels[level],
        alignment: mapAlignment(cfg.align),
        spacing: cfg.spacing ? {
          before: (cfg.spacing.before || 0) * 20,
          after: (cfg.spacing.after || 0) * 20,
          line: Math.round((cfg.spacing.line || 1.2) * 240)
        } : undefined,
        border: Object.keys(border).length > 0 ? border : undefined,
        shading: bgColor ? { fill: formatColor(bgColor), type: ShadingType.CLEAR } : undefined
      }));
      return results;
    }

    // Code blocks
    if (tag === 'pre') {
      results.push(...processCodeBlock(el));
      return results;
    }

    // Tables
    if (tag === 'table') {
      results.push(processTable(el));
      return results;
    }

    // Paragraphs
    if (tag === 'p') {
      const runs = processTextRuns(el, {
        font: body.font,
        size: pt(body.size),
        color: formatColor(resolveColorToHex(body.color))
      });

      const style = el.getAttribute('style') || '';
      const bgMatch = style.match(/background(?:-color)?:\s*#?([a-fA-F0-9]{6})/);

      const debugKey = 'p-debug';
      if (!visitedTags.has(debugKey)) {
        visitedTags.add(debugKey);
        console.log('DOCX P CONFIG:', {
          font: body.font,
          size: body.size,
          color: formatColor(resolveColorToHex(body.color)),
          align: body.align,
          spacing: body.spacing,
          shading: bgMatch ? { fill: bgMatch[1], type: 'clear' } : undefined
        });
      }

      results.push(new Paragraph({
        children: runs.length > 0 ? runs : [new TextRun({ text: el.textContent || '' })],
        alignment: mapAlignment(body.align),
        spacing: {
          before: (body.spacing?.before || 0) * 20,
          after: (body.spacing?.after || 0) * 20,
          line: Math.round((body.spacing?.line || 1.2) * 240)
        },
        shading: bgMatch ? { fill: formatColor(resolveColorToHex(bgMatch[1])), type: ShadingType.CLEAR } : undefined
      }));
      return results;
    }

    // Blockquotes
    if (tag === 'blockquote') {
      const bqConfig = elements?.blockquote;
      const runs = processTextRuns(el, {
        font: bqConfig?.font ? resolveFont(bqConfig.font, fonts || {}) : body.font,
        size: pt(bqConfig?.size || body.size),
        color: formatColor(resolveColorToHex(bqConfig?.color || body.color)),
        italics: true
      });

      const borderColor = resolveColorToHex(bqConfig?.borderLeft?.color) || accentColor;
      const borderWidth = bqConfig?.borderLeft?.width || 3;

      const debugKey = 'blockquote-debug';
      if (!visitedTags.has(debugKey)) {
        visitedTags.add(debugKey);
        console.log('DOCX BLOCKQUOTE CONFIG:', {
          font: bqConfig?.font,
          size: bqConfig?.size,
          color: formatColor(resolveColorToHex(bqConfig?.color)),
          border: { color: borderColor, width: borderWidth },
          background: bqConfig?.background
        });
      }

      results.push(new Paragraph({
        children: runs,
        indent: { left: 720 },
        border: { left: { color: borderColor, space: 10, style: BorderStyle.SINGLE, size: borderWidth * 8 } },
        shading: bqConfig?.background ? { fill: resolveColorToHex(bqConfig.background), type: ShadingType.CLEAR } : (isDark ? undefined : { fill: 'F8F8F8', type: ShadingType.CLEAR }),
        spacing: {
          before: (bqConfig?.spacing?.before || 12) * 20,
          after: (bqConfig?.spacing?.after || 12) * 20,
          line: Math.round((bqConfig?.spacing?.line || body.spacing?.line || 1.2) * 240)
        }
      }));
      return results;
    }

    // Lists
    if (tag === 'ul' || tag === 'ol') {
      results.push(...processList(el, tag === 'ol', 0));
      return results;
    }

    // HR
    if (tag === 'hr') {
      const hrConfig = elements?.hr;
      results.push(new Paragraph({
        border: {
          bottom: {
            color: resolveColorToHex(hrConfig?.border?.color) || accentColor,
            space: 1,
            style: mapBorderStyle(hrConfig?.border?.style || 'single'),
            size: (hrConfig?.border?.width || 1) * 6
          }
        },
        spacing: {
          before: (hrConfig?.spacing?.before || 12) * 20,
          after: (hrConfig?.spacing?.after || 12) * 20
        }
      }));
      return results;
    }

    // Default: process children
    for (const child of Array.from(el.childNodes)) {
      results.push(...processNode(child));
    }
    return results;
  };

  // Process all nodes
  for (const node of Array.from(doc.body.childNodes)) {
    children.push(...processNode(node));
  }

  // Build document options
  const documentOptions: any = {
    styles: {
      default: {
        document: {
          run: { font: body.font, size: pt(body.size), color: formatColor(resolveColorToHex(body.color)) }
        }
      }
    },
    numbering: numberingDefinitions.length > 0 ? { config: numberingDefinitions } : undefined,
    sections: [{
      properties: {
        page: {
          size: {
            width: paperSize === 'A4' ? mmToTwips(210) : inchesToTwips(8.5),
            height: paperSize === 'A4' ? mmToTwips(297) : inchesToTwips(11)
          },
          margin: {
            top: (options.page?.margins?.top || 72) * 20,
            right: (options.page?.margins?.right || 72) * 20,
            bottom: (options.page?.margins?.bottom || 72) * 20,
            left: (options.page?.margins?.left || 72) * 20
          }
        }
      },
      children
    }]
  };

  // Add page background if specified
  if (pageBackground) {
    documentOptions.background = { color: formatColor(pageBackground) };
  }

  // Debug: log document options
  console.log('DOCUMENT OPTIONS:', JSON.stringify({
    hasNumbering: !!documentOptions.numbering,
    numberingCount: numberingDefinitions.length,
    numberingRefs: numberingDefinitions.map((n: any) => n.reference),
    childrenCount: children.length,
    firstChildType: children[0]?.constructor?.name
  }));

  const document = new Document(documentOptions);
  return await Packer.toBlob(document);
};
