/**
 * Utility functions for Slack bubble
 * Handles markdown to Slack blocks conversion and mrkdwn formatting
 */
/**
 * Converts standard markdown text formatting to Slack mrkdwn format.
 *
 * Conversions:
 * - **bold** or __bold__ → *bold*
 * - *italic* (when not **) or _italic_ (when not __) → _italic_
 * - ~~strikethrough~~ → ~strikethrough~
 * - `code` → `code` (unchanged)
 * - [text](url) → <url|text>
 * - > blockquote → > blockquote (unchanged, Slack supports this)
 *
 * @param markdown - Standard markdown text
 * @returns Slack mrkdwn formatted text
 */
export function markdownToMrkdwn(markdown) {
    if (!markdown || typeof markdown !== 'string') {
        return '';
    }
    let result = markdown;
    // Convert links: [text](url) → <url|text>
    // Must be done first to preserve link text formatting
    result = result.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>');
    // Convert bullet lists: - item or * item → • item
    // Must be done BEFORE italic/bold to avoid * being treated as formatting
    result = result.replace(/^(\s*)[-]\s+/gm, '$1• ');
    result = result.replace(/^(\s*)\*\s+/gm, '$1• ');
    // Use placeholder tokens to safely convert bold before italic
    // This prevents **text** from being partially matched by *text* pattern
    const BOLD_PLACEHOLDER = '\u0000BOLD\u0000';
    // Convert headers for inline display: # Header → BOLD placeholder
    // Using placeholder so it doesn't get converted by italic regex
    result = result.replace(/^#{1,6}\s+(.+)$/gm, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
    // Convert bold: **text** → placeholder
    result = result.replace(/\*\*([^*]+)\*\*/g, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
    // Convert bold: __text__ → placeholder
    result = result.replace(/__([^_]+)__/g, `${BOLD_PLACEHOLDER}$1${BOLD_PLACEHOLDER}`);
    // Convert italic: *text* (single asterisk) → _text_
    // Now safe because ** and headers have been converted to placeholder
    result = result.replace(/\*([^*]+)\*/g, '_$1_');
    // Convert italic: _text_ (single underscore, not double) → _text_ (unchanged for Slack)
    // Already correct format for Slack mrkdwn
    // Replace bold placeholders with Slack bold syntax
    result = result.replace(new RegExp(BOLD_PLACEHOLDER, 'g'), '*');
    // Convert strikethrough: ~~text~~ → ~text~
    result = result.replace(/~~([^~]+)~~/g, '~$1~');
    return result;
}
/**
 * Normalizes markdown text by ensuring block-level elements start on new lines.
 * This handles cases where markdown is pasted without proper line breaks.
 */
function normalizeMarkdownNewlines(markdown) {
    let result = markdown;
    // Add newlines before headers (### Header) if not already at line start
    // Lookbehind ensures we don't add newline if already at start or after newline
    result = result.replace(/([^\n])(#{1,6}\s+\S)/g, '$1\n$2');
    // Add newlines before horizontal rules (--- or ***)
    result = result.replace(/([^\n\s])\s*(---+|___+|\*\*\*+)\s*(?=\S|$)/g, '$1\n$2\n');
    // Add newlines before list items (- ** or * **) that appear after sentence endings
    // This catches patterns like "...text. * **Item:**" or "...documentation. - **Item:**"
    result = result.replace(/([.!?:])(\s+)([-*])\s+(\*\*)/g, '$1\n$3 $4');
    // Add newlines before standalone list items (lines starting with - or * followed by space and text)
    // But only after sentence-ending punctuation to avoid false positives
    result = result.replace(/([.!?])(\s+)([-*]\s+)(?!\*)/g, '$1\n$3');
    return result;
}
function parseMarkdownBlocks(markdown) {
    const blocks = [];
    // Normalize markdown to ensure block elements are on their own lines
    const normalizedMarkdown = normalizeMarkdownNewlines(markdown);
    const lines = normalizedMarkdown.split('\n');
    let currentBlock = null;
    let inCodeBlock = false;
    let codeBlockContent = [];
    let codeBlockLanguage = '';
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        // Check for code block start/end
        if (line.startsWith('```')) {
            if (!inCodeBlock) {
                // Start of code block
                if (currentBlock) {
                    blocks.push(currentBlock);
                    currentBlock = null;
                }
                inCodeBlock = true;
                codeBlockLanguage = line.slice(3).trim();
                codeBlockContent = [];
            }
            else {
                // End of code block
                blocks.push({
                    type: 'code',
                    content: codeBlockContent.join('\n'),
                    language: codeBlockLanguage,
                });
                inCodeBlock = false;
                codeBlockContent = [];
                codeBlockLanguage = '';
            }
            continue;
        }
        if (inCodeBlock) {
            codeBlockContent.push(line);
            continue;
        }
        // Check for horizontal rule / divider
        if (/^[-*_]{3,}\s*$/.test(line)) {
            if (currentBlock) {
                blocks.push(currentBlock);
                currentBlock = null;
            }
            blocks.push({ type: 'divider', content: '' });
            continue;
        }
        // Check for header
        const headerMatch = line.match(/^(#{1,6})\s+(.+)$/);
        if (headerMatch) {
            if (currentBlock) {
                blocks.push(currentBlock);
                currentBlock = null;
            }
            blocks.push({
                type: 'header',
                content: headerMatch[2],
                level: headerMatch[1].length,
            });
            continue;
        }
        // Check for blockquote
        if (line.startsWith('>')) {
            const quoteContent = line.replace(/^>\s*/, '');
            if (currentBlock?.type === 'quote') {
                currentBlock.content += '\n' + quoteContent;
            }
            else {
                if (currentBlock) {
                    blocks.push(currentBlock);
                }
                currentBlock = { type: 'quote', content: quoteContent };
            }
            continue;
        }
        // Check for list item
        if (/^[\s]*[-*]\s+/.test(line) || /^[\s]*\d+\.\s+/.test(line)) {
            const listContent = line
                .replace(/^[\s]*[-*]\s+/, '• ')
                .replace(/^[\s]*\d+\.\s+/, (match) => match); // Keep numbered lists as-is
            if (currentBlock?.type === 'list') {
                currentBlock.content += '\n' + listContent;
            }
            else {
                if (currentBlock) {
                    blocks.push(currentBlock);
                }
                currentBlock = { type: 'list', content: listContent };
            }
            continue;
        }
        // Empty line - end current block
        if (line.trim() === '') {
            if (currentBlock) {
                blocks.push(currentBlock);
                currentBlock = null;
            }
            continue;
        }
        // Regular paragraph
        if (currentBlock?.type === 'paragraph') {
            currentBlock.content += '\n' + line;
        }
        else {
            if (currentBlock) {
                blocks.push(currentBlock);
            }
            currentBlock = { type: 'paragraph', content: line };
        }
    }
    // Don't forget the last block
    if (currentBlock) {
        blocks.push(currentBlock);
    }
    // Handle unclosed code block
    if (inCodeBlock && codeBlockContent.length > 0) {
        blocks.push({
            type: 'code',
            content: codeBlockContent.join('\n'),
            language: codeBlockLanguage,
        });
    }
    return blocks;
}
/**
 * Converts markdown text to an array of Slack blocks for rich message formatting.
 *
 * This function parses markdown and creates appropriate Slack block types:
 * - Headers → header blocks or bold section blocks
 * - Paragraphs → section blocks with mrkdwn
 * - Code blocks → section blocks with code formatting
 * - Horizontal rules → divider blocks
 * - Block quotes → section blocks with quote formatting
 * - Lists → section blocks with bullet formatting
 *
 * @param markdown - Standard markdown text
 * @param options - Conversion options
 * @returns Array of Slack blocks
 *
 * @example
 * ```typescript
 * const blocks = markdownToBlocks(`
 * # Welcome
 *
 * This is **bold** and _italic_ text.
 *
 * - Item 1
 * - Item 2
 *
 * \`\`\`javascript
 * const x = 1;
 * \`\`\`
 * `);
 *
 * // Returns:
 * // [
 * //   { type: 'section', text: { type: 'mrkdwn', text: '*Welcome*' } },
 * //   { type: 'section', text: { type: 'mrkdwn', text: 'This is *bold* and _italic_ text.' } },
 * //   { type: 'section', text: { type: 'mrkdwn', text: '• Item 1\n• Item 2' } },
 * //   { type: 'section', text: { type: 'mrkdwn', text: '```const x = 1;```' } }
 * // ]
 * ```
 */
export function markdownToBlocks(markdown, options = {}) {
    const { useHeaderBlocks = false, addDividersAfterHeaders = false, preserveLineBreaks = true, } = options;
    if (!markdown || typeof markdown !== 'string') {
        return [];
    }
    const parsedBlocks = parseMarkdownBlocks(markdown.trim());
    const slackBlocks = [];
    for (const block of parsedBlocks) {
        switch (block.type) {
            case 'header':
                {
                    if (useHeaderBlocks) {
                        // Header blocks only support plain_text and have a 150 char limit
                        slackBlocks.push({
                            type: 'header',
                            text: {
                                type: 'plain_text',
                                text: block.content.slice(0, 150),
                                emoji: true,
                            },
                        });
                    }
                    else {
                        // Strip markdown bold markers since the entire header will be wrapped in bold
                        // This prevents nested/mismatched asterisks like *1. *TanStack Start**
                        let headerContent = block.content
                            .replace(/\*\*([^*]+)\*\*/g, '$1') // Remove **bold**
                            .replace(/__([^_]+)__/g, '$1'); // Remove __bold__
                        // Convert other formatting (links, italics, etc.)
                        headerContent = markdownToMrkdwn(headerContent);
                        // Use section with bold mrkdwn for more formatting flexibility
                        slackBlocks.push({
                            type: 'section',
                            text: {
                                type: 'mrkdwn',
                                text: `*${headerContent}*`,
                            },
                        });
                    }
                }
                if (addDividersAfterHeaders) {
                    slackBlocks.push({ type: 'divider' });
                }
                break;
            case 'divider':
                slackBlocks.push({ type: 'divider' });
                break;
            case 'code':
                // Slack code blocks in mrkdwn
                slackBlocks.push({
                    type: 'section',
                    text: {
                        type: 'mrkdwn',
                        text: '```' + block.content + '```',
                    },
                });
                break;
            case 'quote': {
                // Slack supports > for quotes in mrkdwn
                const quoteLines = block.content.split('\n').map((line) => `> ${line}`);
                slackBlocks.push({
                    type: 'section',
                    text: {
                        type: 'mrkdwn',
                        text: quoteLines.join('\n'),
                    },
                });
                break;
            }
            case 'list':
                slackBlocks.push({
                    type: 'section',
                    text: {
                        type: 'mrkdwn',
                        text: markdownToMrkdwn(block.content),
                    },
                });
                break;
            case 'paragraph':
            default: {
                let content = markdownToMrkdwn(block.content);
                if (!preserveLineBreaks) {
                    content = content.replace(/\n/g, ' ');
                }
                slackBlocks.push({
                    type: 'section',
                    text: {
                        type: 'mrkdwn',
                        text: content,
                    },
                });
                break;
            }
        }
    }
    // Split any blocks that exceed Slack's text character limit
    return splitLongBlocks(slackBlocks);
}
/**
 * Creates a simple text message with optional markdown formatting.
 * Use this for simple messages that don't need complex block structure.
 *
 * @param text - Text to send (supports markdown)
 * @param useMrkdwn - Whether to convert markdown to Slack mrkdwn format
 * @returns A single section block with the formatted text
 */
export function createTextBlock(text, useMrkdwn = true) {
    return {
        type: 'section',
        text: {
            type: 'mrkdwn',
            text: useMrkdwn ? markdownToMrkdwn(text) : text,
        },
    };
}
/**
 * Creates a divider block for visual separation
 */
export function createDividerBlock() {
    return { type: 'divider' };
}
/**
 * Creates a header block with plain text
 * Note: Header blocks have a 150 character limit
 *
 * @param text - Header text (will be truncated to 150 chars)
 */
export function createHeaderBlock(text) {
    return {
        type: 'header',
        text: {
            type: 'plain_text',
            text: text.slice(0, 150),
            emoji: true,
        },
    };
}
/**
 * Creates a context block for secondary information
 * Context blocks display smaller text, useful for timestamps, metadata, etc.
 *
 * @param texts - Array of text strings to display
 */
export function createContextBlock(texts) {
    return {
        type: 'context',
        elements: texts.map((text) => ({
            type: 'mrkdwn',
            text: markdownToMrkdwn(text),
        })),
    };
}
/**
 * Slack's maximum character limit for text in a single block
 */
const SLACK_MAX_BLOCK_TEXT_LENGTH = 3000;
/**
 * Splits long text into chunks that fit within Slack's block text limit.
 * Attempts to split at paragraph boundaries first, then at sentence boundaries,
 * and finally at word boundaries if necessary.
 *
 * @param text - Text to split
 * @param maxLength - Maximum length per chunk (default: 3000)
 * @returns Array of text chunks, each within the limit
 */
export function splitLongText(text, maxLength = SLACK_MAX_BLOCK_TEXT_LENGTH) {
    if (!text || text.length <= maxLength) {
        return [text];
    }
    const chunks = [];
    let remaining = text;
    while (remaining.length > 0) {
        if (remaining.length <= maxLength) {
            chunks.push(remaining);
            break;
        }
        // Find the best split point within maxLength
        let splitPoint = maxLength;
        // Try to split at a double newline (paragraph boundary)
        const paragraphBreak = remaining.lastIndexOf('\n\n', maxLength);
        if (paragraphBreak > maxLength * 0.5) {
            splitPoint = paragraphBreak;
        }
        else {
            // Try to split at a single newline
            const lineBreak = remaining.lastIndexOf('\n', maxLength);
            if (lineBreak > maxLength * 0.5) {
                splitPoint = lineBreak;
            }
            else {
                // Try to split at a sentence boundary (. ! ?)
                const sentenceEnd = Math.max(remaining.lastIndexOf('. ', maxLength), remaining.lastIndexOf('! ', maxLength), remaining.lastIndexOf('? ', maxLength));
                if (sentenceEnd > maxLength * 0.5) {
                    splitPoint = sentenceEnd + 1; // Include the punctuation
                }
                else {
                    // Fall back to splitting at a word boundary
                    const wordBreak = remaining.lastIndexOf(' ', maxLength);
                    if (wordBreak > maxLength * 0.3) {
                        splitPoint = wordBreak;
                    }
                    // If no good break point, just split at maxLength
                }
            }
        }
        chunks.push(remaining.slice(0, splitPoint).trim());
        remaining = remaining.slice(splitPoint).trim();
    }
    return chunks.filter((chunk) => chunk.length > 0);
}
/**
 * Splits a SlackBlock into multiple blocks if its text exceeds the character limit.
 * Only splits section blocks; other block types are returned as-is.
 *
 * @param block - The block to potentially split
 * @returns Array of blocks (1 if no split needed, multiple if text was too long)
 */
function splitLongBlock(block) {
    // Only split section blocks with text
    if (block.type !== 'section' || !block.text) {
        return [block];
    }
    const textContent = block.text.text;
    if (textContent.length <= SLACK_MAX_BLOCK_TEXT_LENGTH) {
        return [block];
    }
    // Split the text and create multiple section blocks
    const textChunks = splitLongText(textContent, SLACK_MAX_BLOCK_TEXT_LENGTH);
    return textChunks.map((chunk) => ({
        type: 'section',
        text: {
            type: block.text.type,
            text: chunk,
        },
    }));
}
/**
 * Processes an array of Slack blocks, splitting any that exceed the text limit.
 *
 * @param blocks - Array of Slack blocks
 * @returns Array of blocks with long texts split into multiple blocks
 */
export function splitLongBlocks(blocks) {
    return blocks.flatMap(splitLongBlock);
}
/**
 * Detects if a string contains markdown formatting.
 * Checks for common markdown patterns like headers, bold, italic, code blocks, lists, etc.
 *
 * @param text - Text to check for markdown
 * @returns True if markdown patterns are detected
 */
export function containsMarkdown(text) {
    if (!text || typeof text !== 'string') {
        return false;
    }
    // Check for headers (# Header)
    if (/^#{1,6}\s+.+$/m.test(text)) {
        return true;
    }
    // Check for bold (**text** or __text__)
    if (/\*\*[^*]+\*\*/.test(text) || /__[^_]+__/.test(text)) {
        return true;
    }
    // Check for italic (*text* or _text_ - but not ** or __)
    if (/(?<!\*)\*[^*]+\*(?!\*)/.test(text) || /(?<!_)_[^_]+_(?!_)/.test(text)) {
        return true;
    }
    // Check for code blocks (```code```)
    if (/```[\s\S]*?```/.test(text)) {
        return true;
    }
    // Check for inline code (`code`)
    if (/`[^`]+`/.test(text)) {
        return true;
    }
    // Check for lists (- item or * item or 1. item)
    if (/^[\s]*[-*]\s+/m.test(text) || /^[\s]*\d+\.\s+/m.test(text)) {
        return true;
    }
    // Check for links [text](url)
    if (/\[([^\]]+)\]\(([^)]+)\)/.test(text)) {
        return true;
    }
    // Check for blockquotes (> text)
    if (/^>\s+/m.test(text)) {
        return true;
    }
    // Check for horizontal rules (--- or ***)
    if (/^[-*_]{3,}\s*$/m.test(text)) {
        return true;
    }
    // Check for strikethrough (~~text~~)
    if (/~~[^~]+~~/.test(text)) {
        return true;
    }
    return false;
}
//# sourceMappingURL=slack.utils.js.map