import React, { forwardRef, useImperativeHandle } from 'react';
import { jsPDF } from 'jspdf';
import autoTable from 'jspdf-autotable';
import { sanitizeRichTextHtml } from '@/lib/customFormHtmlSanitizer';

const PAGE_SIZE = 'letter';
const PAGE_UNIT = 'pt';
const PAGE_MARGIN = 54;
const FOOTER_HEIGHT = 34;
const BODY_FONT_SIZE = 11;
const LINE_HEIGHT_RATIO = 1.45;
const MIN_LINE_HEIGHT_RATIO = 1.1;
const MAX_LINE_HEIGHT_RATIO = 1.9;
const DEFAULT_BLOCK_SPACING = 8;
const MAX_BLOCK_SPACING = 24;
const CONTAINER_TAGS = new Set(['div', 'section', 'article', 'aside']);
const NESTED_BLOCK_TAGS = new Set([
  'p',
  'div',
  'section',
  'article',
  'aside',
  'blockquote',
  'pre',
  'h1',
  'h2',
  'h3',
  'h4',
  'h5',
  'h6',
  'ul',
  'ol',
  'table',
  'hr',
  'img',
]);
const SKIPPABLE_TAGS = new Set(['style', 'script', 'meta', 'link', 'title', 'xml', 'head']);

function resolveContent(html = '', data = {}) {
  if (!html) return '';

  let out = html.replace(/<span[^>]*>\s*(\{\{[\w.]+\}\})\s*<\/span>/g, '$1');

  if (data && typeof data === 'object') {
    out = out.replace(/\{\{([\w.]+)\}\}/g, (match, key) => {
      const value = data[key];
      return value != null && value !== '' ? String(value) : match;
    });
  }

  return out.replace(/<span\s[^>]*class="var-pill"[^>]*>([\s\S]*?)<\/span>/g, '$1');
}

function stripHtml(html = '') {
  return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim();
}

function cssSizeToPt(value) {
  if (!value) return null;
  const match = String(value).trim().match(/^([\d.]+)(px|pt|em|rem)?$/i);
  if (!match) return null;

  const amount = parseFloat(match[1]);
  const unit = (match[2] || 'px').toLowerCase();

  if (unit === 'pt') return amount;
  if (unit === 'em' || unit === 'rem') return amount * 12;
  return amount * 0.75;
}

function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

function getSpacingInPt(value) {
  const size = cssSizeToPt(value);
  if (size == null) return null;
  return clamp(size, 0, MAX_BLOCK_SPACING);
}

function getLineHeightRatio(value, fontSize = BODY_FONT_SIZE) {
  if (!value) return null;

  const raw = String(value).trim().toLowerCase();
  if (!raw || raw === 'normal') return null;

  if (/^[\d.]+$/.test(raw)) {
    return clamp(Number(raw), MIN_LINE_HEIGHT_RATIO, MAX_LINE_HEIGHT_RATIO);
  }

  if (raw.endsWith('%')) {
    const amount = Number(raw.replace('%', ''));
    if (!Number.isNaN(amount)) {
      return clamp(amount / 100, MIN_LINE_HEIGHT_RATIO, MAX_LINE_HEIGHT_RATIO);
    }
  }

  const size = cssSizeToPt(raw);
  if (size != null && fontSize > 0) {
    return clamp(size / fontSize, MIN_LINE_HEIGHT_RATIO, MAX_LINE_HEIGHT_RATIO);
  }

  return null;
}

function parseColor(value) {
  if (!value) return null;
  const color = String(value).trim();

  const hex = color.match(/^#([\da-f]{3}|[\da-f]{6})$/i);
  if (hex) {
    const raw = hex[1];
    const full = raw.length === 3 ? raw.split('').map((c) => c + c).join('') : raw;
    return [
      parseInt(full.slice(0, 2), 16),
      parseInt(full.slice(2, 4), 16),
      parseInt(full.slice(4, 6), 16),
    ];
  }

  const rgb = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
  if (rgb) {
    return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])];
  }

  return null;
}

function mapFontToJsPdf(fontFamily) {
  if (!fontFamily) return 'helvetica';
  const normalized = String(fontFamily).toLowerCase();
  if (normalized.includes('times') || normalized.includes('georgia') || normalized.includes('garamond')) return 'times';
  if (normalized.includes('courier') || normalized.includes('mono')) return 'courier';
  return 'helvetica';
}

