/**
 * ComposableMessage - Data structure for building messages from multiple parts
 *
 * Handles multi-line pastes by storing them as editable blocks that can be:
 * - Displayed as summaries
 * - Edited individually
 * - Assembled into a final message when sent
 *
 * Architecture:
 * - MessagePart: Union type for text or paste blocks
 * - ComposableMessageBuilder: Manages parts and provides editing/assembly
 * - UI helpers: Format blocks for display with summaries
 */
import { theme } from '../ui/theme.js';
/**
 * Paste size warning thresholds
 */
export const PASTE_LIMITS = {
    WARN_CHARS: 10000, // Warn when paste exceeds 10KB
    WARN_LINES: 100, // Warn when paste exceeds 100 lines
    MAX_CHARS: 500000, // Hard limit at 500KB
    MAX_LINES: 5000, // Hard limit at 5000 lines
};
/**
 * Builds messages from composable parts (text + paste blocks)
 * Allows editing blocks before final assembly
 *
 * Features:
 * - Undo/redo support for paste operations
 * - Size warnings for large pastes
 * - Inline editing of paste blocks
 */
export class ComposableMessageBuilder {
    parts = [];
    currentDraft = '';
    idCounter = 0;
    // History for undo/redo
    history = [];
    historyIndex = -1;
    maxHistorySize = 20;
    constructor() {
        this.saveHistory();
    }
    /**
     * Save current state to history
     */
    saveHistory() {
        // Remove any redo states
        if (this.historyIndex < this.history.length - 1) {
            this.history = this.history.slice(0, this.historyIndex + 1);
        }
        // Save current state
        this.history.push(this.parts.map(p => ({ ...p })));
        this.historyIndex = this.history.length - 1;
        // Limit history size
        if (this.history.length > this.maxHistorySize) {
            this.history.shift();
            this.historyIndex--;
        }
    }
    /**
     * Undo last operation
     */
    undo() {
        if (this.historyIndex <= 0) {
            return false;
        }
        this.historyIndex--;
        this.parts = this.history[this.historyIndex].map(p => ({ ...p }));
        return true;
    }
    /**
     * Redo last undone operation
     */
    redo() {
        if (this.historyIndex >= this.history.length - 1) {
            return false;
        }
        this.historyIndex++;
        this.parts = this.history[this.historyIndex].map(p => ({ ...p }));
        return true;
    }
    /**
     * Check if undo is available
     */
    canUndo() {
        return this.historyIndex > 0;
    }
    /**
     * Check if redo is available
     */
    canRedo() {
        return this.historyIndex < this.history.length - 1;
    }
    /**
     * Check paste size and return warnings/errors
     */
    static checkPasteSize(content) {
        const chars = content.length;
        const lines = content.split('\n').length;
        // Hard limits
        if (chars > PASTE_LIMITS.MAX_CHARS) {
            return {
                ok: false,
                error: `Paste too large: ${(chars / 1000).toFixed(1)}KB exceeds ${(PASTE_LIMITS.MAX_CHARS / 1000)}KB limit`,
                chars,
                lines,
            };
        }
        if (lines > PASTE_LIMITS.MAX_LINES) {
            return {
                ok: false,
                error: `Paste too long: ${lines} lines exceeds ${PASTE_LIMITS.MAX_LINES} line limit`,
                chars,
                lines,
            };
        }
        // Warnings
        const warnings = [];
        if (chars > PASTE_LIMITS.WARN_CHARS) {
            warnings.push(`Large paste: ${(chars / 1000).toFixed(1)}KB`);
        }
        if (lines > PASTE_LIMITS.WARN_LINES) {
            warnings.push(`${lines} lines`);
        }
        return {
            ok: true,
            warning: warnings.length > 0 ? warnings.join(', ') : undefined,
            chars,
            lines,
        };
    }
    /**
     * Get current state
     */
    getState() {
        return {
            parts: [...this.parts],
            currentDraft: this.currentDraft,
        };
    }
    /**
     * Check if message is empty
     */
    isEmpty() {
        return this.parts.length === 0 && !this.currentDraft.trim();
    }
    /**
     * Check if message has content
     */
    hasContent() {
        return !this.isEmpty();
    }
    /**
     * Get number of parts
     */
    getPartCount() {
        return this.parts.length;
    }
    /**
     * Set current draft text
     */
    setDraft(text) {
        this.currentDraft = text;
    }
    /**
     * Get current draft
     */
    getDraft() {
        return this.currentDraft;
    }
    /**
     * Add a text part from current draft
     */
    commitDraft() {
        const trimmed = this.currentDraft.trim();
        if (!trimmed) {
            return;
        }
        this.parts.push({
            type: 'text',
            id: this.generateId(),
            content: trimmed,
            timestamp: Date.now(),
        });
        this.currentDraft = '';
    }
    /**
     * Add a paste block
     * @returns Paste ID, or null if paste was rejected due to size limits
     */
    addPaste(content) {
        // Check paste size
        const sizeCheck = ComposableMessageBuilder.checkPasteSize(content);
        if (!sizeCheck.ok) {
            // Paste rejected - return null, caller should show error
            return null;
        }
        const lines = content.split('\n');
        const lineCount = lines.length;
        const summary = this.generateSummary(content, lineCount);
        const id = this.generateId();
        this.parts.push({
            type: 'paste',
            id,
            content,
            lineCount,
            summary,
            timestamp: Date.now(),
            edited: false,
        });
        // Save to history for undo
        this.saveHistory();
        return id;
    }
    /**
     * Add a paste block without size check (for internal use)
     */
    addPasteUnchecked(content) {
        const lines = content.split('\n');
        const lineCount = lines.length;
        const summary = this.generateSummary(content, lineCount);
        const id = this.generateId();
        this.parts.push({
            type: 'paste',
            id,
            content,
            lineCount,
            summary,
            timestamp: Date.now(),
            edited: false,
        });
        this.saveHistory();
        return id;
    }
    /**
     * Edit a paste block by ID
     */
    editPaste(id, newContent) {
        const part = this.parts.find((p) => p.id === id && p.type === 'paste');
        if (!part) {
            return false;
        }
        const lines = newContent.split('\n');
        part.content = newContent;
        part.lineCount = lines.length;
        part.summary = this.generateSummary(newContent, lines.length);
        part.edited = true;
        // Save to history for undo
        this.saveHistory();
        return true;
    }
    /**
     * Remove a part by ID
     */
    removePart(id) {
        const index = this.parts.findIndex((p) => p.id === id);
        if (index === -1) {
            return false;
        }
        this.parts.splice(index, 1);
        // Save to history for undo
        this.saveHistory();
        return true;
    }
    /**
     * Get a part by ID
     */
    getPart(id) {
        return this.parts.find((p) => p.id === id) ?? null;
    }
    /**
     * Assemble final message from all parts
     */
    assemble() {
        const parts = [];
        // Add all committed parts
        for (const part of this.parts) {
            parts.push(part.content);
        }
        // Add current draft if non-empty
        const draftTrimmed = this.currentDraft.trim();
        if (draftTrimmed) {
            parts.push(draftTrimmed);
        }
        return parts.join('\n\n').trim();
    }
    /**
     * Clear all parts and draft
     */
    clear() {
        if (this.parts.length > 0) {
            // Save to history for undo before clearing
            this.saveHistory();
        }
        this.parts = [];
        this.currentDraft = '';
    }
    /**
     * Format for display in chat
     */
    formatForDisplay() {
        if (this.parts.length === 0) {
            return this.currentDraft;
        }
        const lines = [];
        for (const part of this.parts) {
            if (part.type === 'text') {
                lines.push(part.content);
            }
            else if (part.type === 'paste') {
                const header = theme.ui.muted(`[Block ${this.parts.indexOf(part) + 1}: ${part.lineCount} lines${part.edited ? ', edited' : ''}]`);
                const preview = theme.secondary(part.summary.length > 60 ? `${part.summary.slice(0, 57)}...` : part.summary);
                lines.push(`${header}\n${preview}`);
            }
        }
        if (this.currentDraft.trim()) {
            lines.push(this.currentDraft);
        }
        return lines.join('\n\n');
    }
    /**
     * Format summary for status display
     */
    formatSummary() {
        const partCount = this.parts.length;
        const hasDraft = Boolean(this.currentDraft.trim());
        if (partCount === 0 && hasDraft) {
            return 'Current input';
        }
        if (partCount === 1 && !hasDraft) {
            const part = this.parts[0];
            return part.type === 'paste' ? `1 pasted block (${part.lineCount} lines)` : 'Text input';
        }
        const parts = [];
        if (partCount > 0) {
            const pasteCount = this.parts.filter((p) => p.type === 'paste').length;
            if (pasteCount > 0) {
                parts.push(`${pasteCount} block${pasteCount > 1 ? 's' : ''}`);
            }
        }
        if (hasDraft) {
            parts.push('text');
        }
        return parts.join(' + ');
    }
    /**
     * Format paste blocks as inline chips for prompt display.
     * Returns a string like: "[📝 Code: 15L/2.3kc] [📋 Text: 50L/5kc]"
     * Returns empty string if no paste blocks exist.
     */
    formatPasteChips() {
        const pasteBlocks = this.parts.filter((p) => p.type === 'paste');
        if (pasteBlocks.length === 0) {
            return '';
        }
        return pasteBlocks
            .map((block) => this.formatPasteChip(block))
            .join(' ');
    }
    /**
     * Format a single paste chip with type detection
     */
    formatPasteChip(block) {
        const content = block.content;
        const lines = content.split('\n');
        const firstLine = lines[0]?.trim() || '';
        const charCount = content.length;
        // Detect content type
        let typeIcon = '📋';
        let typeLabel = 'Pasted';
        if (firstLine.match(/^(import|export|const|let|var|function|class|def |async |from |interface |type )/)) {
            typeIcon = '📝';
            typeLabel = 'Code';
        }
        else if (firstLine.match(/^[{[\]]/)) {
            typeIcon = '📊';
            typeLabel = 'JSON';
        }
        else if (firstLine.match(/^<[!?]?[a-zA-Z]/)) {
            typeIcon = '📄';
            typeLabel = 'XML';
        }
        // Format size compactly
        const sizeStr = charCount > 1000
            ? `${(charCount / 1000).toFixed(1)}k`
            : `${charCount}`;
        return `[${typeIcon} ${typeLabel}: ${block.lineCount}L/${sizeStr}c]`;
    }
    /**
     * Get count of paste blocks only
     */
    getPasteBlockCount() {
        return this.parts.filter((p) => p.type === 'paste').length;
    }
    /**
     * Generate a summary for a paste block
     */
    generateSummary(content, lineCount) {
        const lines = content.split('\n').map((l) => l.trim()).filter(Boolean);
        if (lines.length === 0) {
            return `[${lineCount} empty lines]`;
        }
        // Use first non-empty line as summary
        const firstLine = lines[0] || '';
        const maxLength = 80;
        if (firstLine.length <= maxLength) {
            return firstLine;
        }
        return `${firstLine.slice(0, maxLength - 3)}...`;
    }
    /**
     * Generate unique ID for parts
     */
    generateId() {
        this.idCounter += 1;
        return `part-${this.idCounter}-${Date.now()}`;
    }
}
//# sourceMappingURL=composableMessage.js.map