/**
 * Email Client
 *
 * Unified email client for IMAP/SMTP operations.
 * Note: This is a mock implementation as actual email requires external libraries.
 * In production, integrate with nodemailer (SMTP) and imap (IMAP) packages.
 */
import { EventEmitter } from 'events';
import * as crypto from 'crypto';
import { DEFAULT_IMAP_CONFIG, DEFAULT_SMTP_CONFIG, } from './types.js';
// ============================================================================
// Utility Functions
// ============================================================================
export function parseEmailAddress(input) {
    if (typeof input === 'object') {
        return input;
    }
    // Parse "Name <email@example.com>" format
    const match = input.match(/^(.+?)\s*<(.+)>$/);
    if (match) {
        return { name: match[1].trim(), address: match[2].trim() };
    }
    return { address: input.trim() };
}
export function formatEmailAddress(addr) {
    if (addr.name) {
        return `${addr.name} <${addr.address}>`;
    }
    return addr.address;
}
export function generateMessageId(domain = 'codebuddy.local') {
    const random = crypto.randomBytes(16).toString('hex');
    return `<${random}@${domain}>`;
}
export class ImapClient extends EventEmitter {
    config;
    connected = false;
    selectedFolder = null;
    idleTimeout = null;
    // Mock data for testing
    mockFolders = new Map();
    mockMessages = new Map();
    nextUid = 1;
    constructor(config) {
        super();
        this.config = { ...DEFAULT_IMAP_CONFIG, ...config };
        this.initializeMockData();
    }
    initializeMockData() {
        // Create default folders
        const folders = [
            { name: 'INBOX', path: 'INBOX', delimiter: '/', specialUse: 'inbox', totalMessages: 0, unseenMessages: 0 },
            { name: 'Sent', path: 'Sent', delimiter: '/', specialUse: 'sent', totalMessages: 0, unseenMessages: 0 },
            { name: 'Drafts', path: 'Drafts', delimiter: '/', specialUse: 'drafts', totalMessages: 0, unseenMessages: 0 },
            { name: 'Trash', path: 'Trash', delimiter: '/', specialUse: 'trash', totalMessages: 0, unseenMessages: 0 },
            { name: 'Spam', path: 'Spam', delimiter: '/', specialUse: 'spam', totalMessages: 0, unseenMessages: 0 },
        ];
        for (const folder of folders) {
            this.mockFolders.set(folder.path, folder);
            this.mockMessages.set(folder.path, new Map());
        }
    }
    /**
     * Connect to IMAP server
     */
    async connect() {
        // Simulate connection delay
        await new Promise(resolve => setTimeout(resolve, 100));
        if (!this.config.host) {
            throw new Error('IMAP host is required');
        }
        this.connected = true;
        this.emit('connected');
    }
    /**
     * Disconnect from server
     */
    async disconnect() {
        if (this.idleTimeout) {
            clearTimeout(this.idleTimeout);
            this.idleTimeout = null;
        }
        this.connected = false;
        this.selectedFolder = null;
        this.emit('disconnected');
    }
    /**
     * Check if connected
     */
    isConnected() {
        return this.connected;
    }
    /**
     * List all folders
     */
    async listFolders() {
        this.ensureConnected();
        return Array.from(this.mockFolders.values());
    }
    /**
     * Select a folder
     */
    async selectFolder(path) {
        this.ensureConnected();
        const folder = this.mockFolders.get(path);
        if (!folder) {
            throw new Error(`Folder not found: ${path}`);
        }
        this.selectedFolder = path;
        return folder;
    }
    /**
     * Get currently selected folder
     */
    getSelectedFolder() {
        return this.selectedFolder;
    }
    /**
     * Create a folder
     */
    async createFolder(path) {
        this.ensureConnected();
        if (this.mockFolders.has(path)) {
            throw new Error(`Folder already exists: ${path}`);
        }
        const folder = {
            name: path.split('/').pop() || path,
            path,
            delimiter: '/',
            totalMessages: 0,
            unseenMessages: 0,
        };
        this.mockFolders.set(path, folder);
        this.mockMessages.set(path, new Map());
        return folder;
    }
    /**
     * Delete a folder
     */
    async deleteFolder(path) {
        this.ensureConnected();
        if (!this.mockFolders.has(path)) {
            throw new Error(`Folder not found: ${path}`);
        }
        this.mockFolders.delete(path);
        this.mockMessages.delete(path);
        if (this.selectedFolder === path) {
            this.selectedFolder = null;
        }
    }
    /**
     * Search for messages
     */
    async search(criteria, _options) {
        this.ensureConnected();
        this.ensureFolderSelected();
        const messages = this.mockMessages.get(this.selectedFolder);
        if (!messages)
            return [];
        const results = [];
        for (const [uid, message] of messages) {
            if (this.matchesCriteria(message, criteria)) {
                results.push(uid);
            }
        }
        return results;
    }
    /**
     * Fetch messages by UID
     */
    async fetch(uids, _options) {
        this.ensureConnected();
        this.ensureFolderSelected();
        const messages = this.mockMessages.get(this.selectedFolder);
        if (!messages)
            return [];
        const uidList = Array.isArray(uids) ? uids : [uids];
        const results = [];
        for (const uid of uidList) {
            const message = messages.get(uid);
            if (message) {
                results.push(message);
            }
        }
        return results;
    }
    /**
     * Fetch a single message
     */
    async fetchOne(uid, options) {
        const messages = await this.fetch(uid, options);
        return messages[0] || null;
    }
    /**
     * Add flags to messages
     */
    async addFlags(uids, flags) {
        this.ensureConnected();
        this.ensureFolderSelected();
        const messages = this.mockMessages.get(this.selectedFolder);
        if (!messages)
            return;
        const uidList = Array.isArray(uids) ? uids : [uids];
        const flagList = Array.isArray(flags) ? flags : [flags];
        for (const uid of uidList) {
            const message = messages.get(uid);
            if (message) {
                message.flags = message.flags || [];
                for (const flag of flagList) {
                    if (!message.flags.includes(flag)) {
                        message.flags.push(flag);
                    }
                }
                this.emit('flags', uid, message.flags);
            }
        }
    }
    /**
     * Remove flags from messages
     */
    async removeFlags(uids, flags) {
        this.ensureConnected();
        this.ensureFolderSelected();
        const messages = this.mockMessages.get(this.selectedFolder);
        if (!messages)
            return;
        const uidList = Array.isArray(uids) ? uids : [uids];
        const flagList = Array.isArray(flags) ? flags : [flags];
        for (const uid of uidList) {
            const message = messages.get(uid);
            if (message && message.flags) {
                message.flags = message.flags.filter(f => !flagList.includes(f));
                this.emit('flags', uid, message.flags);
            }
        }
    }
    /**
     * Move messages to another folder
     */
    async move(uids, destFolder) {
        this.ensureConnected();
        this.ensureFolderSelected();
        const srcMessages = this.mockMessages.get(this.selectedFolder);
        const destMessages = this.mockMessages.get(destFolder);
        if (!srcMessages || !destMessages) {
            throw new Error('Source or destination folder not found');
        }
        const uidList = Array.isArray(uids) ? uids : [uids];
        for (const uid of uidList) {
            const message = srcMessages.get(uid);
            if (message) {
                srcMessages.delete(uid);
                destMessages.set(uid, message);
                this.emit('expunge', uid);
            }
        }
        this.updateFolderCounts();
    }
    /**
     * Copy messages to another folder
     */
    async copy(uids, destFolder) {
        this.ensureConnected();
        this.ensureFolderSelected();
        const srcMessages = this.mockMessages.get(this.selectedFolder);
        const destMessages = this.mockMessages.get(destFolder);
        if (!srcMessages || !destMessages) {
            throw new Error('Source or destination folder not found');
        }
        const uidList = Array.isArray(uids) ? uids : [uids];
        for (const uid of uidList) {
            const message = srcMessages.get(uid);
            if (message) {
                const newUid = this.nextUid++;
                const copy = { ...message, uid: newUid, id: `msg-${newUid}` };
                destMessages.set(newUid, copy);
            }
        }
        this.updateFolderCounts();
    }
    /**
     * Delete messages (move to trash or permanently delete)
     */
    async delete(uids, permanent = false) {
        if (permanent) {
            await this.addFlags(uids, 'deleted');
            await this.expunge();
        }
        else {
            await this.move(uids, 'Trash');
        }
    }
    /**
     * Expunge deleted messages
     */
    async expunge() {
        this.ensureConnected();
        this.ensureFolderSelected();
        const messages = this.mockMessages.get(this.selectedFolder);
        if (!messages)
            return [];
        const expunged = [];
        for (const [uid, message] of messages) {
            if (message.flags?.includes('deleted')) {
                messages.delete(uid);
                expunged.push(uid);
                this.emit('expunge', uid);
            }
        }
        this.updateFolderCounts();
        return expunged;
    }
    /**
     * Start IDLE mode
     */
    async idle(timeout = 30000) {
        this.ensureConnected();
        this.ensureFolderSelected();
        return new Promise((resolve) => {
            this.idleTimeout = setTimeout(() => {
                this.idleTimeout = null;
                resolve();
            }, timeout);
        });
    }
    /**
     * Stop IDLE mode
     */
    stopIdle() {
        if (this.idleTimeout) {
            clearTimeout(this.idleTimeout);
            this.idleTimeout = null;
        }
    }
    /**
     * Append a message to a folder
     */
    async append(folder, message, flags) {
        this.ensureConnected();
        const messages = this.mockMessages.get(folder);
        if (!messages) {
            throw new Error(`Folder not found: ${folder}`);
        }
        const uid = this.nextUid++;
        const fullMessage = {
            id: `msg-${uid}`,
            uid,
            from: message.from || [],
            to: message.to || [],
            subject: message.subject || '(no subject)',
            date: message.date || new Date(),
            text: message.text,
            html: message.html,
            flags: flags || [],
            ...message,
        };
        messages.set(uid, fullMessage);
        this.updateFolderCounts();
        this.emit('mail', 1);
        return uid;
    }
    /**
     * Add a mock message (for testing)
     */
    addMockMessage(folder, message) {
        const messages = this.mockMessages.get(folder);
        if (!messages) {
            throw new Error(`Folder not found: ${folder}`);
        }
        const uid = this.nextUid++;
        const fullMessage = {
            id: `msg-${uid}`,
            uid,
            from: message.from || [{ address: 'sender@example.com' }],
            to: message.to || [{ address: this.config.user }],
            subject: message.subject || 'Test message',
            date: message.date || new Date(),
            text: message.text || 'Test content',
            flags: message.flags || [],
            ...message,
        };
        messages.set(uid, fullMessage);
        this.updateFolderCounts();
        return uid;
    }
    // ============================================================================
    // Private Methods
    // ============================================================================
    ensureConnected() {
        if (!this.connected) {
            throw new Error('Not connected to IMAP server');
        }
    }
    ensureFolderSelected() {
        if (!this.selectedFolder) {
            throw new Error('No folder selected');
        }
    }
    matchesCriteria(message, criteria) {
        if (criteria.all)
            return true;
        if (criteria.seen !== undefined) {
            const hasSeen = message.flags?.includes('seen') ?? false;
            if (criteria.seen !== hasSeen)
                return false;
        }
        if (criteria.unseen !== undefined) {
            const hasSeen = message.flags?.includes('seen') ?? false;
            if (criteria.unseen !== !hasSeen)
                return false;
        }
        if (criteria.flagged !== undefined) {
            const hasFlagged = message.flags?.includes('flagged') ?? false;
            if (criteria.flagged !== hasFlagged)
                return false;
        }
        if (criteria.from) {
            const hasFrom = message.from.some(a => a.address.toLowerCase().includes(criteria.from.toLowerCase()) ||
                a.name?.toLowerCase().includes(criteria.from.toLowerCase()));
            if (!hasFrom)
                return false;
        }
        if (criteria.to) {
            const hasTo = message.to.some(a => a.address.toLowerCase().includes(criteria.to.toLowerCase()) ||
                a.name?.toLowerCase().includes(criteria.to.toLowerCase()));
            if (!hasTo)
                return false;
        }
        if (criteria.subject) {
            if (!message.subject.toLowerCase().includes(criteria.subject.toLowerCase())) {
                return false;
            }
        }
        if (criteria.body || criteria.text) {
            const searchText = (criteria.body || criteria.text).toLowerCase();
            const hasText = message.text?.toLowerCase().includes(searchText) ||
                message.html?.toLowerCase().includes(searchText);
            if (!hasText)
                return false;
        }
        if (criteria.before) {
            if (message.date >= criteria.before)
                return false;
        }
        if (criteria.since) {
            if (message.date < criteria.since)
                return false;
        }
        if (criteria.uid !== undefined) {
            const uids = Array.isArray(criteria.uid) ? criteria.uid : [criteria.uid];
            if (!uids.includes(message.uid))
                return false;
        }
        return true;
    }
    updateFolderCounts() {
        for (const [path, folder] of this.mockFolders) {
            const messages = this.mockMessages.get(path);
            if (messages) {
                folder.totalMessages = messages.size;
                folder.unseenMessages = Array.from(messages.values())
                    .filter(m => !m.flags?.includes('seen')).length;
            }
        }
    }
}
export class SmtpClient extends EventEmitter {
    config;
    connected = false;
    sentMessages = [];
    constructor(config) {
        super();
        this.config = { ...DEFAULT_SMTP_CONFIG, ...config };
    }
    /**
     * Connect to SMTP server
     */
    async connect() {
        await new Promise(resolve => setTimeout(resolve, 50));
        if (!this.config.host) {
            throw new Error('SMTP host is required');
        }
        this.connected = true;
        this.emit('connected');
    }
    /**
     * Disconnect from server
     */
    async disconnect() {
        this.connected = false;
        this.emit('disconnected');
    }
    /**
     * Check if connected
     */
    isConnected() {
        return this.connected;
    }
    /**
     * Send an email
     */
    async send(options) {
        this.ensureConnected();
        // Validate required fields
        if (!options.from) {
            throw new Error('From address is required');
        }
        if (!options.to || (Array.isArray(options.to) && options.to.length === 0)) {
            throw new Error('To address is required');
        }
        if (!options.subject) {
            throw new Error('Subject is required');
        }
        // Simulate send delay
        await new Promise(resolve => setTimeout(resolve, 100));
        const toAddresses = Array.isArray(options.to)
            ? options.to.map(a => typeof a === 'string' ? a : a.address)
            : [typeof options.to === 'string' ? options.to : options.to.address];
        const result = {
            messageId: generateMessageId(),
            accepted: toAddresses,
            rejected: [],
            pending: [],
            response: '250 OK',
        };
        this.sentMessages.push(result);
        this.emit('sent', result);
        return result;
    }
    /**
     * Verify connection
     */
    async verify() {
        this.ensureConnected();
        return true;
    }
    /**
     * Get sent messages (for testing)
     */
    getSentMessages() {
        return [...this.sentMessages];
    }
    /**
     * Clear sent messages (for testing)
     */
    clearSentMessages() {
        this.sentMessages = [];
    }
    ensureConnected() {
        if (!this.connected) {
            throw new Error('Not connected to SMTP server');
        }
    }
}
//# sourceMappingURL=client.js.map