function getExplicitBlockAlignment(element) {
  if (!element) return null;
  const cls = element.classList?.toString?.() || '';
  if (cls.includes('ql-align-center')) return 'center';
  if (cls.includes('ql-align-right')) return 'right';
  if (cls.includes('ql-align-justify')) return 'justify';
  const textAlign = element.style?.textAlign || element.getAttribute?.('align');
  return textAlign || null;
}

function getBlockAlignment(element, fallback = 'left') {
  return getExplicitBlockAlignment(element) || fallback;
}

function getBlockIndent(element) {
  if (!element) return 0;

  const cls = element.classList?.toString?.() || '';
  const match = cls.match(/ql-indent-(\d+)/);
  const classIndent = (match ? Number(match[1]) || 0 : 0) * 18;
  const marginIndent = getSpacingInPt(element.style?.marginLeft) || 0;
  const paddingIndent = getSpacingInPt(element.style?.paddingLeft) || 0;
  const textIndent = getSpacingInPt(element.style?.textIndent) || 0;

  return classIndent + marginIndent + paddingIndent + textIndent;
}

function getBlockSpacing(tag, element) {
  const marginTop = getSpacingInPt(element?.style?.marginTop) || 0;
  const marginBottom = getSpacingInPt(element?.style?.marginBottom);
  const isHeading = /^h[1-6]$/i.test(tag);

  return {
    spacingBefore: marginTop,
    spacingAfter: marginBottom ?? (isHeading ? 10 : DEFAULT_BLOCK_SPACING),
    lineHeight: element?.style?.lineHeight || null,
  };
}

function isPageBreakElement(element) {
  return Boolean(element?.classList?.contains('page-break-marker') || element?.dataset?.pageBreak === 'true');
}

async function toDataUrl(url) {
  if (!url) return null;
  if (url.startsWith('data:')) return url;

  try {
    const response = await fetch(url, { mode: 'cors' });
    const blob = await response.blob();

    return await new Promise((resolve) => {
      const reader = new FileReader();
      reader.onloadend = () => resolve(reader.result);
      reader.onerror = () => resolve(null);
      reader.readAsDataURL(blob);
    });
  } catch {
    return null;
  }
}

function getImageFormat(dataUrl = '') {
  if (dataUrl.startsWith('data:image/png')) return 'PNG';
  if (dataUrl.startsWith('data:image/webp')) return 'WEBP';
  return 'JPEG';
}

function createBaseStyle(defaultFont) {
  return {
    font: defaultFont,
    size: BODY_FONT_SIZE,
    bold: false,
    italic: false,
    underline: false,
    strikethrough: false,
    color: null,
  };
}

function getOrderedListPrefix(index, markerStyle = 'decimal') {
  if (markerStyle === 'lower-alpha' || markerStyle === 'upper-alpha') {
    let value = Math.max(1, index);
    let output = '';

    while (value > 0) {
      value -= 1;
      output = String.fromCharCode(97 + (value % 26)) + output;
      value = Math.floor(value / 26);
    }

    return `${markerStyle === 'upper-alpha' ? output.toUpperCase() : output}. `;
  }

  if (markerStyle === 'lower-roman' || markerStyle === 'upper-roman') {
    const numerals = [
      ['M', 1000], ['CM', 900], ['D', 500], ['CD', 400], ['C', 100], ['XC', 90],
      ['L', 50], ['XL', 40], ['X', 10], ['IX', 9], ['V', 5], ['IV', 4], ['I', 1],
    ];
    let value = Math.max(1, index);
    let output = '';

    numerals.forEach(([symbol, amount]) => {
      while (value >= amount) {
        output += symbol;
        value -= amount;
      }
    });

    const normalized = markerStyle === 'lower-roman' ? output.toLowerCase() : output;
    return `${normalized}. `;
  }

  return `${index}. `;
}

function getListMarkerStyle(tag, depth = 0) {
  if (tag === 'ul') {
    return ['disc', 'circle', 'square'][depth % 3] || 'disc';
  }

  return ['decimal', 'lower-alpha', 'lower-roman'][depth % 3] || 'decimal';
}

function getOrderedListStartValue(element) {
  const start = Number.parseInt(element?.getAttribute?.('start') || '1', 10);
  return Number.isFinite(start) && start > 0 ? start : 1;
}

