UNPKG

@juspay/neurolink

Version:

The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002

2,384 lines • 78.7 kB
/**
 * Slide Renderers
 *
 * Standalone render functions for each slide type.
 * Extracted from SlideGenerator to keep functions under 300 lines.
 *
 * @module presentation/slideRenderers
 */
import { logger } from "../../utils/logger.js";
import { getBulletOptions, getSlideTypeFormatting } from "./constants.js";
import { bufferToDataUrl, calculateFontSize, createFormattedTextProps, hasMarkdownFormatting, parseMarkdownText, validateImageBuffer, } from "./utils.js";
// ============================================================================
// LAYOUT POSITIONS
// ============================================================================
/**
 * Minimum gap between elements (in inches)
 */
const MIN_GAP = 0.1;
/**
 * Default text fit option for pptxgenjs
 * 'shrink' = Shrink text to fit within the container
 */
const DEFAULT_TEXT_FIT = "shrink";
export const LAYOUT_POSITIONS = {
    margin: { x: 0.5, y: 0.4 },
    title: { x: 0.5, y: 0.4, w: 9, h: 0.8 },
    subtitle: { x: 0.5, y: 1.4, w: 9, h: 0.5 },
    content: { x: 0.5, y: 1.4, w: 9, h: 3.8 },
    contentFull: { x: 0.5, y: 1.4, w: 9, h: 3.8 },
    contentLeft: { x: 0.5, y: 1.4, w: 4.2, h: 3.8 },
    contentRight: { x: 5.3, y: 1.4, w: 4.2, h: 3.8 },
    imageRight: { x: 5.3, y: 1.4, w: 4.2, h: 3.8 },
    imageLeft: { x: 0.5, y: 1.4, w: 4.2, h: 3.8 },
    imageFull: { x: 0, y: 0, w: 10, h: 5.625 },
    imageCentered: { x: 2, y: 1.2, w: 6, h: 3.6 },
    columnLeft: { x: 0.5, y: 1.4, w: 4.2, h: 3.8 },
    columnRight: { x: 5.3, y: 1.4, w: 4.2, h: 3.8 },
    col1: { x: 0.5, y: 1.4, w: 2.8, h: 3.8 },
    col2: { x: 3.5, y: 1.4, w: 2.8, h: 3.8 },
    col3: { x: 6.5, y: 1.4, w: 2.8, h: 3.8 },
    chart: { x: 0.5, y: 1.4, w: 9, h: 3.8 },
    statRow: { y: 2.2, h: 2.5 },
    footer: { x: 0.5, y: 5.2, w: 9, h: 0.3 },
    logo: {
        "top-left": { x: 0.3, y: 0.2 },
        "top-right": { x: 8.5, y: 0.2 },
        "bottom-left": { x: 0.3, y: 5.0 },
        "bottom-right": { x: 8.5, y: 5.0 },
    },
    quote: { x: 1, y: 1.5, w: 8, h: 2.5 },
    quoteAuthor: { x: 1, y: 4.2, w: 8, h: 0.5 },
};
/**
 * Map legend position from SlideContent values to pptxgenjs values
 */
const LEGEND_POS_MAP = {
    top: "t",
    bottom: "b",
    left: "l",
    right: "r",
};
// ============================================================================
// HELPER FUNCTIONS - ENHANCED BACKGROUNDS
// ============================================================================
/** Extract theme colors for background (strips # prefix) */
function extractBackgroundColors(theme) {
    return {
        primary: theme.colors.primary.replace("#", ""),
        secondary: theme.colors.secondary.replace("#", ""),
        accent: theme.colors.accent.replace("#", ""),
        background: theme.colors.background.replace("#", ""),
    };
}
/** Blue to purple diagonal gradient effect */
function addGradientBlueBackground(slide, colors) {
    const { primary, secondary } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: "E8F4FD" },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: 6,
        h: 3,
        fill: { color: primary, transparency: 85 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: 4,
        h: 2,
        fill: { color: primary, transparency: 75 },
    });
    slide.addShape("rect", {
        x: 5,
        y: 3,
        w: 5,
        h: 2.625,
        fill: { color: secondary, transparency: 85 },
    });
    slide.addShape("rect", {
        x: 7,
        y: 4,
        w: 3,
        h: 1.625,
        fill: { color: secondary, transparency: 75 },
    });
}
/** Professional dark blue to teal gradient */
function addGradientCorporateBackground(slide, colors) {
    const { primary } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: "F0F9FF" },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: 3,
        h: "100%",
        fill: { color: "1E3A5F", transparency: 92 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: 1.5,
        h: "100%",
        fill: { color: "1E3A5F", transparency: 85 },
    });
    slide.addShape("rect", {
        x: 7,
        y: 0,
        w: 3,
        h: "100%",
        fill: { color: "2E7D32", transparency: 92 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 0.03,
        fill: { color: primary },
    });
}
/** Warm orange to pink gradient */
function addGradientWarmBackground(slide) {
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: "FFF7ED" },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 2.5,
        fill: { color: "EA580C", transparency: 90 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 1,
        fill: { color: "EA580C", transparency: 80 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 4.5,
        w: "100%",
        h: 1.125,
        fill: { color: "DB2777", transparency: 88 },
    });
}
/** Dark theme with accent glow */
function addGradientDarkBackground(slide) {
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: "0F172A" },
    });
    slide.addShape("ellipse", {
        x: -2,
        y: -1,
        w: 6,
        h: 4,
        fill: { color: "06B6D4", transparency: 85 },
    });
    slide.addShape("ellipse", {
        x: 7,
        y: 3,
        w: 5,
        h: 4,
        fill: { color: "A855F7", transparency: 85 },
    });
}
/** Very subtle professional gradient */
function addGradientSubtleBackground(slide, colors) {
    const { primary, secondary } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: "FAFBFC" },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 2,
        fill: { color: primary, transparency: 95 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 4,
        w: "100%",
        h: 1.625,
        fill: { color: secondary, transparency: 96 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 0.02,
        fill: { color: primary },
    });
}
/** Geometric shapes pattern */
function addGeometricBackground(slide, colors) {
    const { primary, secondary, accent, background } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: background },
    });
    slide.addShape("rtTriangle", {
        x: 6,
        y: 3,
        w: 4,
        h: 2.625,
        fill: { color: primary, transparency: 90 },
        rotate: 180,
    });
    slide.addShape("rtTriangle", {
        x: 0,
        y: 0,
        w: 2.5,
        h: 2,
        fill: { color: secondary, transparency: 92 },
    });
    slide.addShape("ellipse", {
        x: 8.5,
        y: 0.2,
        w: 1,
        h: 1,
        fill: { color: accent, transparency: 85 },
    });
    slide.addShape("ellipse", {
        x: 0.5,
        y: 4.5,
        w: 0.6,
        h: 0.6,
        fill: { color: primary, transparency: 80 },
    });
}
/** Large corner accent shapes */
function addCornerAccentBackground(slide, colors) {
    const { primary, secondary, background } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: background },
    });
    slide.addShape("rect", {
        x: 7,
        y: 0,
        w: 3,
        h: 1.8,
        fill: { color: primary, transparency: 88 },
    });
    slide.addShape("rect", {
        x: 8.5,
        y: 0,
        w: 1.5,
        h: 1,
        fill: { color: primary, transparency: 70 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 4,
        w: 2.5,
        h: 1.625,
        fill: { color: secondary, transparency: 88 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 4.8,
        w: 1.2,
        h: 0.825,
        fill: { color: secondary, transparency: 70 },
    });
}
/** Curved wave pattern effect */
function addWaveBackground(slide, colors) {
    const { primary, secondary, accent } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: "F8FAFC" },
    });
    slide.addShape("ellipse", {
        x: -3,
        y: 4,
        w: 8,
        h: 3,
        fill: { color: primary, transparency: 92 },
    });
    slide.addShape("ellipse", {
        x: 2,
        y: 4.5,
        w: 7,
        h: 2.5,
        fill: { color: secondary, transparency: 93 },
    });
    slide.addShape("ellipse", {
        x: 6,
        y: 4.2,
        w: 6,
        h: 3,
        fill: { color: accent, transparency: 94 },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 0.03,
        fill: { color: primary },
    });
}
/** Split diagonal background */
function addSplitBackground(slide, colors) {
    const { primary, secondary, background } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: background },
    });
    slide.addShape("rect", {
        x: 5,
        y: -1,
        w: 6,
        h: 8,
        fill: { color: primary, transparency: 94 },
        rotate: 15,
    });
    slide.addShape("rect", {
        x: 7,
        y: -1,
        w: 5,
        h: 8,
        fill: { color: secondary, transparency: 92 },
        rotate: 15,
    });
}
/** Simple solid with subtle accent */
function addSolidBackground(slide, colors) {
    const { primary, background } = colors;
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: { color: background },
    });
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: 0.02,
        fill: { color: primary },
    });
}
/**
 * Add enhanced background with gradient or multi-color designs
 * Creates visually appealing slides with sophisticated styling
 */
