/**
 * File attachment utilities and state manager for the interactive mode.
 * Handles serialization/deserialization of attached file paths in messages,
 * display formatting, input helpers, and stdin file-drop interception.
 */
import * as fs from 'node:fs';
import * as path from 'node:path';
import { Text } from '../../tui/index.js';
import { theme } from '../theme/runtime.js';
/** Header sent to the LLM when files are attached. Used for both serialization and parsing. */
const ATTACHMENT_HEADER = 'the user attached the following files:';
/** Strip surrounding single or double quotes from a string. */
export function stripQuotes(s) {
    if ((s.startsWith("'") && s.endsWith("'")) || (s.startsWith('"') && s.endsWith('"'))) {
        return s.slice(1, -1);
    }
    return s;
}
/** Check if a path points to a non-empty file. */
function isAttachableFile(filePath) {
    try {
        const stats = fs.statSync(filePath);
        return stats.isFile() && stats.size > 0;
    }
    catch {
        return false;
    }
}
/** Append attached file paths to the user's message text for the LLM. */
export function appendAttachedFiles(text, files) {
    if (files.length === 0)
        return text;
    const attachment = `${ATTACHMENT_HEADER}\n${files.join('\n')}`;
    return text ? `${text}\n${attachment}` : attachment;
}
/** Parse attached file paths from a message that was serialized with appendAttachedFiles. */
export function parseAttachedFiles(textContent) {
    const headerWithNewline = `${ATTACHMENT_HEADER}\n`;
    const headerIdx = textContent.indexOf(headerWithNewline);
    if (headerIdx === -1) {
        return { displayText: textContent, attachedFiles: [] };
    }
    const attachedFiles = [];
    const afterHeader = textContent.substring(headerIdx + headerWithNewline.length);
    for (const line of afterHeader.split('\n')) {
        const trimmed = line.trim();
        if (trimmed.length > 0) {
            attachedFiles.push(trimmed);
        }
        else {
            break;
        }
    }
    const displayText = textContent.substring(0, headerIdx).trim();
    return { displayText, attachedFiles };
}
/** Format file paths for chat history display with ⎿ glyph and underlined basenames. */
export function formatAttachmentLabels(filePaths) {
    return filePaths.map((fp) => `  ⎿  ${theme.underline(path.basename(fp))}`).join('\n');
}
/**
 * Manages file attachment state, display, and stdin interception.
 * InteractiveMode creates one instance and wires its `displayComponent` into the editor container.
 */
export class AttachmentManager {
    files = [];
    /** Text component showing attachment previews above the editor. */
    displayComponent;
    onChanged;
    constructor(onChanged) {
        this.displayComponent = new Text('', 1, 0);
        this.onChanged = onChanged;
    }
    /** Validate and add a file. Returns true if added. */
    add(filePath) {
        if (this.files.includes(filePath))
            return true;
        if (!isAttachableFile(filePath))
            return false;
        this.files.push(filePath);
        this.updateDisplay();
        this.onChanged();
        return true;
    }
    /** Take all files and clear. Returns the file paths that were attached. */
    take() {
        const taken = this.files;
        this.files = [];
        this.updateDisplay();
        return taken;
    }
    /** Whether any files are currently attached. */
    get hasFiles() {
        return this.files.length > 0;
    }
    /**
     * Intercept file drops on macOS.
     * macOS terminals send dropped files as raw quoted paths on stdin without
     * bracketed paste markers. We monkey-patch the terminal's stdin handler
     * to detect and redirect them to the attachment system.
     */
    interceptStdinFileDrops(terminal) {
        const originalHandler = terminal.stdinDataHandler;
        if (!originalHandler)
            return;
        terminal.stdinDataHandler = (data) => {
            const trimmed = data.trim();
            if (trimmed.length >= 2) {
                const candidate = stripQuotes(trimmed);
                if (candidate.startsWith('/') && !/[\x00-\x1f]/.test(candidate)) {
                    if (this.add(candidate)) {
                        return; // Swallow — don't pass to terminal
                    }
                }
            }
            originalHandler(data);
        };
        process.stdin.removeListener('data', originalHandler);
        process.stdin.on('data', terminal.stdinDataHandler);
    }
    updateDisplay() {
        if (this.files.length === 0) {
            this.displayComponent.setText('');
            return;
        }
        const lines = this.files.map((fp) => theme.underline(path.basename(fp))).join('\n');
        this.displayComponent.setText(lines);
    }
}
//# sourceMappingURL=file-attachments.js.map