function extendSegmentStyle(element, inheritedStyle, defaultFont) {
  const style = { ...inheritedStyle };
  style.font = style.font || defaultFont;

  for (const cls of element.classList) {
    if (cls.startsWith('ql-font-')) {
      style.font = mapFontToJsPdf(cls.replace('ql-font-', ''));
    }
    if (cls.startsWith('ql-size-')) {
      const size = cssSizeToPt(cls.replace('ql-size-', ''));
      if (size) style.size = size;
    }
  }

  if (element.style.fontFamily) style.font = mapFontToJsPdf(element.style.fontFamily);
  if (element.style.fontSize) {
    const size = cssSizeToPt(element.style.fontSize);
    if (size) style.size = size;
  }
  if (element.style.color) {
    const color = parseColor(element.style.color);
    if (color) style.color = color;
  }

  const fontWeight = String(element.style.fontWeight || '').toLowerCase();
  if (fontWeight === 'bold' || (Number.isFinite(Number(fontWeight)) && Number(fontWeight) >= 600)) {
    style.bold = true;
  }

  const fontStyle = String(element.style.fontStyle || '').toLowerCase();
  if (fontStyle === 'italic' || fontStyle === 'oblique') {
    style.italic = true;
  }

  const textDecoration = String(element.style.textDecoration || element.style.textDecorationLine || '').toLowerCase();
  if (textDecoration.includes('underline')) style.underline = true;
  if (textDecoration.includes('line-through')) style.strikethrough = true;

  const tag = element.tagName.toLowerCase();
  if (tag === 'strong' || tag === 'b') style.bold = true;
  if (tag === 'em' || tag === 'i') style.italic = true;
  if (tag === 'u') style.underline = true;
  if (tag === 's' || tag === 'strike' || tag === 'del') style.strikethrough = true;
  if (tag === 'a') {
    style.underline = true;
    style.color = style.color || [37, 99, 235];
  }
  if (tag === 'h1') { style.size = 24; style.bold = true; }
  if (tag === 'h2') { style.size = 18; style.bold = true; }
  if (tag === 'h3') { style.size = 14; style.bold = true; }
  if (tag === 'h4') { style.size = 12; style.bold = true; }

  return style;
}

function parseInlineSegments(node, inheritedStyle, defaultFont) {
  if (node.nodeType === Node.TEXT_NODE) {
    const normalizedText = String(node.textContent || '').replace(/[\t\r\n]+/g, ' ');
    return normalizedText ? [{ text: normalizedText, ...inheritedStyle }] : [];
  }

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

  const element = node;
  const tag = element.tagName.toLowerCase();

  if (SKIPPABLE_TAGS.has(tag)) {
    return [];
  }

  if (tag === 'br') {
    return [{ text: '\n', ...inheritedStyle }];
  }

  if (isPageBreakElement(element)) {
    return [{ pageBreak: true }];
  }

  const style = extendSegmentStyle(element, inheritedStyle, defaultFont);

  const segments = [];
  for (const child of element.childNodes) {
    segments.push(...parseInlineSegments(child, style, style.font || defaultFont));
  }
  return segments;
}

function parseTable(element) {
  const rows = [...element.querySelectorAll('tr')].map((row) =>
    [...row.children].map((cell) => stripHtml(cell.innerHTML || cell.textContent || '')),
  );

  const headerRows = rows.filter((_, index) => index === 0 && element.querySelector('th'));
  const bodyRows = rows.slice(headerRows.length);

  return { type: 'table', headerRows, bodyRows };
}

function createBlockContext(defaultFont) {
  return {
    align: 'left',
    indent: 0,
    listDepth: 0,
    lineHeight: null,
    baseStyle: createBaseStyle(defaultFont),
  };
}

function extendBlockContext(context, element, defaultFont) {
  return {
    align: getBlockAlignment(element, context.align || 'left'),
    indent: (context.indent || 0) + getBlockIndent(element),
    lineHeight: element.style?.lineHeight || context.lineHeight || null,
    baseStyle: extendSegmentStyle(element, context.baseStyle, defaultFont),
  };
}

function hasNestedBlockChildren(element) {
  return [...element.children].some((child) => {
    if (isPageBreakElement(child)) return true;
    return NESTED_BLOCK_TAGS.has(child.tagName.toLowerCase());
  });
}

