// OpenCode message part formatting for Discord.
// Converts SDK message parts (text, tools, reasoning) to Discord-friendly format,
// handles file attachments, and provides tool summary generation.
import * as errore from 'errore';
import { createLogger, LogPrefix } from './logger.js';
import { FetchError } from './errors.js';
import { processImage } from './image-utils.js';
import { parsePatchFileCounts } from './patch-text-parser.js';
const logger = createLogger(LogPrefix.FORMATTING);
/**
 * Resolves Discord mentions in message content to human-readable names.
 * Replaces <@userId> with @displayName, <@&roleId> with @roleName, <#channelId> with #channelName.
 */
export function resolveMentions(message) {
    let content = message.content || '';
    // Replace user mentions <@userId> or <@!userId> with @displayName
    for (const [userId, user] of message.mentions.users) {
        const member = message.guild?.members.cache.get(userId);
        const displayName = member?.displayName || user.displayName || user.username;
        content = content.replace(new RegExp(`<@!?${userId}>`, 'g'), `@${displayName}`);
    }
    // Replace role mentions <@&roleId> with @roleName
    for (const [roleId, role] of message.mentions.roles) {
        content = content.replace(new RegExp(`<@&${roleId}>`, 'g'), `@${role.name}`);
    }
    // Replace channel mentions <#channelId> with #channelName
    for (const [channelId, channel] of message.mentions.channels) {
        const name = 'name' in channel ? channel.name : channelId;
        content = content.replace(new RegExp(`<#${channelId}>`, 'g'), `#${name}`);
    }
    return content;
}
/**
 * Escapes Discord inline markdown characters so dynamic content
 * doesn't break formatting when wrapped in *, _, **, etc.
 */
