/**
 * InMemoryThreadStore — reference in-memory implementation of
 * {@link ThreadStore}.
 *
 * Mirrors the write-time CAS contract of the disk store: every
 * `updateThread` compares the supplied `ownerVersion` against the persisted
 * copy and rejects with `StaleThreadError` on mismatch. Convention #17:
 * cross-tenant access throws `TenantIsolationError` with no fallback.
 */
import { StaleThreadError, TenantIsolationError } from '../../session/errors.js';
import { generateThreadId } from '../../utils/id.js';
export class InMemoryThreadStore {
    threads = new Map();
    async createThread(params, tenantId) {
        const now = new Date();
        const thread = {
            id: generateThreadId(),
            projectId: params.projectId,
            tenantId,
            title: params.title,
            status: 'open',
            ownerVersion: 0,
            createdAt: now,
            updatedAt: now,
        };
        this.threads.set(thread.id, { tenantId, thread });
        return thread;
    }
    async getThread(threadId, tenantId) {
        const record = this.threads.get(threadId);
        if (!record)
            return null;
        this.assertTenant(record.tenantId, tenantId, `thread(${threadId})`);
        return record.thread;
    }
    async updateThread(thread, tenantId) {
        if (thread.tenantId !== tenantId) {
            throw new TenantIsolationError({
                requested: tenantId,
                resource: `thread(${thread.id}) payload`,
            });
        }
        const existing = this.threads.get(thread.id);
        if (!existing) {
            throw new Error(`Thread ${thread.id} not found`);
        }
        this.assertTenant(existing.tenantId, tenantId, `thread(${thread.id})`);
        // CAS on ownerVersion — supplied version must match persisted exactly.
        // Any drift means another writer already advanced the record; the caller
        // must re-read + re-apply + retry.
        if (thread.ownerVersion !== existing.thread.ownerVersion) {
            throw new StaleThreadError({
                threadId: thread.id,
                expectedVersion: thread.ownerVersion,
                actualVersion: existing.thread.ownerVersion,
            });
        }
        const updated = {
            ...thread,
            ownerVersion: existing.thread.ownerVersion + 1,
            updatedAt: new Date(),
        };
        this.threads.set(thread.id, { tenantId, thread: updated });
    }
    async deleteThread(threadId, tenantId) {
        const record = this.threads.get(threadId);
        if (!record)
            return; // Idempotent: missing = no-op.
        this.assertTenant(record.tenantId, tenantId, `thread(${threadId})`);
        this.threads.delete(threadId);
    }
    async listThreads(projectId, tenantId) {
        const matches = [];
        for (const { tenantId: ownerTenant, thread } of this.threads.values()) {
            if (ownerTenant !== tenantId)
                continue;
            if (thread.projectId !== projectId)
                continue;
            matches.push(thread);
        }
        matches.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
        return matches;
    }
    assertTenant(owning, requested, resource) {
        if (owning !== requested) {
            throw new TenantIsolationError({ requested, resource });
        }
    }
}
//# sourceMappingURL=memory.js.map