function pushTextNodeBlock(blocks, text, context) {
  const value = String(text || '').replace(/\s+/g, ' ').trim();
  if (!value) return;

  blocks.push({
    type: 'text',
    segments: [{ ...context.baseStyle, text: value }],
    align: context.align || 'left',
    indent: context.indent || 0,
    spacingBefore: 0,
    spacingAfter: DEFAULT_BLOCK_SPACING,
    lineHeight: context.lineHeight || null,
  });
}

function appendBlocksFromNode(blocks, node, defaultFont, context) {
  if (node.nodeType === Node.TEXT_NODE) {
    pushTextNodeBlock(blocks, node.textContent, context);
    return;
  }

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

  const element = node;
  const tag = element.tagName.toLowerCase();

  if (SKIPPABLE_TAGS.has(tag)) return;

  if (isPageBreakElement(element)) {
    blocks.push({ type: 'pageBreak' });
    return;
  }

  if (tag === 'table') {
    blocks.push(parseTable(element));
    return;
  }

  if (tag === 'hr') {
    blocks.push({ type: 'divider' });
    return;
  }

  if (tag === 'img') {
    blocks.push({ type: 'image', src: element.getAttribute('src'), alt: element.getAttribute('alt') || '' });
    return;
  }

  const nextContext = extendBlockContext(context, element, defaultFont);

  if (tag === 'ol' || tag === 'ul') {
    const listSpacing = getBlockSpacing(tag, element);
    const listItems = [...element.children].filter((li) => li.tagName.toLowerCase() === 'li');
    const listDepth = context.listDepth || 0;
    const listMarkerStyle = getListMarkerStyle(tag, listDepth);
    let index = tag === 'ol' ? getOrderedListStartValue(element) - 1 : 0;

    for (const li of listItems) {
      const explicitValue = tag === 'ol' ? Number.parseInt(li.getAttribute('value') || '', 10) : Number.NaN;
      index = Number.isFinite(explicitValue) && explicitValue > 0 ? explicitValue : index + 1;
      const itemContext = extendBlockContext(nextContext, li, defaultFont);
      const itemSpacing = getBlockSpacing('li', li);
      const itemAlign = itemContext.align === 'justify' ? 'left' : (itemContext.align || 'left');
      const segments = li.childNodes.length
        ? [...li.childNodes].flatMap((child) => {
            if (child.nodeType === Node.ELEMENT_NODE) {
              const childTag = child.tagName.toLowerCase();
              if (childTag === 'ol' || childTag === 'ul') return [];
            }

            return parseInlineSegments(child, itemContext.baseStyle, itemContext.baseStyle.font || defaultFont);
          })
        : parseInlineSegments(li, itemContext.baseStyle, itemContext.baseStyle.font || defaultFont);

      const textSegments = segments.filter((segment) => !segment.pageBreak);

      if (textSegments.length > 0 || li.childNodes.length === 0) {
        blocks.push({
          type: 'text',
          segments: textSegments.length ? textSegments : [{ ...itemContext.baseStyle, text: '' }],
          align: itemAlign,
          indent: itemContext.indent,
          spacingBefore: index === 1 ? Math.max(listSpacing.spacingBefore, itemSpacing.spacingBefore) : 2,
          spacingAfter: 4,
          lineHeight: itemSpacing.lineHeight || listSpacing.lineHeight || itemContext.lineHeight,
          listType: tag === 'ol' ? 'ordered' : 'bullet',
          listMarkerStyle,
          listIndex: index,
        });
      }

      const nestedListContext = { ...itemContext, align: itemAlign, indent: itemContext.indent + 18, listDepth: listDepth + 1 };
      const nestedLists = [...li.children].filter((child) => {
        const childTag = child.tagName.toLowerCase();
        return childTag === 'ol' || childTag === 'ul';
      });

      nestedLists.forEach((child, nestedIndex) => {
        appendBlocksFromNode(blocks, child, defaultFont, nestedListContext);

        if (nestedIndex === nestedLists.length - 1) {
          const lastBlock = blocks[blocks.length - 1];
          if (lastBlock?.type === 'text') {
            lastBlock.spacingAfter = Math.max(lastBlock.spacingAfter ?? 0, index === listItems.length ? Math.max(listSpacing.spacingAfter, itemSpacing.spacingAfter) : 4);
          }
        }
      });
    }

    return;
  }

  if (CONTAINER_TAGS.has(tag) && hasNestedBlockChildren(element)) {
    for (const child of element.childNodes) {
      appendBlocksFromNode(blocks, child, defaultFont, nextContext);
    }
    return;
  }

  const segments = parseInlineSegments(element, nextContext.baseStyle, nextContext.baseStyle.font || defaultFont);
  const pageBreaks = segments.some((segment) => segment.pageBreak);
  const textSegments = segments.filter((segment) => !segment.pageBreak);
  const spacing = getBlockSpacing(tag, element);

  if (pageBreaks) blocks.push({ type: 'pageBreak' });

  blocks.push({
    type: 'text',
    segments: textSegments.length ? textSegments : [{ ...nextContext.baseStyle, text: '' }],
    align: nextContext.align || 'left',
    indent: nextContext.indent,
    spacingBefore: spacing.spacingBefore,
    spacingAfter: spacing.spacingAfter,
    lineHeight: spacing.lineHeight || nextContext.lineHeight || null,
    isHeading: /^h[1-6]$/i.test(tag),
  });
}

