import { now } from './_util';
import { join } from 'node:path';
import { homedir } from 'node:os';
import { randomUUID } from 'node:crypto';
import { mkdirSync, existsSync, unlinkSync } from 'node:fs';
import { openSync, writeSync, closeSync, readFileSync } from 'node:fs';
export class LocalStreamStorage {
    #db;
    #projectPath;
    #serverUrl;
    #tempDir;
    constructor(db, projectPath, serverUrl) {
        this.#db = db;
        this.#projectPath = projectPath;
        this.#serverUrl = serverUrl;
        // Create temp directory for stream buffering
        this.#tempDir = join(homedir(), '.config', 'agentuity', 'streams');
        if (!existsSync(this.#tempDir)) {
            mkdirSync(this.#tempDir, { recursive: true });
        }
    }
    async create(namespace, props) {
        if (!namespace || namespace.length < 1 || namespace.length > 254) {
            throw new Error('Stream namespace must be between 1 and 254 characters');
        }
        const id = randomUUID();
        const timestamp = now();
        const metadata = props?.metadata ? JSON.stringify(props.metadata) : null;
        // Insert stream record with NULL data
        const stmt = this.#db.prepare(`
			INSERT INTO stream_storage (
				project_path, id, name, metadata, content_type, created_at
			)
			VALUES (?, ?, ?, ?, ?, ?)
		`);
        stmt.run(this.#projectPath, id, namespace, metadata, props?.contentType || 'application/octet-stream', timestamp);
        const url = `${this.#serverUrl}/_agentuity/local/stream/${id}`;
        return new LocalStream(id, url, this.#db, this.#projectPath, this.#tempDir, props?.compress ?? false);
    }
    async list(params) {
        if (params?.limit && (params.limit <= 0 || params.limit > 1000)) {
            throw new Error('limit must be between 1 and 1000');
        }
        let query = `
			SELECT id, name, metadata, size_bytes 
			FROM stream_storage 
			WHERE project_path = ?
		`;
        const queryParams = [this.#projectPath];
        // Add filters (map namespace to name for the database)
        if (params?.namespace) {
            query += ` AND name = ?`;
            queryParams.push(params.namespace);
        }
        if (params?.metadata) {
            // Simple JSON matching - check if metadata contains all key-value pairs
            for (const [key, value] of Object.entries(params.metadata)) {
                query += ` AND metadata LIKE ?`;
                queryParams.push(`%"${key}":"${value}"%`);
            }
        }
        // Get total count
        const countQuery = this.#db.query(query.replace('SELECT id, name, metadata, size_bytes', 'SELECT COUNT(*) as count'));
        const { count } = countQuery.get(...queryParams);
        // Add pagination
        query += ` ORDER BY created_at DESC`;
        if (params?.limit) {
            query += ` LIMIT ${params.limit}`;
        }
        if (params?.offset) {
            query += ` OFFSET ${params.offset}`;
        }
        const stmt = this.#db.query(query);
        const rows = stmt.all(...queryParams);
        // Map name to namespace for the SDK interface
        const streams = rows.map((row) => ({
            id: row.id,
            namespace: row.name,
            metadata: row.metadata ? JSON.parse(row.metadata) : {},
            url: `${this.#serverUrl}/_agentuity/local/stream/${row.id}`,
            sizeBytes: row.size_bytes,
            expiresAt: null,
        }));
        return {
            success: true,
            streams,
            total: count,
        };
    }
    async get(id) {
        if (!id?.trim()) {
            throw new Error('Stream id is required');
        }
        const stmt = this.#db.query(`
			SELECT id, name, metadata, size_bytes
			FROM stream_storage
			WHERE project_path = ? AND id = ?
		`);
        const row = stmt.get(this.#projectPath, id);
        if (!row) {
            throw new Error(`Stream not found: ${id}`);
        }
        const metadata = row.metadata ? JSON.parse(row.metadata) : {};
        const url = `${this.#serverUrl}/_agentuity/local/stream/${id}`;
        // Map name to namespace for the SDK interface
        return {
            id: row.id,
            namespace: row.name,
            metadata,
            url,
            sizeBytes: row.size_bytes,
            expiresAt: null,
        };
    }
    async download(id) {
        if (!id?.trim()) {
            throw new Error('Stream id is required');
        }
        const stmt = this.#db.query(`
			SELECT data FROM stream_storage
			WHERE project_path = ? AND id = ?
		`);
        const row = stmt.get(this.#projectPath, id);
        if (!row || !row.data) {
            throw new Error(`Stream not found or empty: ${id}`);
        }
        // Convert Buffer to ReadableStream
        const buffer = row.data;
        return new ReadableStream({
            start(controller) {
                controller.enqueue(new Uint8Array(buffer));
                controller.close();
            },
        });
    }
    async delete(id) {
        if (!id?.trim()) {
            throw new Error('Stream id is required');
        }
        const stmt = this.#db.prepare(`
			DELETE FROM stream_storage 
			WHERE project_path = ? AND id = ?
		`);
        stmt.run(this.#projectPath, id);
    }
}
class LocalStream extends WritableStream {
    id;
    url;
    #db;
    #projectPath;
    #compressed;
    #tempFilePath;
    #fileHandle = null;
    #bytesWritten = 0;
    #closed = false;
    constructor(id, url, db, projectPath, tempDir, compressed) {
        super({
            write: async (chunk) => {
                await this.#writeToFile(chunk);
            },
            close: async () => {
                await this.#persist();
            },
        });
        this.id = id;
        this.url = url;
        this.#db = db;
        this.#projectPath = projectPath;
        this.#compressed = compressed;
        this.#tempFilePath = join(tempDir, `${id}.tmp`);
        // Open file for writing
        this.#fileHandle = openSync(this.#tempFilePath, 'w');
    }
    get bytesWritten() {
        return this.#bytesWritten;
    }
    get compressed() {
        return this.#compressed;
    }
    async write(chunk) {
        if (this.#closed) {
            throw new Error('Stream is closed');
        }
        let binary;
        if (chunk instanceof Uint8Array) {
            binary = chunk;
        }
        else if (typeof chunk === 'string') {
            binary = new TextEncoder().encode(chunk);
        }
        else if (chunk instanceof ArrayBuffer) {
            binary = new Uint8Array(chunk);
        }
        else if (typeof chunk === 'object') {
            binary = new TextEncoder().encode(JSON.stringify(chunk));
        }
        else {
            binary = new TextEncoder().encode(String(chunk));
        }
        await this.#writeToFile(binary);
    }
    async close() {
        if (this.#closed) {
            return;
        }
        this.#closed = true;
        // Close file handle if open
        if (this.#fileHandle !== null) {
            closeSync(this.#fileHandle);
            this.#fileHandle = null;
        }
        await this.#persist();
    }
    getReader() {
        const db = this.#db;
        const projectPath = this.#projectPath;
        const id = this.id;
        return new ReadableStream({
            start(controller) {
                const query = db.query(`
					SELECT data FROM stream_storage 
					WHERE project_path = ? AND id = ?
				`);
                const row = query.get(projectPath, id);
                if (!row || !row.data) {
                    controller.error(new Error('Stream not found or not finalized'));
                    return;
                }
                controller.enqueue(new Uint8Array(row.data));
                controller.close();
            },
        });
    }
    async #writeToFile(chunk) {
        if (this.#fileHandle === null) {
            throw new Error('File handle is closed');
        }
        const written = writeSync(this.#fileHandle, chunk);
        this.#bytesWritten += written;
    }
    async #persist() {
        // Read buffered file
        let data = readFileSync(this.#tempFilePath);
        // Optional: Apply compression if enabled
        if (this.#compressed) {
            const { gzipSync } = await import('node:zlib');
            data = gzipSync(data);
        }
        // Update DB with finalized data
        const stmt = this.#db.prepare(`
			UPDATE stream_storage 
			SET data = ?, size_bytes = ? 
			WHERE project_path = ? AND id = ?
		`);
        stmt.run(data, this.#bytesWritten, this.#projectPath, this.id);
        // Clean up temp file
        try {
            unlinkSync(this.#tempFilePath);
        }
        catch {
            // Ignore cleanup errors
        }
    }
}
//# sourceMappingURL=stream.js.map