const ALL_MEMORY_TYPES = ['task_list', 'recently_memory', 'memory'];
const LISTABLE_MEMORY_TYPES = ['task_list', 'recently_memory'];
const LISTABLE_MEMORY_TOTAL_LIMIT = 64;
const LISTABLE_MEMORY_TYPE_LIMIT = 32;
const LISTABLE_MEMORY_TOTAL_HARD_LIMIT = LISTABLE_MEMORY_TOTAL_LIMIT * 2;
const LISTABLE_MEMORY_TYPE_HARD_LIMIT = LISTABLE_MEMORY_TYPE_LIMIT * 2;
export class MemoryCapacityError extends Error {
    details;
    constructor(message, details) {
        super(message);
        this.name = 'MemoryCapacityError';
        this.details = details;
    }
}
const URI_PATTERN = /^([a-zA-Z_][a-zA-Z0-9_]*):\/\/(.*)$/;
export class MemoryStore {
    filePath;
    constructor(filePath) {
        this.filePath = filePath;
    }
    nowIso() {
        return new Date().toISOString();
    }
    normalizeUri(input) {
        const value = String(input || '').trim();
        const matched = URI_PATTERN.exec(value);
        if (!matched) {
            throw new Error(`Invalid memory uri: ${input}`);
        }
        const domainPart = matched[1];
        const pathPart = matched[2];
        if (!domainPart || !pathPart) {
            throw new Error(`Invalid memory uri: ${input}`);
        }
        const domain = domainPart.toLowerCase();
        const path = pathPart.replace(/^\/+|\/+$/g, '');
        if (!path) {
            throw new Error(`Invalid memory uri path: ${input}`);
        }
        return {
            uri: `${domain}://${path}`,
            domain,
            path
        };
    }
    normalizeMemoryType(input, allowedTypes = ALL_MEMORY_TYPES) {
        const value = String(input || '').trim();
        if (!allowedTypes.includes(value)) {
            throw new Error(`Invalid memory_type: ${input}`);
        }
        return value;
    }
    ensureTypeMatch(record, expectedType) {
        if (expectedType && record.memory_type !== expectedType) {
            throw new Error(`Memory type mismatch: expected ${expectedType}, actual ${record.memory_type}`);
        }
    }
    async ensureParentDir() {
        const { mkdir } = await import('node:fs/promises');
        const path = await import('node:path');
        const parent = path.dirname(this.filePath);
        await mkdir(parent, { recursive: true });
    }
    async loadData() {
        const { readFile } = await import('node:fs/promises');
        try {
            const raw = await readFile(this.filePath, 'utf-8');
            const parsed = JSON.parse(raw);
            const original = parsed.by_uri && typeof parsed.by_uri === 'object'
                ? parsed.by_uri
                : parsed.byUri && typeof parsed.byUri === 'object'
                    ? parsed.byUri
                    : {};
            const normalizedByUri = {};
            for (const [uri, record] of Object.entries(original)) {
                const legacyType = record.memoryType;
                const memory_type = this.normalizeMemoryType(record.memory_type || legacyType || 'memory');
                normalizedByUri[uri] = {
                    ...record,
                    memory_type
                };
            }
            return { by_uri: normalizedByUri };
        }
        catch {
            return { by_uri: {} };
        }
    }
    async saveData(data) {
        const { rename, rm, writeFile } = await import('node:fs/promises');
        await this.ensureParentDir();
        const temporaryPath = `${this.filePath}.${process.pid}.${Date.now()}.tmp`;
        try {
            await writeFile(temporaryPath, JSON.stringify(data, null, 2), 'utf-8');
            await rename(temporaryPath, this.filePath);
        }
        catch (error) {
            await rm(temporaryPath, { force: true });
            throw error;
        }
    }
    createEmptyStats() {
        return {
            total: 0,
            listable: {
                count: 0,
                limit: LISTABLE_MEMORY_TOTAL_LIMIT,
                hardLimit: LISTABLE_MEMORY_TOTAL_HARD_LIMIT,
                exceeded: false,
                hardExceeded: false
            },
            by_type: {
                task_list: {
                    count: 0,
                    limit: LISTABLE_MEMORY_TYPE_LIMIT,
                    hardLimit: LISTABLE_MEMORY_TYPE_HARD_LIMIT,
                    exceeded: false,
                    hardExceeded: false,
                    listable: true
                },
                recently_memory: {
                    count: 0,
                    limit: LISTABLE_MEMORY_TYPE_LIMIT,
                    hardLimit: LISTABLE_MEMORY_TYPE_HARD_LIMIT,
                    exceeded: false,
                    hardExceeded: false,
                    listable: true
                },
                memory: {
                    count: 0,
                    limit: null,
                    hardLimit: null,
                    exceeded: false,
                    hardExceeded: false,
                    listable: false
                }
            }
        };
    }
    /**
     * 统计本地记忆容量。
     * 主流程：遍历所有记录 -> 按类型计数 -> 单独汇总可 list 类型容量。
     */
    calculateStats(data) {
        const stats = this.createEmptyStats();
        for (const record of Object.values(data.by_uri)) {
            stats.total += 1;
            stats.by_type[record.memory_type].count += 1;
            if (LISTABLE_MEMORY_TYPES.includes(record.memory_type)) {
                stats.listable.count += 1;
            }
        }
        stats.listable.exceeded = stats.listable.count > stats.listable.limit;
        stats.listable.hardExceeded = stats.listable.count >= stats.listable.hardLimit;
        for (const memoryType of LISTABLE_MEMORY_TYPES) {
            const typeStats = stats.by_type[memoryType];
            if (typeStats.limit !== null && typeStats.hardLimit !== null) {
                typeStats.exceeded = typeStats.count > typeStats.limit;
                typeStats.hardExceeded = typeStats.count >= typeStats.hardLimit;
            }
        }
        return stats;
    }
    generateId(uri) {
        const cleaned = uri.replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_+|_+$/g, '');
        return `${cleaned}_${Date.now().toString(36)}`;
    }
    /**
     * 校验可列出记忆容量。
     * 主流程：仅限制 task_list/recently_memory 新建 -> 先校验该类型数量 -> 再校验可 list 总量。
     */
    ensureListableMemoryCapacity(data, memory_type) {
        if (!LISTABLE_MEMORY_TYPES.includes(memory_type)) {
            return;
        }
        const stats = this.calculateStats(data);
        const sameTypeCount = stats.by_type[memory_type].count;
        if (sameTypeCount >= LISTABLE_MEMORY_TYPE_HARD_LIMIT) {
            throw new MemoryCapacityError(`Cannot create ${memory_type}: hard type limit ${LISTABLE_MEMORY_TYPE_HARD_LIMIT} reached. ` +
                `Please delete an existing ${memory_type}` +
                (memory_type === 'recently_memory'
                    ? ' or archive one to long-term memory with archive_recently_memory'
                    : '') +
                ` before creating a new ${memory_type}.`, {
                code: 'MEMORY_TYPE_LIMIT_REACHED',
                memory_type,
                limit: LISTABLE_MEMORY_TYPE_HARD_LIMIT,
                current: sameTypeCount,
                recommended_actions: memory_type === 'recently_memory'
                    ? [
                        "list_memories({ memory_type: 'recently_memory' })",
                        'archive_recently_memory({ uri })',
                        "delete_memory({ uri, memory_type: 'recently_memory' })"
                    ]
                    : [
                        "list_memories({ memory_type: 'task_list' })",
                        'remove_task_list_memory({ uri })',
                        "delete_memory({ uri, memory_type: 'task_list' })"
                    ]
            });
        }
        if (stats.listable.count >= LISTABLE_MEMORY_TOTAL_HARD_LIMIT) {
            throw new MemoryCapacityError(`Cannot create ${memory_type}: listable memory total hard limit ${LISTABLE_MEMORY_TOTAL_HARD_LIMIT} reached. ` +
                'Please delete existing task_list/recently_memory records or archive recently_memory records to long-term memory before creating new listable memory.', {
                code: 'MEMORY_LISTABLE_TOTAL_LIMIT_REACHED',
                memory_type,
                limit: LISTABLE_MEMORY_TOTAL_HARD_LIMIT,
                current: stats.listable.count,
                recommended_actions: [
                    "list_memories({ memory_type: 'task_list' })",
                    "list_memories({ memory_type: 'recently_memory' })",
                    'remove_task_list_memory({ uri })',
                    'archive_recently_memory({ uri })',
                    "delete_memory({ uri, memory_type: 'recently_memory' })"
                ]
            });
        }
    }
    async getStats() {
        const data = await this.loadData();
        return this.calculateStats(data);
    }
    async createMemory(input) {
        const normalizedType = this.normalizeMemoryType(input.memory_type);
        const normalized = this.normalizeUri(input.uri);
        const data = await this.loadData();
        if (data.by_uri[normalized.uri]) {
            throw new Error(`Memory already exists: ${normalized.uri}`);
        }
        this.ensureListableMemoryCapacity(data, normalizedType);
        const now = this.nowIso();
        const record = {
            id: this.generateId(normalized.uri),
            uri: normalized.uri,
            domain: normalized.domain,
            path: normalized.path,
            content: String(input.content || ''),
            memory_type: normalizedType,
            createdAt: now,
            updatedAt: now,
            version: 1
        };
        data.by_uri[normalized.uri] = record;
        await this.saveData(data);
        return record;
    }
    async readMemory(input) {
        const normalized = this.normalizeUri(input.uri);
        const expectedType = typeof input.memory_type === 'string'
            ? this.normalizeMemoryType(input.memory_type)
            : undefined;
        const data = await this.loadData();
        const record = data.by_uri[normalized.uri] ?? null;
        if (!record) {
            return null;
        }
        this.ensureTypeMatch(record, expectedType);
        return record;
    }
    async updateMemory(input) {
        const normalized = this.normalizeUri(input.uri);
        const expectedType = typeof input.memory_type === 'string'
            ? this.normalizeMemoryType(input.memory_type)
            : undefined;
        const data = await this.loadData();
        const existing = data.by_uri[normalized.uri];
        if (!existing) {
            throw new Error(`Memory not found: ${normalized.uri}`);
        }
        this.ensureTypeMatch(existing, expectedType);
        let nextContent = existing.content;
        if (typeof input.old_string === 'string' && typeof input.new_string === 'string') {
            const oldString = input.old_string;
            if (!oldString) {
                throw new Error('old_string cannot be empty when replacing');
            }
            const occurrenceCount = nextContent.split(oldString).length - 1;
            if (occurrenceCount === 0) {
                throw new Error('old_string not found in memory content');
            }
            if (occurrenceCount > 1) {
                throw new Error('old_string appears multiple times; replace is ambiguous');
            }
            nextContent = nextContent.replace(oldString, input.new_string);
        }
        if (typeof input.append === 'string' && input.append.length > 0) {
            nextContent = `${nextContent}${input.append}`;
        }
        const updated = {
            ...existing,
            content: nextContent,
            updatedAt: this.nowIso(),
            version: existing.version + 1
        };
        data.by_uri[normalized.uri] = updated;
        await this.saveData(data);
        return updated;
    }
    async deleteMemory(input) {
        const normalized = this.normalizeUri(input.uri);
        const expectedType = typeof input.memory_type === 'string'
            ? this.normalizeMemoryType(input.memory_type)
            : undefined;
        const data = await this.loadData();
        const existing = data.by_uri[normalized.uri];
        if (!existing) {
            return false;
        }
        this.ensureTypeMatch(existing, expectedType);
        delete data.by_uri[normalized.uri];
        await this.saveData(data);
        return true;
    }
    async searchMemory(input) {
        const data = await this.loadData();
        const query = String(input.query || '').trim().toLowerCase();
        const memoryTypeFilter = input.memory_type
            ? this.normalizeMemoryType(input.memory_type)
            : undefined;
        const limit = typeof input.limit === 'number' && input.limit > 0 ? input.limit : 10;
        if (!query) {
            return { query, results: [] };
        }
        const seenById = new Set();
        const ranked = [];
        for (const record of Object.values(data.by_uri)) {
            if (memoryTypeFilter && record.memory_type !== memoryTypeFilter) {
                continue;
            }
            if (seenById.has(record.id)) {
                continue;
            }
            const haystack = `${record.uri}\n${record.content}`.toLowerCase();
            if (!haystack.includes(query)) {
                continue;
            }
            const inUri = record.uri.toLowerCase().includes(query) ? 2 : 0;
            const inContent = record.content.toLowerCase().includes(query) ? 1 : 0;
            const score = inUri + inContent;
            seenById.add(record.id);
            ranked.push({ score, record });
        }
        ranked.sort((a, b) => {
            if (b.score !== a.score) {
                return b.score - a.score;
            }
            return b.record.updatedAt.localeCompare(a.record.updatedAt);
        });
        return {
            query,
            results: ranked.slice(0, limit).map((item) => item.record)
        };
    }
    async listByType(memory_type) {
        const listType = this.normalizeMemoryType(memory_type, LISTABLE_MEMORY_TYPES);
        const data = await this.loadData();
        const seenById = new Set();
        const list = [];
        for (const record of Object.values(data.by_uri)) {
            if (record.memory_type !== listType) {
                continue;
            }
            if (seenById.has(record.id)) {
                continue;
            }
            seenById.add(record.id);
            list.push(record);
        }
        list.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
        return list;
    }
    async removeTaskListMemory(uri) {
        const record = await this.readMemory({ uri });
        if (!record) {
            return false;
        }
        if (record.memory_type !== 'task_list') {
            throw new Error('task_list_remove only supports task_list type');
        }
        return this.deleteMemory({ uri: record.uri, memory_type: 'task_list' });
    }
    async archiveRecentlyMemory(uri) {
        const record = await this.readMemory({ uri });
        if (!record) {
            throw new Error(`Memory not found: ${uri}`);
        }
        if (record.memory_type !== 'recently_memory') {
            throw new Error('recently_memory_archive only supports recently_memory type');
        }
        const data = await this.loadData();
        const existing = data.by_uri[record.uri];
        if (!existing) {
            throw new Error(`Memory not found: ${uri}`);
        }
        const archived = {
            ...existing,
            memory_type: 'memory',
            updatedAt: this.nowIso(),
            version: existing.version + 1
        };
        data.by_uri[record.uri] = archived;
        await this.saveData(data);
        return archived;
    }
}
//# sourceMappingURL=memory-store.js.map