function parseHtmlToBlocks(html, defaultFont) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(`<div>${html || ''}</div>`, 'text/html');
  const root = doc.body.firstChild;
  const blocks = [];

  if (!root) return blocks;

  const baseContext = createBlockContext(defaultFont);

  for (const child of root.childNodes) {
    appendBlocksFromNode(blocks, child, defaultFont, baseContext);
  }

  return blocks;
}

function setSegmentStyle(doc, segment, fallbackFont) {
  const fontName = segment.font || fallbackFont || 'helvetica';
  let fontStyle = 'normal';

  if (segment.bold && segment.italic) fontStyle = 'bolditalic';
  else if (segment.bold) fontStyle = 'bold';
  else if (segment.italic) fontStyle = 'italic';

  doc.setFont(fontName, fontStyle);
  doc.setFontSize(segment.size || BODY_FONT_SIZE);

  if (segment.color) {
    doc.setTextColor(segment.color[0], segment.color[1], segment.color[2]);
  } else {
    doc.setTextColor(17, 17, 17);
  }
}

function measureText(doc, text, style, fallbackFont) {
  setSegmentStyle(doc, style, fallbackFont);
  return doc.getTextWidth(text);
}

function trimLineTokens(tokens) {
  const next = [...tokens];
  while (next.length && /^\s+$/.test(next[next.length - 1].text)) next.pop();
  return next;
}

function splitLongToken(doc, token, maxWidth, fallbackFont) {
  const chars = [...token.text];
  const chunks = [];
  let current = '';

  chars.forEach((char) => {
    const test = current + char;
    const width = measureText(doc, test, token, fallbackFont);
    if (current && width > maxWidth) {
      chunks.push({ ...token, text: current });
      current = char;
    } else {
      current = test;
    }
  });

  if (current) chunks.push({ ...token, text: current });
  return chunks;
}

function tokenizeSegments(doc, segments, maxWidth, fallbackFont) {
  const tokens = [];

  segments.forEach((segment) => {
    const pieces = String(segment.text || '').split(/(\n)/);
    pieces.forEach((piece) => {
      if (piece === '\n') {
        tokens.push({ type: 'break' });
        return;
      }

      const parts = piece.split(/(\s+)/).filter(Boolean);
      parts.forEach((part) => {
        const token = { ...segment, text: part };
        const width = measureText(doc, part, token, fallbackFont);
        if (!/^\s+$/.test(part) && width > maxWidth) {
          splitLongToken(doc, token, maxWidth, fallbackFont).forEach((chunk) => tokens.push(chunk));
        } else {
          tokens.push(token);
        }
      });
    });
  });

  return tokens;
}

function buildLines(doc, segments, maxWidth, fallbackFont) {
  const tokens = tokenizeSegments(doc, segments, maxWidth, fallbackFont);
  const lines = [];
  let current = [];
  let currentWidth = 0;

  const pushLine = (endsWithBreak = false) => {
    lines.push({ tokens: trimLineTokens(current), endsWithBreak });
    current = [];
    currentWidth = 0;
  };

  tokens.forEach((token) => {
    if (token.type === 'break') {
      pushLine(true);
      return;
    }

    if (/^\s+$/.test(token.text) && current.length === 0) return;

    const width = measureText(doc, token.text, token, fallbackFont);

    if (currentWidth + width > maxWidth && current.length > 0 && !/^\s+$/.test(token.text)) {
      pushLine();
    }

    if (/^\s+$/.test(token.text) && current.length === 0) return;

    current.push(token);
    currentWidth += width;
  });

  if (current.length || lines.length === 0) pushLine();
  return lines;
}

