/**
 * Workspace entity and store for multi-tenant team-workspace support.
 *
 * Each Workspace maps to a billable tenant (design-partner, team, or
 * enterprise tier). WorkspaceMembers hold the per-user role assignment
 * inside a workspace.
 *
 * All mutations emit to the tamper-evident audit chain.
 * In-memory store -- Prisma migration flagged at bottom of file.
 *
 * @connexum/ai-governance tenant substrate
 */
import * as crypto from 'node:crypto';
// ---------------------------------------------------------------------------
// WorkspaceStore
// ---------------------------------------------------------------------------
export class WorkspaceStore {
    workspaces = new Map();
    byTenant = new Map(); // tenantId -> workspaceId[]
    audit;
    constructor(opts) {
        this.audit = opts?.appendAudit;
    }
    async emit(workspaceId, entry) {
        if (!this.audit)
            return;
        try {
            await this.audit(workspaceId, { ...entry, timestamp: new Date().toISOString() });
        }
        catch {
            // Audit chain failure must not block the write.
        }
    }
    /** Create a new workspace. The caller automatically becomes owner. */
    async create(params) {
        const workspaceId = crypto.randomUUID();
        const now = new Date().toISOString();
        const workspace = {
            ...params,
            workspaceId,
            createdAt: now,
        };
        this.workspaces.set(workspaceId, workspace);
        let tenantSet = this.byTenant.get(params.tenantId);
        if (!tenantSet) {
            tenantSet = new Set();
            this.byTenant.set(params.tenantId, tenantSet);
        }
        tenantSet.add(workspaceId);
        await this.emit(workspaceId, {
            kind: 'tenant:workspace-created',
            workspaceId,
            tenantId: params.tenantId,
            name: params.name,
            billingTier: params.billingTier,
            createdBy: params.createdBy,
        });
        return workspace;
    }
    /** Retrieve a workspace by ID, or null if not found. */
    get(workspaceId) {
        return this.workspaces.get(workspaceId) ?? null;
    }
    /** Update mutable workspace fields. Emits audit event on any change. */
    async update(workspaceId, updates, actor) {
        const existing = this.workspaces.get(workspaceId);
        if (!existing)
            throw new Error(`Workspace not found: ${workspaceId}`);
        const updated = { ...existing, ...updates };
        this.workspaces.set(workspaceId, updated);
        await this.emit(workspaceId, {
            kind: 'tenant:workspace-updated',
            workspaceId,
            actor,
            fields: Object.keys(updates),
        });
        return updated;
    }
    /** Delete a workspace (owner only -- enforced at route layer). */
    async delete(workspaceId, actor) {
        const existing = this.workspaces.get(workspaceId);
        if (!existing)
            return; // idempotent
        this.workspaces.delete(workspaceId);
        this.byTenant.get(existing.tenantId)?.delete(workspaceId);
        await this.emit(workspaceId, {
            kind: 'tenant:workspace-deleted',
            workspaceId,
            tenantId: existing.tenantId,
            actor,
        });
    }
    /** List all workspaces for a tenant. */
    listByTenant(tenantId) {
        const ids = this.byTenant.get(tenantId);
        if (!ids)
            return [];
        const result = [];
        for (const id of ids) {
            const w = this.workspaces.get(id);
            if (w)
                result.push(w);
        }
        return result;
    }
    /** List all workspaces (for super-admin). */
    listAll() {
        return Array.from(this.workspaces.values());
    }
}
// ---------------------------------------------------------------------------
// WorkspaceMemberStore
// ---------------------------------------------------------------------------
export class WorkspaceMemberStore {
    members = new Map(); // key: workspaceId:userId
    byWorkspace = new Map(); // workspaceId -> userId[]
    audit;
    constructor(opts) {
        this.audit = opts?.appendAudit;
    }
    key(workspaceId, userId) {
        return `${workspaceId}:${userId}`;
    }
    async emit(workspaceId, entry) {
        if (!this.audit)
            return;
        try {
            await this.audit(workspaceId, { ...entry, timestamp: new Date().toISOString() });
        }
        catch {
            // Non-blocking.
        }
    }
    /** Add or update a member record. */
    async upsert(member) {
        const k = this.key(member.workspaceId, member.userId);
        const existing = this.members.get(k);
        this.members.set(k, member);
        let wsSet = this.byWorkspace.get(member.workspaceId);
        if (!wsSet) {
            wsSet = new Set();
            this.byWorkspace.set(member.workspaceId, wsSet);
        }
        wsSet.add(member.userId);
        await this.emit(member.workspaceId, {
            kind: existing ? 'tenant:member-updated' : 'tenant:member-added',
            workspaceId: member.workspaceId,
            userId: member.userId,
            email: member.email,
            role: member.role,
            status: member.status,
            invitedBy: member.invitedBy,
        });
        return member;
    }
    /** Retrieve a single member. */
    get(workspaceId, userId) {
        return this.members.get(this.key(workspaceId, userId)) ?? null;
    }
    /** List all members of a workspace. */
    listByWorkspace(workspaceId) {
        const ids = this.byWorkspace.get(workspaceId);
        if (!ids)
            return [];
        const result = [];
        for (const userId of ids) {
            const m = this.members.get(this.key(workspaceId, userId));
            if (m)
                result.push(m);
        }
        return result;
    }
    /** Remove a member from a workspace. Emits audit event. */
    async remove(workspaceId, userId, actor) {
        const k = this.key(workspaceId, userId);
        const existing = this.members.get(k);
        if (!existing)
            return; // idempotent
        this.members.delete(k);
        this.byWorkspace.get(workspaceId)?.delete(userId);
        await this.emit(workspaceId, {
            kind: 'tenant:member-removed',
            workspaceId,
            userId,
            actor,
        });
    }
    /** Update the role of an existing member. */
    async setRole(workspaceId, userId, role, actor) {
        const k = this.key(workspaceId, userId);
        const existing = this.members.get(k);
        if (!existing)
            throw new Error(`Member not found: ${userId} in workspace ${workspaceId}`);
        const updated = { ...existing, role };
        this.members.set(k, updated);
        await this.emit(workspaceId, {
            kind: 'tenant:member-role-changed',
            workspaceId,
            userId,
            previousRole: existing.role,
            newRole: role,
            actor,
        });
        return updated;
    }
    /** Suspend a member (status -> suspended). */
    async suspend(workspaceId, userId, actor) {
        const k = this.key(workspaceId, userId);
        const existing = this.members.get(k);
        if (!existing)
            throw new Error(`Member not found: ${userId} in workspace ${workspaceId}`);
        const updated = { ...existing, status: 'suspended' };
        this.members.set(k, updated);
        await this.emit(workspaceId, {
            kind: 'tenant:member-suspended',
            workspaceId,
            userId,
            actor,
        });
        return updated;
    }
}
// ---------------------------------------------------------------------------
// Singleton stores (shared within a process)
// ---------------------------------------------------------------------------
let _workspaceStore = null;
let _memberStore = null;
export function getWorkspaceStore(opts) {
    if (!_workspaceStore)
        _workspaceStore = new WorkspaceStore(opts);
    return _workspaceStore;
}
export function getWorkspaceMemberStore(opts) {
    if (!_memberStore)
        _memberStore = new WorkspaceMemberStore(opts);
    return _memberStore;
}
/** Test helper: reset both singletons. */
export function __resetWorkspaceStoresForTest() {
    _workspaceStore = null;
    _memberStore = null;
}
// ---------------------------------------------------------------------------
// Prisma migration note (flagged for later)
// ---------------------------------------------------------------------------
//
// TODO(Prisma migration): Add three models to schema.prisma:
//
// model Workspace {
//   workspaceId   String   @id @default(uuid())
//   tenantId      String
//   name          String
//   createdBy     String
//   createdAt     DateTime @default(now())
//   billingTier   String
//   activePackIds String[]
//   ssoProviderId String?
//   settings      Json
//   members       WorkspaceMember[]
//   policies      TenantPolicy[]
// }
//
// model WorkspaceMember {
//   workspaceId String
//   userId      String
//   email       String
//   role        String
//   joinedAt    DateTime @default(now())
//   invitedBy   String
//   status      String   @default("pending")
//   workspace   Workspace @relation(fields: [workspaceId], references: [workspaceId])
//   @@id([workspaceId, userId])
// }
//# sourceMappingURL=workspace.js.map