export function addEnhancedBackground(slide, theme, style = "gradient-subtle") {
    const colors = extractBackgroundColors(theme);
    switch (style) {
        case "gradient-blue":
            addGradientBlueBackground(slide, colors);
            break;
        case "gradient-corporate":
            addGradientCorporateBackground(slide, colors);
            break;
        case "gradient-warm":
            addGradientWarmBackground(slide);
            break;
        case "gradient-dark":
            addGradientDarkBackground(slide);
            break;
        case "gradient-subtle":
            addGradientSubtleBackground(slide, colors);
            break;
        case "geometric":
            addGeometricBackground(slide, colors);
            break;
        case "corner-accent":
            addCornerAccentBackground(slide, colors);
            break;
        case "wave":
            addWaveBackground(slide, colors);
            break;
        case "split":
            addSplitBackground(slide, colors);
            break;
        case "solid":
        default:
            addSolidBackground(slide, colors);
            break;
    }
}
/**
 * Add subtle colored background to slides (legacy - use addEnhancedBackground for more options)
 */
export function addColoredBackground(slide, theme, opacity = 0.05) {
    const primaryColor = theme.colors.primary.replace("#", "");
    slide.addShape("rect", {
        x: 0,
        y: 0,
        w: "100%",
        h: "100%",
        fill: {
            color: primaryColor,
            transparency: (1 - opacity) * 100, // pptx transparency is 0-100 where 100 is fully transparent
        },
    });
}
/**
 * Add modern card-style container with border
 */
export function addCardContainer(slide, pos, theme, borderWidth = 2) {
    // Card background with subtle border
    slide.addShape("rect", {
        x: pos.x,
        y: pos.y,
        w: pos.w,
        h: pos.h,
        fill: {
            color: theme.colors.background.replace("#", ""),
            transparency: 0, // Fully opaque
        },
        line: {
            color: theme.colors.muted.replace("#", ""),
            width: borderWidth,
        },
    });
}
/**
 * Add accent bar for visual hierarchy
 */
export function addAccentBar(slide, pos, theme, position = "left") {
    const barConfig = {
        left: { x: pos.x, y: pos.y, w: 0.1, h: pos.h },
        top: { x: pos.x, y: pos.y, w: pos.w, h: 0.1 },
        bottom: { x: pos.x, y: pos.y + pos.h - 0.1, w: pos.w, h: 0.1 },
    };
    slide.addShape("rect", {
        ...barConfig[position],
        fill: { color: theme.colors.primary.replace("#", "") },
    });
}
/**
 * Calculate text width in inches based on font size and character count.
 * Uses typographic metrics for Arial font family.
 *
 * @param text - The text string to measure
 * @param fontSize - Font size in points
 * @param isBold - Whether the text is bold (adds ~10% width)
 * @returns Width in inches
 */
export function calculateTextWidth(text, fontSize, isBold = false) {
    // For proportional fonts like Arial:
    // - Average character width is approximately 0.42 * em-height for normal text
    // - 1 point = 1/72 inch, so em-height = fontSize / 72 inches
    // - Bold text is approximately 10% wider
    const emHeight = fontSize / 72; // em-height in inches
    const avgCharWidthRatio = 0.42; // Average char width as ratio of em-height (reduced for tighter fit)
    const boldMultiplier = isBold ? 1.1 : 1.0;
    const charWidth = emHeight * avgCharWidthRatio * boldMultiplier;
    return text.length * charWidth;
}
export function addTitle(slide, title, theme, showUnderline = true) {
    slide.addText(title, {
        x: LAYOUT_POSITIONS.title.x,
        y: LAYOUT_POSITIONS.title.y,
        w: LAYOUT_POSITIONS.title.w,
        h: LAYOUT_POSITIONS.title.h,
        fontSize: theme.fonts.sizes.heading,
        fontFace: theme.fonts.heading,
        color: theme.colors.text.replace("#", ""),
        bold: true,
        fit: DEFAULT_TEXT_FIT,
    });
    if (showUnderline) {
        // Calculate dynamic underline width based on actual title text and font size
        const textWidth = calculateTextWidth(title, theme.fonts.sizes.heading, true);
        // Constrain to reasonable bounds (min 1.5", max fits within slide margins)
        const maxWidth = LAYOUT_POSITIONS.title.w; // 9 inches - same as title container
        const minWidth = 1.0;
        const calculatedWidth = Math.min(maxWidth, Math.max(minWidth, textWidth));
        slide.addShape("rect", {
            x: LAYOUT_POSITIONS.title.x,
            y: LAYOUT_POSITIONS.title.y + LAYOUT_POSITIONS.title.h + 0.1,
            w: calculatedWidth,
            h: 0.03,
            fill: { color: theme.colors.primary.replace("#", "") },
        });
    }
}
/**
 * Add individual bullet items (each as separate text element)
 * This creates cleaner spacing and no bounding box around bullets
 * Useful for column layouts and when you want more control over spacing
 */
export function addIndividualBullets(options) {
    const { slide, bullets, startX, startY, width, theme, itemSpacing = 0.45, } = options;
    if (!bullets || bullets.length === 0) {
        return;
    }
    // Ensure minimum gap between bullets
    const effectiveSpacing = Math.max(itemSpacing, MIN_GAP * 2);
    bullets.forEach((bullet, index) => {
        // Skip invalid bullets
        if (!bullet || !bullet.text) {
            return;
        }
        const isBold = bullet.bold || bullet.emphasis || false;
        const color = bullet.color?.replace("#", "") || theme.colors.text.replace("#", "");
        // Check if bullet text contains markdown formatting
        if (hasMarkdownFormatting(bullet.text)) {
            // Parse markdown and create rich text runs with bullet prefix
            const segments = parseMarkdownText(bullet.text);
            const textRuns = [
                {
                    text: "• ",
                    options: {
                        fontSize: theme.fonts.sizes.body,
                        fontFace: theme.fonts.body,
                        color,
                    },
                },
                ...createFormattedTextProps(segments, {
                    fontSize: theme.fonts.sizes.body,
                    fontFace: theme.fonts.body,
                    color,
                    baseBold: isBold,
                }),
            ];
            slide.addText(textRuns, {
                x: startX,
                y: startY + index * effectiveSpacing,
                w: width,
                h: Math.max(0.4, effectiveSpacing - MIN_GAP),
                fit: DEFAULT_TEXT_FIT,
            });
        }
        else {
            // No markdown - use simple text
            const bulletText = `• ${bullet.text}`;
            slide.addText(bulletText, {
                x: startX,
                y: startY + index * effectiveSpacing,
                w: width,
                h: Math.max(0.4, effectiveSpacing - MIN_GAP),
                fontSize: theme.fonts.sizes.body,
                fontFace: theme.fonts.body,
                color,
                bold: isBold,
                fit: DEFAULT_TEXT_FIT,
            });
        }
    });
}
/**
 * Add bullets to a slide with hybrid formatting
 *
 * Priority: bullet-level > slide-level > type-defaults > theme-defaults
 *
 * @param slide - The pptxgenjs slide
 * @param bullets - Array of bullet points (normalized)
 * @param pos - Position and dimensions
 * @param theme - Presentation theme
 * @param slideType - Slide type for default formatting (optional)
 */