function measureLine(doc, line, fallbackFont) {
  return line.reduce((total, token) => total + measureText(doc, token.text, token, fallbackFont), 0);
}

function renderLine(doc, line, x, y, maxWidth, align, fallbackFont, justify) {
  const lineWidth = measureLine(doc, line, fallbackFont);
  let cursorX = x;

  if (align === 'center') cursorX = x + (maxWidth - lineWidth) / 2;
  if (align === 'right') cursorX = x + maxWidth - lineWidth;

  const whitespaceTokens = justify ? line.filter((token) => /^\s+$/.test(token.text)).length : 0;
  const extraGap = justify && whitespaceTokens > 0 ? (maxWidth - lineWidth) / whitespaceTokens : 0;

  line.forEach((token) => {
    const width = measureText(doc, token.text, token, fallbackFont);
    if (!/^\s+$/.test(token.text)) {
      setSegmentStyle(doc, token, fallbackFont);
      doc.text(token.text, cursorX, y);

      if (token.underline) {
        doc.setLineWidth(0.5);
        doc.line(cursorX, y + 1.5, cursorX + width, y + 1.5);
      }

      if (token.strikethrough) {
        doc.setLineWidth(0.5);
        doc.line(cursorX, y - (token.size || BODY_FONT_SIZE) * 0.28, cursorX + width, y - (token.size || BODY_FONT_SIZE) * 0.28);
      }
    }

    cursorX += width + (/^\s+$/.test(token.text) ? extraGap : 0);
  });
}

function drawBorder(doc, pageWidth, pageHeight, borderConfig) {
  if (!borderConfig?.enabled) return;
  doc.setDrawColor(borderConfig.color || '#000000');
  doc.setLineWidth(1);
  doc.rect(22, 22, pageWidth - 44, pageHeight - 44);
}

function drawFooter(doc, pageWidth, pageHeight, footerConfig, footerStyles, totalPages, pageNum, sampleData) {
  if (footerConfig?.enabled === false) return;

  const footerY = pageHeight - 24;
  const today = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
  const centerText = stripHtml(footerConfig?.content || '');
  const fontName = mapFontToJsPdf(footerStyles?.fontFamily);
  const fontSize = Math.max(7, Number(footerStyles?.fontSize) || 8);

  doc.setDrawColor(203, 213, 225);
  doc.setLineWidth(0.5);
  doc.line(PAGE_MARGIN, footerY - 10, pageWidth - PAGE_MARGIN, footerY - 10);

  doc.setFont(fontName, 'normal');
  doc.setFontSize(fontSize);
  doc.setTextColor(100, 116, 139);

  if (footerConfig?.showDate) {
    doc.text(today, PAGE_MARGIN, footerY);
  }

  if (centerText) {
    doc.text(centerText, pageWidth / 2, footerY, { align: 'center' });
  }

  if (footerConfig?.showPageNumbers !== false) {
    doc.text(`Page ${pageNum} of ${totalPages}`, pageWidth - PAGE_MARGIN, footerY, { align: 'right' });
  }

  doc.setTextColor(17, 17, 17);
  if (sampleData?.clientName && !centerText) {
    doc.text(String(sampleData.clientName), pageWidth / 2, footerY, { align: 'center' });
  }
}