function escapeInlineMarkdown(text) {
    return text.replace(/([*_~|`\\])/g, '\\$1');
}
// parsePatchCounts → imported from patch-text-parser.ts as parsePatchFileCounts
/**
 * Normalize whitespace: convert newlines to spaces and collapse consecutive spaces.
 */
function normalizeWhitespace(text) {
    return text.replace(/[\r\n]+/g, ' ').replace(/\s+/g, ' ');
}
/**
 * Collects and formats the last N assistant parts from session messages.
 * Used by both /resume and /fork to show recent assistant context.
 */
export function collectLastAssistantParts({ messages, limit = 30, }) {
    const allAssistantParts = [];
    for (const message of messages) {
        if (message.info.role === 'assistant') {
            for (const part of message.parts) {
                const content = formatPart(part);
                if (content.trim()) {
                    allAssistantParts.push({ id: part.id, content: content.trimEnd() });
                }
            }
        }
    }
    const partsToRender = allAssistantParts.slice(-limit);
    const partIds = partsToRender.map((p) => p.id);
    const content = partsToRender.map((p) => p.content).join('\n');
    const skippedCount = allAssistantParts.length - partsToRender.length;
    return { partIds, content, skippedCount };
}
export const TEXT_MIME_TYPES = [
    'text/',
    'application/json',
    'application/xml',
    'application/javascript',
    'application/typescript',
    'application/x-yaml',
    'application/toml',
];
export function isTextMimeType(contentType) {
    if (!contentType) {
        return false;
    }
    return TEXT_MIME_TYPES.some((prefix) => contentType.startsWith(prefix));
}
export async function getTextAttachments(message) {
    const textAttachments = Array.from(message.attachments.values()).filter((attachment) => isTextMimeType(attachment.contentType));
    if (textAttachments.length === 0) {
        return '';
    }
    const textContents = await Promise.all(textAttachments.map(async (attachment) => {
        const response = await errore.tryAsync({
            try: () => fetch(attachment.url),
            catch: (e) => new FetchError({ url: attachment.url, cause: e }),
        });
        if (response instanceof Error) {
            return `<attachment filename="${attachment.name}" error="${response.message}" />`;
        }
        if (!response.ok) {
            return `<attachment filename="${attachment.name}" error="Failed to fetch: ${response.status}" />`;
        }
        const text = await response.text();
        return `<attachment filename="${attachment.name}" mime="${attachment.contentType}">\n${text}\n</attachment>`;
    }));
    return textContents.join('\n\n');
}
export async function getFileAttachments(message) {
    const fileAttachments = Array.from(message.attachments.values()).filter((attachment) => {
        const contentType = attachment.contentType || '';
        return (contentType.startsWith('image/') || contentType === 'application/pdf');
    });
    if (fileAttachments.length === 0) {
        return [];
    }
    const results = await Promise.all(fileAttachments.map(async (attachment) => {
        const response = await errore.tryAsync({
            try: () => fetch(attachment.url),
            catch: (e) => new FetchError({ url: attachment.url, cause: e }),
        });
        if (response instanceof Error) {
            logger.error(`Error downloading attachment ${attachment.name}:`, response.message);
            return null;
        }
        if (!response.ok) {
            logger.error(`Failed to fetch attachment ${attachment.name}: ${response.status}`);
            return null;
        }
        const rawBuffer = Buffer.from(await response.arrayBuffer());
        const originalMime = attachment.contentType || 'application/octet-stream';
        // Process image (resize if needed, convert to JPEG)
        const { buffer, mime } = await processImage(rawBuffer, originalMime);
        const base64 = buffer.toString('base64');
        const dataUrl = `data:${mime};base64,${base64}`;
        logger.log(`Attachment ${attachment.name}: ${rawBuffer.length} → ${buffer.length} bytes, ${mime}`);
        return {
            type: 'file',
            mime,
            filename: attachment.name,
            url: dataUrl,
            sourceUrl: attachment.url,
        };
    }));
    return results.filter((r) => r !== null);
}
const MAX_BASH_COMMAND_INLINE_LENGTH = 100;
export function getToolSummaryText(part) {
    if (part.type !== 'tool')
        return '';
    if (part.tool === 'edit') {
        const filePath = part.state.input?.filePath || '';
        const newString = part.state.input?.newString || '';
        const oldString = part.state.input?.oldString || '';
        const added = newString.split('\n').length;
        const removed = oldString.split('\n').length;
        const fileName = filePath.split('/').pop() || '';
        return fileName
            ? `*${escapeInlineMarkdown(fileName)}* (+${added}-${removed})`
            : `(+${added}-${removed})`;
    }
    if (part.tool === 'apply_patch') {
        // Only inputs are available when parts are sent during streaming (output/metadata not yet populated)
        const patchText = part.state.input?.patchText || '';
        if (!patchText) {
            return '';
        }
        const patchCounts = parsePatchFileCounts(patchText);
        return [...patchCounts.entries()]
            .map(([filePath, { additions, deletions }]) => {
            const fileName = filePath.split('/').pop() || '';
            return fileName
                ? `*${escapeInlineMarkdown(fileName)}* (+${additions}-${deletions})`
                : `(+${additions}-${deletions})`;
        })
            .join(', ');
    }
    if (part.tool === 'write') {
        const filePath = part.state.input?.filePath || '';
        const content = part.state.input?.content || '';
        const lines = content.split('\n').length;
        const fileName = filePath.split('/').pop() || '';
        return fileName
            ? `*${escapeInlineMarkdown(fileName)}* (${lines} line${lines === 1 ? '' : 's'})`
            : `(${lines} line${lines === 1 ? '' : 's'})`;
    }
    if (part.tool === 'webfetch') {
        const url = part.state.input?.url || '';
        const urlWithoutProtocol = url.replace(/^https?:\/\//, '');
        return urlWithoutProtocol
            ? `*${escapeInlineMarkdown(urlWithoutProtocol)}*`
            : '';
    }
    if (part.tool === 'read') {
        const filePath = part.state.input?.filePath || '';
        const fileName = filePath.split('/').pop() || '';
        return fileName ? `*${escapeInlineMarkdown(fileName)}*` : '';
    }
    if (part.tool === 'list') {
        const path = part.state.input?.path || '';
        const dirName = path.split('/').pop() || path;
        return dirName ? `*${escapeInlineMarkdown(dirName)}*` : '';
    }
    if (part.tool === 'glob') {
        const pattern = part.state.input?.pattern || '';
        return pattern ? `*${escapeInlineMarkdown(pattern)}*` : '';
    }
    if (part.tool === 'grep') {
        const pattern = part.state.input?.pattern || '';
        return pattern ? `*${escapeInlineMarkdown(pattern)}*` : '';
    }
    if (part.tool === 'bash' ||
        part.tool === 'todoread' ||
        part.tool === 'todowrite') {
        return '';
    }
    // Task tool display is handled via subtask part in session-handler (shows name + agent)
    if (part.tool === 'task') {
        return '';
    }
    if (part.tool === 'skill') {
        const name = part.state.input?.name || '';
        return name ? `_${escapeInlineMarkdown(name)}_` : '';
    }
    // File upload tool - show the prompt
    if (part.tool.endsWith('kimaki_file_upload')) {
        const prompt = part.state.input?.prompt || '';
        return prompt ? `*${escapeInlineMarkdown(prompt.slice(0, 60))}*` : '';
    }
    if (!part.state.input)
        return '';
    const inputFields = Object.entries(part.state.input)
        .map(([key, value]) => {
        if (value === null || value === undefined)
            return null;
        const stringValue = typeof value === 'string' ? value : JSON.stringify(value);
        const normalized = normalizeWhitespace(stringValue);
        const truncatedValue = normalized.length > 50 ? normalized.slice(0, 50) + '…' : normalized;
        return `${key}: ${truncatedValue}`;
    })
        .filter(Boolean);
    if (inputFields.length === 0)
        return '';
    return `(${inputFields.join(', ')})`;
}
export function formatTodoList(part) {
    if (part.type !== 'tool' || part.tool !== 'todowrite')
        return '';
    const todos = part.state.input?.todos || [];
    const activeIndex = todos.findIndex((todo) => {
        return todo.status === 'in_progress';
    });
    const activeTodo = todos[activeIndex];
    if (activeIndex === -1 || !activeTodo)
        return '';
    // digit-with-period ⒈-⒛ for 1-20, fallback to regular number for 21+
    const digitWithPeriod = '⒈⒉⒊⒋⒌⒍⒎⒏⒐⒑⒒⒓⒔⒕⒖⒗⒘⒙⒚⒛';
    const todoNumber = activeIndex + 1;
    const num = todoNumber <= 20 ? digitWithPeriod[todoNumber - 1] : `${todoNumber}.`;
    const content = activeTodo.content.charAt(0).toLowerCase() + activeTodo.content.slice(1);
    return `${num} **${escapeInlineMarkdown(content)}**`;
}
export function formatPart(part, prefix) {
    const pfx = prefix ? `${prefix} ⋅ ` : '';
    if (part.type === 'text') {
        const text = part.text?.trim();
        if (!text)
            return '';
        // For subtask text, always use bullet with prefix
        if (prefix) {
            return `⬥ ${pfx}${text}`;
        }
        const firstChar = text[0] || '';
        const markdownStarters = ['#', '*', '_', '-', '>', '`', '[', '|'];
        const startsWithMarkdown = markdownStarters.includes(firstChar) || /^\d+\./.test(text);
        if (startsWithMarkdown) {
            return `\n${text}`;
        }
        return `⬥ ${text}`;
    }
    if (part.type === 'reasoning') {
        if (!part.text?.trim())
            return '';
        return `┣ ${pfx}thinking`;
    }
    if (part.type === 'file') {
        return prefix
            ? `📄 ${pfx}${part.filename || 'File'}`
            : `📄 ${part.filename || 'File'}`;
    }
    if (part.type === 'step-start' ||
        part.type === 'step-finish' ||
        part.type === 'patch') {
        return '';
    }
    if (part.type === 'agent') {
        return `┣ ${pfx}agent ${part.id}`;
    }
    if (part.type === 'snapshot') {
        return `┣ ${pfx}snapshot ${part.snapshot}`;
    }
    if (part.type === 'tool') {
        if (part.tool === 'todowrite') {
            const formatted = formatTodoList(part);
            return prefix && formatted ? `┣ ${pfx}${formatted}` : formatted;
        }
        // Question tool is handled via Discord dropdowns, not text
        if (part.tool === 'question') {
            return '';
        }
        // File upload tool is handled via Discord button + modal, not text
        if (part.tool.endsWith('kimaki_file_upload')) {
            return '';
        }
        // Action buttons tool is handled via Discord buttons, not text
        if (part.tool.endsWith('kimaki_action_buttons')) {
            return '';
        }
        // Task tool display is handled in session-handler with proper label
        if (part.tool === 'task') {
            return '';
        }
        if (part.state.status === 'pending') {
            if (part.tool !== 'bash') {
                return '';
            }
            const command = part.state.input?.command || '';
            const description = part.state.input?.description || '';
            const isSingleLine = !command.includes('\n');
            const toolTitle = isSingleLine && command.length <= MAX_BASH_COMMAND_INLINE_LENGTH
                ? ` _${escapeInlineMarkdown(command)}_`
                : description
                    ? ` _${escapeInlineMarkdown(description)}_`
                    : '';
            return `┣ ${pfx}bash${toolTitle}`;
        }
        const summaryText = getToolSummaryText(part);
        const stateTitle = 'title' in part.state ? part.state.title : undefined;
        let toolTitle = '';
        if (part.state.status === 'error') {
            toolTitle = part.state.error || 'error';
        }
        else if (part.tool === 'bash') {
            const command = part.state.input?.command || '';
            const description = part.state.input?.description || '';
            const isSingleLine = !command.includes('\n');
            if (isSingleLine && command.length <= MAX_BASH_COMMAND_INLINE_LENGTH) {
                toolTitle = `_${escapeInlineMarkdown(command)}_`;
            }
            else if (description) {
                toolTitle = `_${escapeInlineMarkdown(description)}_`;
            }
            else if (stateTitle) {
                toolTitle = `_${escapeInlineMarkdown(stateTitle)}_`;
            }
        }
        else if (stateTitle) {
            toolTitle = `_${escapeInlineMarkdown(stateTitle)}_`;
        }
        const icon = (() => {
            if (part.state.status === 'error') {
                return '⨯';
            }
            if (part.tool === 'edit' ||
                part.tool === 'write' ||
                part.tool === 'apply_patch') {
                return '◼︎';
            }
            return '┣';
        })();
        const toolParts = [part.tool, toolTitle, summaryText]
            .filter(Boolean)
            .join(' ');
        return `${icon} ${pfx}${toolParts}`;
    }
    logger.warn('Unknown part type:', part);
    return '';
}