export function addBullets(slide, bullets, pos, theme, slideType = "content", options) {
    if (!bullets || bullets.length === 0) {
        return;
    }
    // Use individual bullets for cleaner spacing (no bounding box)
    if (options?.useIndividualBullets) {
        addIndividualBullets({
            slide,
            bullets,
            startX: pos.x,
            startY: pos.y,
            width: pos.w,
            theme,
            itemSpacing: options.itemSpacing ?? 0.45,
        });
        return;
    }
    // Get hardcoded defaults for this slide type
    const typeDefaults = getSlideTypeFormatting(slideType);
    // Calculate base font size based on bullet count (if not overridden)
    const calculatedFontSize = calculateFontSize(bullets.length, typeDefaults.baseFontSize || theme.fonts.sizes.body);
    const textLines = [];
    bullets.forEach((bullet) => {
        // Skip invalid bullets
        if (!bullet || !bullet.text) {
            return;
        }
        // Priority: bullet-level > type-defaults
        const bulletStyle = bullet.bulletStyle || typeDefaults.bulletStyle || "disc";
        const fontSize = bullet.fontSize || calculatedFontSize;
        const color = bullet.color?.replace("#", "") || theme.colors.text.replace("#", "");
        const isBold = bullet.bold ?? bullet.emphasis ?? false;
        // Get pptxgenjs bullet options based on style
        // If bullet has custom icon, use that instead of style
        let bulletOptions;
        if (bullet.icon) {
            bulletOptions = { code: bullet.icon };
        }
        else {
            bulletOptions = getBulletOptions(bulletStyle);
        }
        // Check if bullet text contains markdown formatting (**bold**, *italic*)
        if (hasMarkdownFormatting(bullet.text)) {
            // Parse markdown and create rich text runs
            const segments = parseMarkdownText(bullet.text);
            const textRuns = createFormattedTextProps(segments, {
                fontSize,
                fontFace: theme.fonts.body,
                color,
                baseBold: isBold,
            });
            textLines.push({
                text: textRuns,
                options: {
                    bullet: bulletOptions,
                    indentLevel: 0,
                    paraSpaceBefore: 6,
                    paraSpaceAfter: 6,
                },
            });
        }
        else {
            // No markdown - use simple text
            textLines.push({
                text: bullet.text,
                options: {
                    bullet: bulletOptions,
                    fontSize,
                    fontFace: theme.fonts.body,
                    color,
                    bold: isBold,
                    indentLevel: 0,
                    paraSpaceBefore: 6, // Add space before each paragraph (in points)
                    paraSpaceAfter: 6, // Add space after each paragraph (in points)
                },
            });
        }
        // Handle sub-bullets (2pt smaller than main bullet)
        if (bullet.subBullets && bullet.subBullets.length > 0) {
            bullet.subBullets.forEach((subBullet) => {
                // Check for markdown in sub-bullets too
                if (hasMarkdownFormatting(subBullet)) {
                    const segments = parseMarkdownText(subBullet);
                    const textRuns = createFormattedTextProps(segments, {
                        fontSize: Math.max(10, fontSize - 2),
                        fontFace: theme.fonts.body,
                        color: theme.colors.muted.replace("#", ""),
                    });
                    textLines.push({
                        text: textRuns,
                        options: {
                            bullet: true,
                            indentLevel: 1,
                            paraSpaceBefore: 3,
                            paraSpaceAfter: 3,
                        },
                    });
                }
                else {
                    textLines.push({
                        text: subBullet,
                        options: {
                            bullet: true,
                            fontSize: Math.max(10, fontSize - 2),
                            fontFace: theme.fonts.body,
                            color: theme.colors.muted.replace("#", ""),
                            indentLevel: 1,
                            paraSpaceBefore: 3,
                            paraSpaceAfter: 3,
                        },
                    });
                }
            });
        }
    });
    slide.addText(textLines, {
        x: pos.x,
        y: pos.y,
        w: pos.w,
        h: pos.h,
        valign: "top",
        fit: DEFAULT_TEXT_FIT,
    });
}
export function addImage(slide, imageBuffer, pos) {
    // Validate and get data URL
    const validation = validateImageBuffer(imageBuffer);
    if (!validation.isValid && validation.format === "") {
        logger.warn("[addImage] Invalid image buffer", { error: validation.error });
        return;
    }
    if (!validation.isValid) {
        logger.warn("[addImage] Image validation warning, attempting to add anyway", {
            error: validation.error,
            mimeType: validation.mimeType,
        });
    }
    const dataUrl = bufferToDataUrl(imageBuffer);
    if (!dataUrl) {
        logger.warn("[addImage] Failed to convert buffer to data URL");
        return;
    }
    try {
        slide.addImage({
            data: dataUrl,
            x: pos.x,
            y: pos.y,
            w: pos.w,
            h: pos.h,
            sizing: { type: "cover", w: pos.w, h: pos.h },
        });
    }
    catch (error) {
        logger.error("[addImage] Failed to add image to slide", {
            error: error instanceof Error ? error.message : String(error),
        });
    }
}
export function getPptxChartType(slideType) {
    switch (slideType) {
        case "chart-bar":
            return "bar";
        case "chart-line":
            return "line";
        case "chart-pie":
            return "pie";
        case "chart-area":
            return "area";
        default:
            return "bar";
    }
}
// ============================================================================
// SLIDE RENDERERS - OPENING/CLOSING
// ============================================================================
export function renderTitleSlide(slide, title, content, theme, imageBuffer) {
    const layout = content.layoutOptions || {};
    const titleOpts = layout.title || {};
    const subtitleOpts = layout.subtitle || {};
    const bgOpts = layout.background || {};
    // Background: image > custom color > theme default (white)
    const imageDataUrl = imageBuffer ? bufferToDataUrl(imageBuffer) : null;
    if (imageDataUrl) {
        slide.background = {
            data: imageDataUrl,
        };
        slide.addShape("rect", {
            x: 0,
            y: 0,
            w: "100%",
            h: "100%",
            fill: { color: "000000", transparency: 40 },
        });
    }
    else if (bgOpts.color ||
        bgOpts.useThemePrimary ||
        bgOpts.useThemeSecondary) {
        const bgColor = bgOpts.useThemePrimary
            ? theme.colors.primary
            : bgOpts.useThemeSecondary
                ? theme.colors.secondary
                : bgOpts.color || theme.colors.background;
        slide.addShape("rect", {
            x: 0,
            y: 0,
            w: 10,
            h: 5.625,
            fill: { color: bgColor.replace("#", "") },
        });
    }
    // Determine text colors based on background
    const hasColoredBg = imageBuffer ||
        bgOpts.color ||
        bgOpts.useThemePrimary ||
        bgOpts.useThemeSecondary;
    const defaultTitleColor = hasColoredBg
        ? "FFFFFF"
        : theme.colors.text.replace("#", "");
    const defaultSubtitleColor = hasColoredBg
        ? "FFFFFF"
        : theme.colors.muted.replace("#", "");
    // Title
    slide.addText(title, {
        x: titleOpts.x ?? 0.5,
        y: titleOpts.y ?? 2,
        w: titleOpts.w ?? 9,
        h: titleOpts.h ?? 1.2,
        fontSize: titleOpts.fontSize ?? theme.fonts.sizes.title,
        fontFace: theme.fonts.heading,
        color: titleOpts.color?.replace("#", "") ?? defaultTitleColor,
        align: titleOpts.align ?? "center",
        bold: true,
        fit: DEFAULT_TEXT_FIT,
    });
    // Subtitle
    if (content.subtitle) {
        slide.addText(content.subtitle, {
            x: subtitleOpts.x ?? 0.5,
            y: subtitleOpts.y ?? 3.3,
            w: subtitleOpts.w ?? 9,
            h: subtitleOpts.h ?? 0.6,
            fontSize: subtitleOpts.fontSize ?? theme.fonts.sizes.subtitle,
            fontFace: theme.fonts.body,
            color: subtitleOpts.color?.replace("#", "") ?? defaultSubtitleColor,
            align: subtitleOpts.align ?? "center",
            fit: DEFAULT_TEXT_FIT,
        });
    }
}
export function renderSectionHeaderSlide(slide, title, content, theme) {
    const layout = content.layoutOptions || {};
    const sectionNumOpts = layout.sectionNumber || {};
    const titleOpts = layout.title || {};
    const subtitleOpts = layout.subtitle || {};
    const bgOpts = layout.background || {};
    // Apply background if specified
    if (bgOpts.color) {
        slide.addShape("rect", {
            x: 0,
            y: 0,
            w: 10,
            h: 5.625,
            fill: { color: bgOpts.color.replace("#", "") },
        });
    }
    // Section number
    if (content.sectionNumber) {
        const isWatermark = sectionNumOpts.style === "watermark";
        const isSmall = sectionNumOpts.style === "small";
        slide.addText(String(content.sectionNumber).padStart(2, "0"), {
            x: sectionNumOpts.x ?? (isWatermark ? 5.5 : 0.5),
            y: sectionNumOpts.y ?? (isWatermark ? 0.5 : 1.5),
            w: isWatermark ? 5 : 2,
            h: isWatermark ? 4 : 1,
            fontSize: sectionNumOpts.fontSize ?? (isWatermark ? 200 : isSmall ? 32 : 72),
            fontFace: theme.fonts.heading,
            color: theme.colors.primary.replace("#", ""),
            bold: true,
            align: isWatermark ? "right" : "left",
            transparency: isWatermark ? 70 : 0,
        });
    }
    // Title
    slide.addText(title, {
        x: titleOpts.x ?? 0.5,
        y: titleOpts.y ?? 2.5,
        w: titleOpts.w ?? 9,
        h: titleOpts.h ?? 1,
        fontSize: titleOpts.fontSize ?? theme.fonts.sizes.title,
        fontFace: theme.fonts.heading,
        color: theme.colors.text.replace("#", ""),
        bold: true,
        align: titleOpts.align ?? "left",
    });
    // Subtitle (if provided)
    if (content.subtitle) {
        slide.addText(content.subtitle, {
            x: subtitleOpts.x ?? 0.5,
            y: subtitleOpts.y ?? 3.6,
            w: subtitleOpts.w ?? 8,
            h: subtitleOpts.h ?? 0.6,
            fontSize: subtitleOpts.fontSize ?? theme.fonts.sizes.subtitle,
            fontFace: theme.fonts.body,
            color: theme.colors.muted.replace("#", ""),
            align: subtitleOpts.align ?? "left",
        });
    }
}
export function renderThankYouSlide(slide, title, content, theme, imageBuffer) {
    if (imageBuffer) {
        const dataUrl = bufferToDataUrl(imageBuffer);
        if (dataUrl) {
            slide.background = {
                data: dataUrl,
            };
            slide.addShape("rect", {
                x: 0,
                y: 0,
                w: "100%",
                h: "100%",
                fill: { color: "000000", transparency: 40 },
            });
        }
    }
    const textColor = imageBuffer ? "FFFFFF" : theme.colors.text.replace("#", "");
    slide.addText(title || "Thank You!", {
        x: 0.5,
        y: 1.8,
        w: 9,
        h: 1,
        fontSize: theme.fonts.sizes.title,
        fontFace: theme.fonts.heading,
        color: textColor,
        align: "center",
        bold: true,
        fit: DEFAULT_TEXT_FIT,
    });
    if (content.cta) {
        slide.addText(content.cta, {
            x: 0.5,
            y: 2.9,
            w: 9,
            h: 0.5,
            fontSize: theme.fonts.sizes.subtitle,
            fontFace: theme.fonts.body,
            color: imageBuffer ? "FFFFFF" : theme.colors.muted.replace("#", ""),
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
    }
    if (content.contactInfo) {
        const contactLines = [];
        if (content.contactInfo.email) {
            contactLines.push(`Email: ${content.contactInfo.email}`);
        }
        if (content.contactInfo.website) {
            contactLines.push(`Web: ${content.contactInfo.website}`);
        }
        if (content.contactInfo.phone) {
            contactLines.push(`Phone: ${content.contactInfo.phone}`);
        }
        if (contactLines.length > 0) {
            slide.addText(contactLines.join("   •   "), {
                x: 0.5,
                y: 4.2,
                w: 9,
                h: 0.4,
                fontSize: theme.fonts.sizes.body,
                fontFace: theme.fonts.body,
                color: textColor,
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
        if (content.contactInfo.social &&
            Array.isArray(content.contactInfo.social) &&
            content.contactInfo.social.length > 0) {
            const socialText = content.contactInfo.social
                .map((s) => `${s.platform || ""}: ${s.handle || ""}`)
                .join("   •   ");
            slide.addText(socialText, {
                x: 0.5,
                y: 4.7,
                w: 9,
                h: 0.3,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: imageBuffer ? "CCCCCC" : theme.colors.muted.replace("#", ""),
                align: "center",
            });
        }
    }
}
// ============================================================================
// SLIDE RENDERERS - CONTENT
// ============================================================================
export function renderContentSlide(options) {
    const { slide, title, content, layout, theme, imageBuffer, slideType = "content", } = options;
    addTitle(slide, title, theme);
    const hasImage = imageBuffer && layout.includes("image");
    const contentPos = hasImage
        ? layout.includes("left")
            ? LAYOUT_POSITIONS.contentRight
            : LAYOUT_POSITIONS.contentLeft
        : LAYOUT_POSITIONS.contentFull;
    if (content.bullets && content.bullets.length > 0) {
        addBullets(slide, content.bullets, contentPos, theme, slideType);
    }
    else if (content.body) {
        slide.addText(content.body, {
            x: contentPos.x,
            y: contentPos.y,
            w: contentPos.w,
            h: contentPos.h,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.body,
            color: theme.colors.text.replace("#", ""),
            valign: "top",
        });
    }
    if (imageBuffer && hasImage) {
        const imagePos = layout.includes("left")
            ? LAYOUT_POSITIONS.imageLeft
            : LAYOUT_POSITIONS.imageRight;
        addImage(slide, imageBuffer, imagePos);
    }
}
export function renderImageSlide(slide, title, content, layout, theme, imageBuffer) {
    if (layout === "image-full-overlay" && imageBuffer) {
        const dataUrl = bufferToDataUrl(imageBuffer);
        if (dataUrl) {
            slide.background = {
                data: dataUrl,
            };
            slide.addShape("rect", {
                x: 0,
                y: 0,
                w: "100%",
                h: "100%",
                fill: { color: "000000", transparency: 50 },
            });
            slide.addText(title, {
                x: 0.5,
                y: 4.2,
                w: 9,
                h: 1,
                fontSize: theme.fonts.sizes.heading,
                fontFace: theme.fonts.heading,
                color: "FFFFFF",
                bold: true,
                fit: DEFAULT_TEXT_FIT,
            });
        }
        else {
            // Fallback to standard layout if image is invalid
            addTitle(slide, title, theme);
        }
    }
    else if (layout === "image-centered" || layout === "image-full-overlay") {
        addTitle(slide, title, theme);
        if (imageBuffer) {
            addImage(slide, imageBuffer, LAYOUT_POSITIONS.imageCentered);
        }
        else {
            // Fallback when no image is available - show placeholder with content
            slide.addShape("rect", {
                x: 1.5,
                y: 1.8,
                w: 7,
                h: 3.5,
                fill: { color: theme.colors.muted.replace("#", ""), transparency: 90 },
                line: {
                    color: theme.colors.muted.replace("#", ""),
                    width: 1,
                    dashType: "dash",
                },
            });
            slide.addText("📷", {
                x: 1.5,
                y: 2.8,
                w: 7,
                h: 1,
                fontSize: 48,
                align: "center",
                valign: "middle",
                fit: DEFAULT_TEXT_FIT,
            });
            slide.addText("Image not available", {
                x: 1.5,
                y: 3.8,
                w: 7,
                h: 0.5,
                fontSize: 12,
                fontFace: theme.fonts.body,
                color: theme.colors.muted.replace("#", ""),
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
        if (content.caption) {
            slide.addText(content.caption, {
                x: 0.5,
                y: 5,
                w: 9,
                h: 0.4,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: theme.colors.muted.replace("#", ""),
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
    }
    else {
        renderContentSlide({ slide, title, content, layout, theme, imageBuffer });
    }
}
// ============================================================================
// SLIDE RENDERERS - COLUMNS
// ============================================================================
// ============================================================================
// SLIDE RENDERERS - COLUMNS (Generic)
// ============================================================================
/**
 * Generic column slide renderer
 * Handles 2, 3, or more columns dynamically
 * Renders each bullet as separate element (comparison-style)
 */
export function renderColumnSlide(slide, title, columns, theme, options) {
    addTitle(slide, title, theme, false);
    const opts = {
        headerY: options?.headerY ?? 1.3,
        headerHeight: options?.headerHeight ?? 0.5,
        bulletsStartY: options?.bulletsStartY ?? 2.0,
        columnGap: options?.columnGap ?? 0.3,
        highlightFirstColumn: options?.highlightFirstColumn ?? true,
    };
    const numColumns = columns.length;
    if (numColumns === 0) {
        return;
    }
    // Calculate dynamic column widths
    const totalWidth = 9; // Available width (10 - 0.5 margin on each side)
    const totalGaps = (numColumns - 1) * opts.columnGap;
    const columnWidth = (totalWidth - totalGaps) / numColumns;
    const startX = 0.5;
    columns.forEach((col, index) => {
        if (!col) {
            return;
        }
        const x = startX + index * (columnWidth + opts.columnGap);
        const isPrimary = opts.highlightFirstColumn && index === 0;
        // Render header box if title exists
        if (col.title) {
            slide.addShape("roundRect", {
                x,
                y: opts.headerY,
                w: columnWidth,
                h: opts.headerHeight,
                fill: {
                    color: isPrimary
                        ? theme.colors.primary.replace("#", "")
                        : theme.colors.muted.replace("#", ""),
                },
                rectRadius: 0.05,
            });
            // Adjust font size based on column count
            const headerFontSize = numColumns <= 2 ? theme.fonts.sizes.body : theme.fonts.sizes.body - 2;
            slide.addText(col.title, {
                x,
                y: opts.headerY,
                w: columnWidth,
                h: opts.headerHeight,
                fontSize: headerFontSize,
                fontFace: theme.fonts.heading,
                color: isPrimary
                    ? theme.colors.textOnPrimary.replace("#", "")
                    : theme.colors.text.replace("#", ""),
                align: "center",
                bold: true,
                valign: "middle",
                fit: DEFAULT_TEXT_FIT,
            });
        }
        // Render bullets individually (like comparison slide)
        if (col.bullets && col.bullets.length > 0) {
            addIndividualBullets({
                slide,
                bullets: col.bullets,
                startX: x,
                startY: opts.bulletsStartY,
                width: columnWidth,
                theme,
            });
        }
    });
}
export function renderTwoColumnSlide(slide, title, content, layout, theme, imageBuffer) {
    const columns = [];
    if (content.leftColumn) {
        columns.push(content.leftColumn);
    }
    if (content.rightColumn) {
        columns.push(content.rightColumn);
    }
    if (columns.length > 0) {
        renderColumnSlide(slide, title, columns, theme, {
            highlightFirstColumn: true,
        });
    }
    // Handle image if right column has no bullets
    if (imageBuffer && !content.rightColumn?.bullets) {
        addImage(slide, imageBuffer, LAYOUT_POSITIONS.columnRight);
    }
}
export function renderThreeColumnSlide(slide, title, content, theme) {
    const columns = [];
    if (content.leftColumn) {
        columns.push(content.leftColumn);
    }
    if (content.centerColumn) {
        columns.push(content.centerColumn);
    }
    if (content.rightColumn) {
        columns.push(content.rightColumn);
    }
    if (columns.length > 0) {
        renderColumnSlide(slide, title, columns, theme, {
            highlightFirstColumn: true,
        });
    }
}
// ============================================================================
// SLIDE RENDERERS - DATA VISUALIZATION
// ============================================================================
export function renderQuoteSlide(slide, title, content, theme) {
    slide.addText("\u201C", {
        x: 0.5,
        y: 1,
        w: 1,
        h: 1,
        fontSize: 120,
        fontFace: "Georgia",
        color: theme.colors.primary.replace("#", ""),
        fit: DEFAULT_TEXT_FIT,
    });
    if (content.quote) {
        slide.addText(content.quote, {
            x: LAYOUT_POSITIONS.quote.x,
            y: LAYOUT_POSITIONS.quote.y,
            w: LAYOUT_POSITIONS.quote.w,
            h: LAYOUT_POSITIONS.quote.h,
            fontSize: theme.fonts.sizes.heading,
            fontFace: "Georgia",
            color: theme.colors.text.replace("#", ""),
            italic: true,
            valign: "middle",
            fit: DEFAULT_TEXT_FIT,
        });
    }
    if (content.quoteAuthor) {
        let authorText = `— ${content.quoteAuthor}`;
        if (content.quoteAuthorTitle) {
            authorText += `, ${content.quoteAuthorTitle}`;
        }
        slide.addText(authorText, {
            x: LAYOUT_POSITIONS.quoteAuthor.x,
            y: LAYOUT_POSITIONS.quoteAuthor.y,
            w: LAYOUT_POSITIONS.quoteAuthor.w,
            h: LAYOUT_POSITIONS.quoteAuthor.h,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.body,
            color: theme.colors.muted.replace("#", ""),
            align: "right",
            fit: DEFAULT_TEXT_FIT,
        });
    }
}
export function renderStatisticsSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    if (!content.statistics || content.statistics.length === 0) {
        return;
    }
    const stats = content.statistics.slice(0, 4);
    const statWidth = 9 / stats.length;
    stats.forEach((stat, index) => {
        const x = 0.5 + index * statWidth;
        slide.addText(stat.value, {
            x,
            y: LAYOUT_POSITIONS.statRow.y,
            w: statWidth - 0.2,
            h: 1.2,
            fontSize: 48,
            fontFace: theme.fonts.heading,
            color: theme.colors.primary.replace("#", ""),
            bold: true,
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
        slide.addText(stat.label, {
            x,
            y: LAYOUT_POSITIONS.statRow.y + 1.3,
            w: statWidth - 0.2,
            h: 0.5,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.body,
            color: theme.colors.text.replace("#", ""),
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
        if (stat.change || stat.trend) {
            const trendColor = stat.trend === "up"
                ? "22C55E"
                : stat.trend === "down"
                    ? "EF4444"
                    : theme.colors.muted.replace("#", "");
            // Use simple text indicators instead of Unicode arrows for better compatibility
            const trendSymbol = stat.trend === "up" ? "(+)" : stat.trend === "down" ? "(-)" : "";
            slide.addText(`${trendSymbol} ${stat.change || ""}`, {
                x,
                y: LAYOUT_POSITIONS.statRow.y + 1.8,
                w: statWidth - 0.2,
                h: 0.4,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: trendColor,
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
    });
}
export function renderChartSlide(slide, title, content, chartType, theme) {
    addTitle(slide, title, theme);
    // Validate chartData exists with valid series
    if (!content.chartData ||
        !content.chartData.series ||
        content.chartData.series.length === 0) {
        // Add placeholder text for empty chart
        slide.addText("No chart data available", {
            x: LAYOUT_POSITIONS.chart.x,
            y: LAYOUT_POSITIONS.chart.y + 1,
            w: LAYOUT_POSITIONS.chart.w,
            h: 1,
            fontSize: 18,
            color: theme.colors.muted.replace("#", ""),
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
        return;
    }
    // Filter out invalid series (must have name, labels array, and values array)
    const validSeries = content.chartData.series.filter((series) => series.name &&
        Array.isArray(series.labels) &&
        series.labels.length > 0 &&
        Array.isArray(series.values) &&
        series.values.length > 0);
    if (validSeries.length === 0) {
        slide.addText("Invalid chart data format", {
            x: LAYOUT_POSITIONS.chart.x,
            y: LAYOUT_POSITIONS.chart.y + 1,
            w: LAYOUT_POSITIONS.chart.w,
            h: 1,
            fontSize: 18,
            color: theme.colors.muted.replace("#", ""),
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
        return;
    }
    const pptxChartType = getPptxChartType(chartType);
    const chartData = validSeries.map((series) => ({
        name: series.name,
        labels: series.labels,
        values: series.values,
    }));
    slide.addChart(pptxChartType, chartData, {
        x: LAYOUT_POSITIONS.chart.x,
        y: LAYOUT_POSITIONS.chart.y,
        w: LAYOUT_POSITIONS.chart.w,
        h: LAYOUT_POSITIONS.chart.h,
        showTitle: !!content.chartData.title,
        title: content.chartData.title,
        showLegend: content.chartData.legendPosition !== "none",
        legendPos: LEGEND_POS_MAP[content.chartData.legendPosition || "bottom"] || "b",
        showValue: content.chartData.showLabels,
        chartColors: [
            theme.colors.primary.replace("#", ""),
            theme.colors.secondary.replace("#", ""),
            theme.colors.accent.replace("#", ""),
        ],
    });
}
export function renderTableSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    if (!content.tableData) {
        return;
    }
    const { headers, rows, hasHeader } = content.tableData;
    const tableRows = [];
    if (hasHeader && headers) {
        tableRows.push(headers.map((header) => ({
            text: header,
            options: {
                bold: true,
                fill: { color: theme.colors.primary.replace("#", "") },
                color: theme.colors.textOnPrimary.replace("#", ""),
                align: "center",
            },
        })));
    }
    rows.forEach((row, rowIndex) => {
        tableRows.push(row.map((cell) => ({
            text: cell.text,
            options: {
                fill: { color: rowIndex % 2 === 0 ? "F8FAFC" : "FFFFFF" },
                color: theme.colors.text.replace("#", ""),
                align: (cell.align || "left"),
            },
        })));
    });
    slide.addTable(tableRows, {
        x: LAYOUT_POSITIONS.chart.x,
        y: LAYOUT_POSITIONS.chart.y,
        w: LAYOUT_POSITIONS.chart.w,
        colW: Array(headers?.length || rows[0]?.length || 1).fill(LAYOUT_POSITIONS.chart.w / (headers?.length || rows[0]?.length || 1)),
        fontSize: theme.fonts.sizes.body - 2,
        fontFace: theme.fonts.body,
        border: { pt: 0.5, color: "E2E8F0" },
        autoPage: true,
    });
    if (content.tableData.caption) {
        slide.addText(content.tableData.caption, {
            x: 0.5,
            y: 5.1,
            w: 9,
            h: 0.3,
            fontSize: theme.fonts.sizes.caption,
            fontFace: theme.fonts.body,
            color: theme.colors.muted.replace("#", ""),
            align: "center",
        });
    }
}
// ============================================================================
// SLIDE RENDERERS - PROCESS & TIMELINE
// ============================================================================
export function renderTimelineSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    if (!content.timeline?.items) {
        return;
    }
    const items = content.timeline.items.slice(0, 5);
    const isHorizontal = content.timeline.orientation !== "vertical";
    if (isHorizontal) {
        renderHorizontalTimeline(slide, items, theme);
    }
    else {
        renderVerticalTimeline(slide, items, theme);
    }
}
function renderHorizontalTimeline(slide, items, theme) {
    const itemWidth = 8 / items.length;
    const lineY = 2.8;
    slide.addShape("rect", {
        x: 1,
        y: lineY,
        w: 8,
        h: 0.02,
        fill: { color: theme.colors.primary.replace("#", "") },
    });
    items.forEach((item, index) => {
        const x = 1 + index * itemWidth + itemWidth / 2 - 0.15;
        slide.addShape("ellipse", {
            x,
            y: lineY - 0.15,
            w: 0.3,
            h: 0.3,
            fill: { color: theme.colors.primary.replace("#", "") },
        });
        slide.addText(item.date, {
            x: x - itemWidth / 2 + 0.15,
            y: lineY - 0.8,
            w: itemWidth,
            h: 0.4,
            fontSize: theme.fonts.sizes.caption,
            fontFace: theme.fonts.body,
            color: theme.colors.primary.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        slide.addText(item.title, {
            x: x - itemWidth / 2 + 0.15,
            y: lineY + 0.4,
            w: itemWidth,
            h: 0.4,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.heading,
            color: theme.colors.text.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        if (item.description) {
            slide.addText(item.description, {
                x: x - itemWidth / 2 + 0.15,
                y: lineY + 0.85,
                w: itemWidth,
                h: 0.8,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: theme.colors.muted.replace("#", ""),
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
    });
}
function renderVerticalTimeline(slide, items, theme) {
    const itemHeight = 3 / items.length;
    const lineX = 1.5;
    slide.addShape("rect", {
        x: lineX,
        y: 1.5,
        w: 0.04,
        h: 3.5,
        fill: { color: theme.colors.primary.replace("#", "") },
    });
    items.forEach((item, index) => {
        const y = 1.7 + index * itemHeight;
        slide.addShape("ellipse", {
            x: lineX - 0.12,
            y,
            w: 0.28,
            h: 0.28,
            fill: { color: theme.colors.primary.replace("#", "") },
        });
        slide.addText(item.date, {
            x: 0.3,
            y: y - 0.1,
            w: 1,
            h: 0.3,
            fontSize: theme.fonts.sizes.caption,
            fontFace: theme.fonts.body,
            color: theme.colors.primary.replace("#", ""),
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        slide.addText(item.title, {
            x: 2,
            y: y - 0.1,
            w: 7,
            h: 0.4,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.heading,
            color: theme.colors.text.replace("#", ""),
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        if (item.description) {
            slide.addText(item.description, {
                x: 2,
                y: y + 0.3,
                w: 7,
                h: 0.4,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: theme.colors.muted.replace("#", ""),
                fit: DEFAULT_TEXT_FIT,
            });
        }
    });
}
export function renderProcessFlowSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    if (!content.processSteps) {
        return;
    }
    const steps = content.processSteps.slice(0, 5);
    const stepWidth = 8 / steps.length;
    steps.forEach((step, index) => {
        const x = 1 + index * stepWidth;
        const boxWidth = stepWidth - 0.4;
        slide.addShape("roundRect", {
            x,
            y: 2,
            w: boxWidth,
            h: 2,
            fill: { color: theme.colors.primary.replace("#", "") },
            rectRadius: 0.1,
        });
        slide.addText(String(step.step), {
            x,
            y: 2.1,
            w: boxWidth,
            h: 0.5,
            fontSize: 24,
            fontFace: theme.fonts.heading,
            color: theme.colors.textOnPrimary.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        slide.addText(step.title, {
            x,
            y: 2.6,
            w: boxWidth,
            h: 0.5,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.heading,
            color: theme.colors.textOnPrimary.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        if (step.description) {
            slide.addText(step.description, {
                x,
                y: 3.2,
                w: boxWidth,
                h: 0.7,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: theme.colors.textOnPrimary.replace("#", ""),
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
        if (index < steps.length - 1) {
            // Use rightArrow shape for better visibility
            slide.addShape("rightArrow", {
                x: x + boxWidth + 0.02,
                y: 2.85,
                w: 0.35,
                h: 0.3,
                fill: { color: theme.colors.primary.replace("#", "") },
            });
        }
    });
}
// ============================================================================
// SLIDE RENDERERS - COMPARISON & FEATURES
// ============================================================================
export function renderComparisonSlide(slide, title, content, theme) {
    addTitle(slide, title, theme, false);
    if (!content.comparison?.columns) {
        return;
    }
    const columns = content.comparison.columns.slice(0, 2);
    const columnWidth = 4.2;
    // Calculate available height for bullet items (from y=2.0 to y=5.0 = 3.0 inches)
    const contentStartY = 2.0;
    const contentEndY = 5.0;
    const availableHeight = contentEndY - contentStartY;
    // Find max items across columns to calculate consistent spacing
    const maxItems = Math.max(...columns.map((col) => Math.min(col.items?.length || 0, 6)));
    // Calculate item height with minimum gap (at least 0.1 inch gap between items)
    const itemHeight = maxItems > 0
        ? Math.min(0.7, (availableHeight - MIN_GAP * maxItems) / maxItems)
        : 0.5;
    const effectiveSpacing = itemHeight + MIN_GAP;
    columns.forEach((col, index) => {
        const x = 0.5 + index * 4.8;
        slide.addShape("roundRect", {
            x,
            y: 1.4,
            w: columnWidth,
            h: 0.5,
            fill: {
                color: col.highlight
                    ? theme.colors.primary.replace("#", "")
                    : theme.colors.muted.replace("#", ""),
            },
            rectRadius: 0.05,
        });
        slide.addText(col.title, {
            x,
            y: 1.4,
            w: columnWidth,
            h: 0.5,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.heading,
            color: col.highlight
                ? theme.colors.textOnPrimary.replace("#", "")
                : theme.colors.text.replace("#", ""),
            align: "center",
            bold: true,
            valign: "middle",
            fit: DEFAULT_TEXT_FIT,
        });
        // Limit items to prevent overflow (max 6 items)
        const items = (col.items || []).slice(0, 6);
        // Calculate font size based on item count (smaller font for more items)
        const fontSize = items.length > 4
            ? Math.max(12, theme.fonts.sizes.body - 2)
            : theme.fonts.sizes.body;
        items.forEach((item, itemIndex) => {
            // Check for markdown formatting
            if (hasMarkdownFormatting(item)) {
                const segments = parseMarkdownText(item);
                const textRuns = [
                    {
                        text: "• ",
                        options: {
                            fontSize,
                            fontFace: theme.fonts.body,
                            color: theme.colors.text.replace("#", ""),
                        },
                    },
                    ...createFormattedTextProps(segments, {
                        fontSize,
                        fontFace: theme.fonts.body,
                        color: theme.colors.text.replace("#", ""),
                    }),
                ];
                slide.addText(textRuns, {
                    x: x + 0.2,
                    y: contentStartY + itemIndex * effectiveSpacing,
                    w: columnWidth - 0.4,
                    h: itemHeight,
                    valign: "top",
                    fit: DEFAULT_TEXT_FIT,
                });
            }
            else {
                slide.addText(`• ${item}`, {
                    x: x + 0.2,
                    y: contentStartY + itemIndex * effectiveSpacing,
                    w: columnWidth - 0.4,
                    h: itemHeight,
                    fontSize,
                    fontFace: theme.fonts.body,
                    color: theme.colors.text.replace("#", ""),
                    valign: "top",
                    fit: DEFAULT_TEXT_FIT,
                });
            }
        });
    });
}
export function renderFeaturesSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    const rawFeatures = content.features || [];
    const rawIcons = content.icons || [];
    const normalizedFeatures = rawFeatures.length > 0
        ? rawFeatures
        : rawIcons.map((icon) => ({
            title: icon.label,
            description: icon.description || "",
            icon: icon.icon,
        }));
    if (normalizedFeatures.length === 0) {
        return;
    }
    const itemsPerRow = Math.min(normalizedFeatures.length, 3);
    const itemWidth = 9 / itemsPerRow;
    normalizedFeatures
        .slice(0, 6)
        .forEach((feature, index) => {
        const row = Math.floor(index / itemsPerRow);
        const col = index % itemsPerRow;
        const x = 0.5 + col * itemWidth;
        const y = 1.6 + row * 1.8;
        if (feature.icon) {
            const codePoint = parseInt(feature.icon, 16);
            if (!Number.isNaN(codePoint) &&
                codePoint >= 0 &&
                codePoint <= 0x10ffff) {
                const iconChar = String.fromCodePoint(codePoint);
                slide.addText(iconChar, {
                    x,
                    y,
                    w: itemWidth - 0.2,
                    h: 0.6,
                    fontSize: 36,
                    fontFace: "Segoe UI Emoji",
                    color: theme.colors.primary.replace("#", ""),
                    align: "center",
                    fit: DEFAULT_TEXT_FIT,
                });
            }
        }
        slide.addText(feature.title, {
            x,
            y: y + 0.6,
            w: itemWidth - 0.2,
            h: 0.4,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.heading,
            color: theme.colors.text.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        if (feature.description) {
            slide.addText(feature.description, {
                x,
                y: y + 1,
                w: itemWidth - 0.2,
                h: 0.6,
                fontSize: theme.fonts.sizes.caption,
                fontFace: theme.fonts.body,
                color: theme.colors.muted.replace("#", ""),
                align: "center",
                fit: DEFAULT_TEXT_FIT,
            });
        }
    });
}
export function renderTeamSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    if (!content.teamMembers) {
        return;
    }
    const members = content.teamMembers.slice(0, 4);
    const memberWidth = 9 / members.length;
    members.forEach((member, index) => {
        const x = 0.5 + index * memberWidth;
        slide.addShape("ellipse", {
            x: x + (memberWidth - 1.5) / 2,
            y: 1.6,
            w: 1.5,
            h: 1.5,
            fill: { color: theme.colors.muted.replace("#", "") },
        });
        const initials = member.name
            .split(" ")
            .map((n) => n[0])
            .join("")
            .substring(0, 2);
        slide.addText(initials, {
            x: x + (memberWidth - 1.5) / 2,
            y: 2,
            w: 1.5,
            h: 0.7,
            fontSize: 28,
            fontFace: theme.fonts.heading,
            color: "FFFFFF",
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        slide.addText(member.name, {
            x,
            y: 3.3,
            w: memberWidth - 0.2,
            h: 0.4,
            fontSize: theme.fonts.sizes.body,
            fontFace: theme.fonts.heading,
            color: theme.colors.text.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        slide.addText(member.role, {
            x,
            y: 3.7,
            w: memberWidth - 0.2,
            h: 0.3,
            fontSize: theme.fonts.sizes.caption,
            fontFace: theme.fonts.body,
            color: theme.colors.muted.replace("#", ""),
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
    });
}
export function renderConclusionSlide(slide, title, content, theme) {
    addTitle(slide, title, theme);
    if (content.bullets) {
        const checkmarkBullets = content.bullets.map((bullet) => ({
            ...bullet,
            icon: bullet.icon || "2713",
        }));
        addBullets(slide, checkmarkBullets, LAYOUT_POSITIONS.contentFull, theme, "conclusion");
    }
    if (content.cta) {
        slide.addText(content.cta, {
            x: 0.5,
            y: 4.6,
            w: 9,
            h: 0.5,
            fontSize: theme.fonts.sizes.heading - 4,
            fontFace: theme.fonts.heading,
            color: theme.colors.primary.replace("#", ""),
            align: "center",
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
    }
}
// ============================================================================
// COMPOSITE/DASHBOARD SLIDE RENDERERS
// Mixed content types on a single slide
// ============================================================================
/**
 * Predefined grid layouts for composite slides
 */
export const COMPOSITE_LAYOUTS = {
    // Two halves side by side
    "left-right": [
        { x: 0.5, y: 1.4, w: 4.3, h: 3.6 },
        { x: 5.2, y: 1.4, w: 4.3, h: 3.6 },
    ],
    // Top and bottom
    "top-bottom": [
        { x: 0.5, y: 1.2, w: 9, h: 1.8 },
        { x: 0.5, y: 3.2, w: 9, h: 2.0 },
    ],
    // Three columns
    "three-cols": [
        { x: 0.5, y: 1.4, w: 2.8, h: 3.6 },
        { x: 3.5, y: 1.4, w: 2.8, h: 3.6 },
        { x: 6.5, y: 1.4, w: 2.8, h: 3.6 },
    ],
    // Four quadrants
    quadrants: [
        { x: 0.5, y: 1.2, w: 4.3, h: 1.8 },
        { x: 5.2, y: 1.2, w: 4.3, h: 1.8 },
        { x: 0.5, y: 3.2, w: 4.3, h: 1.8 },
        { x: 5.2, y: 3.2, w: 4.3, h: 1.8 },
    ],
    // Five boxes (2 on top, 3 on bottom)
    "five-boxes": [
        { x: 0.5, y: 1.2, w: 4.3, h: 1.6 },
        { x: 5.2, y: 1.2, w: 4.3, h: 1.6 },
        { x: 0.5, y: 3.0, w: 2.8, h: 2.0 },
        { x: 3.5, y: 3.0, w: 2.8, h: 2.0 },
        { x: 6.5, y: 3.0, w: 2.8, h: 2.0 },
    ],
    // Six boxes (2x3 grid)
    "six-boxes": [
        { x: 0.5, y: 1.2, w: 2.8, h: 1.6 },
        { x: 3.5, y: 1.2, w: 2.8, h: 1.6 },
        { x: 6.5, y: 1.2, w: 2.8, h: 1.6 },
        { x: 0.5, y: 3.0, w: 2.8, h: 1.8 },
        { x: 3.5, y: 3.0, w: 2.8, h: 1.8 },
        { x: 6.5, y: 3.0, w: 2.8, h: 1.8 },
    ],
    // Main content left, sidebar right
    "main-sidebar": [
        { x: 0.5, y: 1.4, w: 5.8, h: 3.6 },
        { x: 6.5, y: 1.4, w: 3.0, h: 3.6 },
    ],
    // Wide top, three boxes bottom
    "top-three": [
        { x: 0.5, y: 1.2, w: 9, h: 1.6 },
        { x: 0.5, y: 3.0, w: 2.8, h: 2.0 },
        { x: 3.5, y: 3.0, w: 2.8, h: 2.0 },
        { x: 6.5, y: 3.0, w: 2.8, h: 2.0 },
    ],
};
/**
 * Render a content box with background
 */
function renderContentBox(slide, pos, title, theme, isPrimary = false) {
    // Add background box
    slide.addShape("roundRect", {
        x: pos.x,
        y: pos.y,
        w: pos.w,
        h: pos.h,
        fill: {
            color: isPrimary
                ? theme.colors.primary.replace("#", "") + "15"
                : "F8F9FA",
        },
        line: {
            color: isPrimary ? theme.colors.primary.replace("#", "") : "E5E7EB",
            width: 1,
        },
        rectRadius: 0.1,
    });
    let contentY = pos.y + 0.15;
    let contentH = pos.h - 0.3;
    // Add title if provided
    if (title) {
        slide.addText(title, {
            x: pos.x + 0.15,
            y: pos.y + 0.1,
            w: pos.w - 0.3,
            h: 0.35,
            fontSize: 14,
            fontFace: theme.fonts.heading,
            color: isPrimary
                ? theme.colors.primary.replace("#", "")
                : theme.colors.text.replace("#", ""),
            bold: true,
            fit: DEFAULT_TEXT_FIT,
        });
        contentY = pos.y + 0.45;
        contentH = pos.h - 0.55;
    }
    return { contentY, contentH };
}
/**
 * Render bullets in a zone
 */
function renderBulletsInZone(slide, bullets, pos, theme, contentY, contentH) {
    if (!bullets || bullets.length === 0) {
        return;
    }
    const fontSize = Math.max(11, Math.min(14, 16 - bullets.length));
    const color = theme.colors.text.replace("#", "");
    bullets.forEach((bullet, idx) => {
        // Skip invalid bullets
        if (!bullet || !bullet.text) {
            return;
        }
        const bulletY = contentY + idx * (contentH / bullets.length);
        // Check if bullet text contains markdown formatting
        if (hasMarkdownFormatting(bullet.text)) {
            const segments = parseMarkdownText(bullet.text);
            const textRuns = [
                {
                    text: "• ",
                    options: { fontSize, fontFace: theme.fonts.body, color },
                },
                ...createFormattedTextProps(segments, {
                    fontSize,
                    fontFace: theme.fonts.body,
                    color,
                }),
            ];
            slide.addText(textRuns, {
                x: pos.x + 0.15,
                y: bulletY,
                w: pos.w - 0.3,
                h: contentH / bullets.length,
                valign: "top",
                fit: DEFAULT_TEXT_FIT,
            });
        }
        else {
            slide.addText(`• ${bullet.text}`, {
                x: pos.x + 0.15,
                y: bulletY,
                w: pos.w - 0.3,
                h: contentH / bullets.length,
                fontSize,
                fontFace: theme.fonts.body,
                color,
                valign: "top",
                fit: DEFAULT_TEXT_FIT,
            });
        }
    });
}
/**
 * Render mini stat in a zone
 */
function renderStatInZone(slide, stat, pos, theme, contentY, contentH) {
    // Value
    slide.addText(stat.value, {
        x: pos.x,
        y: contentY,
        w: pos.w,
        h: contentH * 0.6,
        fontSize: Math.min(32, pos.w * 8),
        fontFace: theme.fonts.heading,
        color: theme.colors.primary.replace("#", ""),
        bold: true,
        align: "center",
        valign: "bottom",
        fit: DEFAULT_TEXT_FIT,
    });
    // Label
    slide.addText(stat.label, {
        x: pos.x,
        y: contentY + contentH * 0.6,
        w: pos.w,
        h: contentH * 0.4,
        fontSize: 11,
        fontFace: theme.fonts.body,
        color: theme.colors.muted.replace("#", ""),
        align: "center",
        valign: "top",
        fit: DEFAULT_TEXT_FIT,
    });
}
/**
 * Render icon box in a zone
 */
function renderIconBoxInZone(options) {
    const { slide, icon, label, description, pos, theme, contentY, contentH } = options;
    // Icon circle
    const iconSize = Math.min(0.6, pos.w * 0.2);
    slide.addShape("ellipse", {
        x: pos.x + (pos.w - iconSize) / 2,
        y: contentY,
        w: iconSize,
        h: iconSize,
        fill: { color: theme.colors.primary.replace("#", "") },
    });
    // Icon text (Unicode)
    const codePoint = parseInt(icon, 16);
    if (Number.isNaN(codePoint) || codePoint < 0 || codePoint > 0x10ffff) {
        logger.warn("[renderIconBoxInZone] Invalid icon code", { icon });
        return;
    }
    const iconChar = String.fromCodePoint(codePoint);
    slide.addText(iconChar, {
        x: pos.x + (pos.w - iconSize) / 2,
        y: contentY,
        w: iconSize,
        h: iconSize,
        fontSize: 16,
        color: "FFFFFF",
        align: "center",
        valign: "middle",
        fit: DEFAULT_TEXT_FIT,
    });
    // Label
    slide.addText(label, {
        x: pos.x,
        y: contentY + iconSize + 0.1,
        w: pos.w,
        h: 0.3,
        fontSize: 12,
        fontFace: theme.fonts.heading,
        color: theme.colors.text.replace("#", ""),
        bold: true,
        align: "center",
        fit: DEFAULT_TEXT_FIT,
    });
    // Description
    if (description) {
        slide.addText(description, {
            x: pos.x + 0.1,
            y: contentY + iconSize + 0.4,
            w: pos.w - 0.2,
            h: contentH - iconSize - 0.5,
            fontSize: 10,
            fontFace: theme.fonts.body,
            color: theme.colors.muted.replace("#", ""),
            align: "center",
            fit: DEFAULT_TEXT_FIT,
        });
    }
}
/**
 * Render a mini chart in a zone
 */
function renderMiniChartInZone(slide, chartData, pos, theme, contentY, contentH) {
    const chartType = chartData.type === "pie"
        ? "pie"
        : chartData.type === "line"
            ? "line"
            : "bar";
    const series = chartData.series || [];
    if (series.length === 0) {
        return;
    }
    const chartDataForPptx = series.map((s) => ({
        name: s.name || "Data",
        labels: s.labels || [],
        values: s.values || [],
    }));
    try {
        slide.addChart(chartType, chartDataForPptx, {
            x: pos.x + 0.1,
            y: contentY,
            w: pos.w - 0.2,
            h: contentH,
            showLegend: false,
            showTitle: false,
            chartColors: [
                theme.colors.primary.replace("#", ""),
                theme.colors.secondary.replace("#", ""),
                theme.colors.accent.replace("#", ""),
                theme.colors.muted.replace("#", ""),
            ],
        });
    }
    catch {
        // Fallback: show placeholder
        slide.addText("Chart", {
            x: pos.x,
            y: contentY,
            w: pos.w,
            h: contentH,
            fontSize: 14,
            color: theme.colors.muted.replace("#", ""),
            align: "center",
            valign: "middle",
            fit: DEFAULT_TEXT_FIT,
        });
    }
}
/**
 * Render a composite/dashboard slide with multiple content zones
 *
 * @example
 * // Left: bullets, Right: chart
 * renderDashboardSlide(slide, "Overview", {
 *   layout: "left-right",
 *   zones: [
 *     { type: "bullets", title: "Key Points", data: bullets },
 *     { type: "chart", title: "Trend", data: chartData }
 *   ]
 * }, theme);
 */
export function renderDashboardSlide(slide, title, content, theme) {
    addTitle(slide, title, theme, false);
    const layoutPositions = COMPOSITE_LAYOUTS[content.layout] || COMPOSITE_LAYOUTS["left-right"];
    const zones = content.zones || [];
    zones.forEach((zone, idx) => {
        if (idx >= layoutPositions.length) {
            return;
        }
        const pos = layoutPositions[idx];
        const { contentY, contentH } = renderContentBox(slide, pos, zone.title, theme, zone.isPrimary);
        switch (zone.type) {
            case "bullets":
                if (Array.isArray(zone.data)) {
                    renderBulletsInZone(slide, zone.data, pos, theme, contentY, contentH);
                }
                break;
            case "stats":
                if (zone.data && typeof zone.data === "object") {
                    renderStatInZone(slide, zone.data, pos, theme, contentY, contentH);
                }
                break;
            case "icon-box":
                if (zone.data && typeof zone.data === "object") {
                    const iconData = zone.data;
                    renderIconBoxInZone({
                        slide,
                        icon: iconData.icon,
                        label: iconData.label,
                        description: iconData.description,
                        pos,
                        theme,
                        contentY,
                        contentH,
                    });
                }
                break;
            case "chart":
                if (zone.data && typeof zone.data === "object") {
                    renderMiniChartInZone(slide, zone.data, pos, theme, contentY, contentH);
                }
                break;
            case "text-box":
                if (typeof zone.data === "string") {
                    slide.addText(zone.data, {
                        x: pos.x + 0.15,
                        y: contentY,
                        w: pos.w - 0.3,
                        h: contentH,
                        fontSize: 12,
                        fontFace: theme.fonts.body,
                        color: theme.colors.text.replace("#", ""),
                        valign: "top",
                    });
                }
                break;
        }
    });
}
/**
 * Render a mixed content slide with left bullets and right chart
 * Common pattern: explanation on left, visualization on right
 */
export function renderMixedContentSlide(slide, title, content, theme) {
    addTitle(slide, title, theme, false);
    // Left side: bullets or text
    if (content.bullets && content.bullets.length > 0) {
        const leftPos = COMPOSITE_LAYOUTS["left-right"][0];
        const { contentY, contentH } = renderContentBox(slide, leftPos, content.leftColumn?.title, theme, true);
        renderBulletsInZone(slide, content.bullets, leftPos, theme, contentY, contentH);
    }
    // Right side: chart if available
    if (content.chartData) {
        const rightPos = COMPOSITE_LAYOUTS["left-right"][1];
        const { contentY, contentH } = renderContentBox(slide, rightPos, "Data", theme, false);
        renderMiniChartInZone(slide, content.chartData, rightPos, theme, contentY, contentH);
    }
    // Or statistics
    else if (content.statistics && content.statistics.length > 0) {
        const rightPos = COMPOSITE_LAYOUTS["left-right"][1];
        renderContentBox(slide, rightPos, "Metrics", theme, false);
        // Show up to 3 stats stacked
        const stats = content.statistics.slice(0, 3);
        const statHeight = rightPos.h / stats.length;
        stats.forEach((stat, idx) => {
            renderStatInZone(slide, stat, { ...rightPos, y: rightPos.y + idx * statHeight, h: statHeight }, theme, rightPos.y + idx * statHeight + 0.1, statHeight - 0.2);
        });
    }
}
/**
 * Render a stats grid slide with multiple stat boxes
 */
export function renderStatsGridSlide(slide, title, content, theme) {
    addTitle(slide, title, theme, false);
    const stats = content.statistics || [];
    if (stats.length === 0) {
        return;
    }
    // Choose layout based on stat count
    let layout;
    if (stats.length <= 2) {
        layout = COMPOSITE_LAYOUTS["left-right"];
    }
    else if (stats.length <= 3) {
        layout = COMPOSITE_LAYOUTS["three-cols"];
    }
    else if (stats.length <= 4) {
        layout = COMPOSITE_LAYOUTS["quadrants"];
    }
    else if (stats.length <= 5) {
        layout = COMPOSITE_LAYOUTS["five-boxes"];
    }
    else {
        layout = COMPOSITE_LAYOUTS["six-boxes"];
    }
    stats.slice(0, layout.length).forEach((stat, idx) => {
        const pos = layout[idx];
        const isPrimary = idx === 0;
        const { contentY, contentH } = renderContentBox(slide, pos, undefined, theme, isPrimary);
        renderStatInZone(slide, stat, pos, theme, contentY, contentH);
    });
}
/**
 * Render an icon grid slide with multiple icon boxes
 */
export function renderIconGridSlide(slide, title, content, theme) {
    addTitle(slide, title, theme, false);
    const icons = content.icons || content.features || [];
    if (icons.length === 0) {
        return;
    }
    // Choose layout based on icon count
    let layout;
    if (icons.length <= 2) {
        layout = COMPOSITE_LAYOUTS["left-right"];
    }
    else if (icons.length <= 3) {
        layout = COMPOSITE_LAYOUTS["three-cols"];
    }
    else if (icons.length <= 4) {
        layout = COMPOSITE_LAYOUTS["quadrants"];
    }
    else if (icons.length <= 5) {
        layout = COMPOSITE_LAYOUTS["five-boxes"];
    }
    else {
        layout = COMPOSITE_LAYOUTS["six-boxes"];
    }
    icons.slice(0, layout.length).forEach((item, idx) => {
        const pos = layout[idx];
        const isPrimary = idx === 0;
        const { contentY, contentH } = renderContentBox(slide, pos, undefined, theme, isPrimary);
        const icon = item.icon || "1F4A1";
        const label = item.title || item.label || "";
        const description = item.description;
        renderIconBoxInZone({
            slide,
            icon,
            label,
            description,
            pos,
            theme,
            contentY,
            contentH,
        });
    });
}