async function drawHeader(doc, pageWidth, headerConfig, sampleData) {
  if (headerConfig?.enabled === false) return PAGE_MARGIN;

  const styles = headerConfig?.styles || {};
  const align = styles.alignment || 'left';
  const titleFont = mapFontToJsPdf(styles.fontFamily);
  const titleSize = Number(styles.fontSize) || 24;
  const textColor = parseColor(styles.textColor) || [0, 0, 0];
  const logoUrl = await toDataUrl(headerConfig?.logoUrl || '');

  let y = PAGE_MARGIN;
  let leftX = PAGE_MARGIN;
  let rightX = pageWidth - PAGE_MARGIN;

  if (logoUrl) {
    try {
      const props = doc.getImageProperties(logoUrl);
      const logoHeight = Number(styles.logoHeight) || 60;
      const logoWidth = Number(styles.logoWidth) || (logoHeight * props.width) / props.height;
      const drawX = align === 'right' ? rightX - logoWidth : PAGE_MARGIN;
      doc.addImage(logoUrl, getImageFormat(logoUrl), drawX, y, logoWidth, logoHeight);
      if (align !== 'right') leftX += logoWidth + 14;
      if (align === 'right') rightX -= logoWidth + 14;
    } catch {
      // ignore logo rendering failure
    }
  }

  const textX = align === 'center' ? pageWidth / 2 : align === 'right' ? rightX : leftX;
  const textOptions = align === 'center' ? { align: 'center' } : align === 'right' ? { align: 'right' } : undefined;

  doc.setTextColor(textColor[0], textColor[1], textColor[2]);
  doc.setFont(titleFont, styles.fontWeight === 'normal' ? 'normal' : 'bold');
  doc.setFontSize(titleSize);

  if (headerConfig?.title) {
    const titleLines = doc.splitTextToSize(stripHtml(resolveContent(headerConfig.title, sampleData || {})), pageWidth - PAGE_MARGIN * 2 - 90);
    doc.text(titleLines, textX, y + titleSize * 0.85, textOptions);
    y += titleLines.length * (titleSize * 1.1);
  }

  if (headerConfig?.description) {
    const descriptionSize = Math.max(10, Math.round(titleSize * 0.45));
    doc.setFont(titleFont, 'normal');
    doc.setFontSize(descriptionSize);
    const descriptionLines = doc.splitTextToSize(stripHtml(resolveContent(headerConfig.description, sampleData || {})), pageWidth - PAGE_MARGIN * 2 - 90);
    doc.text(descriptionLines, textX, y + 8, textOptions);
    y += descriptionLines.length * (descriptionSize * 1.2) + 10;
  } else {
    y += 10;
  }

  doc.setDrawColor(226, 232, 240);
  doc.setLineWidth(1);
  doc.line(PAGE_MARGIN, y, pageWidth - PAGE_MARGIN, y);
  doc.setTextColor(17, 17, 17);

  return y + 16;
}

export const CustomFormPdfExport = forwardRef(function CustomFormPdfExport(
  { formConfig, sampleData, borderSettings },
  ref,
) {
  useImperativeHandle(ref, () => ({
    async generatePdf(name = 'Document', silent = false, returnBlob = false) {
      const headerConfig = formConfig?.header || {};
      const footerConfig = formConfig?.footer || {};
      const globalSettings = formConfig?.globalSettings || {};
      const effectiveBorder = borderSettings || formConfig?.borderSettings || {};
      const defaultFont = mapFontToJsPdf(globalSettings.fontFamily);
      const richContent = sanitizeRichTextHtml(resolveContent(formConfig?.richContent || '', sampleData || {}));

      const doc = new jsPDF({ orientation: 'portrait', unit: PAGE_UNIT, format: PAGE_SIZE });
      const pageWidth = doc.internal.pageSize.getWidth();
      const pageHeight = doc.internal.pageSize.getHeight();
      const contentWidth = pageWidth - PAGE_MARGIN * 2;
      const contentBottom = pageHeight - PAGE_MARGIN - (footerConfig?.enabled === false ? 0 : FOOTER_HEIGHT);

      const blocks = parseHtmlToBlocks(richContent, defaultFont);
      let y = await drawHeader(doc, pageWidth, headerConfig, sampleData || {});

      const ensureSpace = (neededHeight = 16) => {
        if (y + neededHeight <= contentBottom) return;
        doc.addPage();
        y = PAGE_MARGIN;
      };

      for (const block of blocks) {
        if (block.type === 'pageBreak') {
          doc.addPage();
          y = PAGE_MARGIN;
          continue;
        }

        if (block.type === 'divider') {
          ensureSpace(20);
          doc.setDrawColor(226, 232, 240);
          doc.setLineWidth(1);
          doc.line(PAGE_MARGIN, y, pageWidth - PAGE_MARGIN, y);
          y += 16;
          continue;
        }

        if (block.type === 'image' && block.src) {
          const imageData = await toDataUrl(block.src);
          if (imageData) {
            try {
              const props = doc.getImageProperties(imageData);
              const maxWidth = Math.min(contentWidth, 320);
              const imageWidth = Math.min(maxWidth, props.width);
              const imageHeight = (imageWidth * props.height) / props.width;
              ensureSpace(imageHeight + 12);
              doc.addImage(imageData, getImageFormat(imageData), PAGE_MARGIN, y, imageWidth, imageHeight);
              y += imageHeight + 12;
            } catch {
              // ignore image block failures
            }
          }
          continue;
        }

        if (block.type === 'table') {
          ensureSpace(30);

          autoTable(doc, {
            startY: y,
            head: block.headerRows?.length ? block.headerRows : undefined,
            body: block.bodyRows || [],
            margin: { left: PAGE_MARGIN, right: PAGE_MARGIN, top: PAGE_MARGIN, bottom: PAGE_MARGIN + FOOTER_HEIGHT },
            theme: 'grid',
            styles: {
              font: defaultFont,
              fontSize: 9,
              cellPadding: 4,
              lineColor: [203, 213, 225],
              lineWidth: 0.5,
              textColor: [17, 17, 17],
              overflow: 'linebreak',
            },
            headStyles: {
              fillColor: [241, 245, 249],
              textColor: [17, 17, 17],
              fontStyle: 'bold',
            },
          });

          y = (doc.lastAutoTable?.finalY || y) + 14;
          continue;
        }

        const segments = block.segments || [];
        const maxFontSize = Math.max(...segments.map((segment) => segment.size || BODY_FONT_SIZE), BODY_FONT_SIZE);
        const lineHeight = maxFontSize * (getLineHeightRatio(block.lineHeight, maxFontSize) || LINE_HEIGHT_RATIO);
        const indentWidth = block.indent || 0;
        const prefix = block.listType
          ? (block.listType === 'ordered'
              ? getOrderedListPrefix(block.listIndex, block.listMarkerStyle)
              : (block.listMarkerStyle === 'square' ? '▪ ' : block.listMarkerStyle === 'circle' ? '◦ ' : '• '))
          : '';
        const prefixWidth = prefix ? measureText(doc, prefix, { font: defaultFont, size: maxFontSize }, defaultFont) : 0;
        const availableWidth = Math.max(contentWidth - indentWidth - prefixWidth, 80);
        const lines = buildLines(doc, segments, availableWidth, defaultFont);
        const spacingBefore = Math.min(block.spacingBefore || 0, MAX_BLOCK_SPACING);
        const spacingAfter = Math.min(block.spacingAfter ?? (block.isHeading ? 6 : DEFAULT_BLOCK_SPACING), MAX_BLOCK_SPACING);

        if (spacingBefore > 0) {
          ensureSpace(spacingBefore);
          y += spacingBefore;
        }

        if (lines.length === 0 || (lines.length === 1 && lines[0].tokens.length === 0)) {
          ensureSpace(Math.max(lineHeight * 0.65, spacingAfter));
          y += Math.max(lineHeight * 0.65, spacingAfter);
          continue;
        }

        lines.forEach((line, lineIndex) => {
          ensureSpace(lineHeight + 2);

          const lineX = PAGE_MARGIN + indentWidth + prefixWidth;
          if (lineIndex === 0 && prefix) {
            setSegmentStyle(doc, { font: defaultFont, size: maxFontSize }, defaultFont);
            doc.text(prefix, PAGE_MARGIN + indentWidth, y);
          }

          renderLine(
            doc,
            line.tokens,
            lineX,
            y,
            availableWidth,
            block.align || 'left',
            defaultFont,
            block.align === 'justify' && !block.listType && !line.endsWithBreak && lineIndex < lines.length - 1,
          );

          y += lineHeight;
        });

        y += spacingAfter;
      }

      const totalPages = doc.getNumberOfPages();
      const footerStyles = footerConfig?.styles || {};

      for (let pageNum = 1; pageNum <= totalPages; pageNum += 1) {
        doc.setPage(pageNum);
        drawBorder(doc, pageWidth, pageHeight, effectiveBorder);
        drawFooter(doc, pageWidth, pageHeight, footerConfig, footerStyles, totalPages, pageNum, sampleData || {});
      }

      if (returnBlob) {
        return doc.output('blob');
      }

      if (!silent) {
        doc.save(`${(name || 'Document').replace(/[^a-z0-9]/gi, '_').toLowerCase()}.pdf`);
      }

      return undefined;
    },
  }));

  return null;
});

export default CustomFormPdfExport;