generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["postgresqlExtensions"]
}

datasource db {
  provider   = "postgresql"
  url        = env("DATABASE_URL")
  extensions = [vector]
}

/// Tenant represents an organization or a single-user instance
model Tenant {
  id               String           @id @default(cuid())
  name             String
  settings         Json?
  status           TenantStatus     @default(ACTIVE)
  createdAt        DateTime         @default(now())
  updatedAt        DateTime         @updatedAt
  apiKeys          ApiKey[]
  episodicMemories EpisodicMemory[]
  messageLogs      MessageLog[]
  recentSummaries  RecentSummary[]
  semanticMemories SemanticMemory[]
  sessions         Session[]
  users            User[]

  @@index([status])
}

/// User represents an individual user within a tenant
model User {
  id               String
  tenantId         String
  email            String?
  username         String?
  metadata         Json?
  createdAt        DateTime         @default(now())
  updatedAt        DateTime         @updatedAt
  episodicMemories EpisodicMemory[]
  messageLogs      MessageLog[]
  recentSummaries  RecentSummary[]
  semanticMemories SemanticMemory[]
  sessions         Session[]
  tenant           Tenant           @relation(fields: [tenantId], references: [id], onDelete: Cascade)

  @@id([tenantId, id])
  @@unique([tenantId, email])
  @@index([tenantId])
}

/// Session represents a conversation session
model Session {
  id               String           @id @default(cuid())
  tenantId         String
  userId           String
  title            String?
  status           SessionStatus    @default(ACTIVE)
  startedAt        DateTime         @default(now())
  endedAt          DateTime?
  pausedAt         DateTime?
  resumedAt        DateTime?
  episodicMemories EpisodicMemory[]
  messageLogs      MessageLog[]
  semanticMemories SemanticMemory[]
  tenant           Tenant           @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user             User             @relation(fields: [tenantId, userId], references: [tenantId, id], onDelete: Cascade)

  @@index([tenantId, userId])
  @@index([startedAt])
  @@index([status])
}

/// MessageLog - Raw conversational logs (Single Source of Truth)
/// Does NOT participate in semantic retrieval (no embedding)
model MessageLog {
  id           String   @id @default(cuid())
  tenantId     String
  userId       String
  sessionId    String
  messageId    String   @unique
  content      String
  role         String
  model        String?
  toolCalls    Json?
  finishReason String?
  tokenUsage   Json?
  metadata     Json?
  dreamStage   Int      @default(0)
  createdAt    DateTime @default(now())
  session      Session  @relation(fields: [sessionId], references: [id], onDelete: Cascade)
  tenant       Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user         User     @relation(fields: [tenantId, userId], references: [tenantId, id], onDelete: Cascade)

  @@index([tenantId, userId])
  @@index([sessionId])
  @@index([createdAt])
  @@index([dreamStage])
}

/// EpisodicMemory - Encoded events with summary embeddings
/// Derived from MessageLog through dream process
model EpisodicMemory {
  id               String                 @id @default(cuid())
  tenantId         String
  userId           String
  sessionId        String
  episodeId        String                 @unique
  summary          String
  embedding        Unsupported("vector")?
  episodeType      EpisodeType            @default(qa)
  emotionalValence Float?
  attachments      Json?
  messageIds       Json
  confidence       Float                  @default(1)
  graphStage       Int                    @default(0)
  timestamp        DateTime               @default(now())
  createdAt        DateTime               @default(now())
  updatedAt        DateTime               @updatedAt
  session          Session                @relation(fields: [sessionId], references: [id], onDelete: Cascade)
  tenant           Tenant                 @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user             User                   @relation(fields: [tenantId, userId], references: [tenantId, id], onDelete: Cascade)

  @@index([tenantId, userId])
  @@index([sessionId, timestamp])
  @@index([timestamp])
  @@index([graphStage])
}

/// SemanticMemory - Structured knowledge records (PG primary storage)
/// Neo4j is used for relationship indexing only
model SemanticMemory {
  id             String                 @id @default(cuid())
  tenantId       String
  userId         String
  sessionId      String?
  knowledgeId    String                 @unique
  content        String
  embedding      Unsupported("vector")?
  type           KnowledgeType
  entityId       String
  entityType     String?
  episodeIds     Json
  properties     Json?
  confidence     Float                  @default(1)
  decayRate      Float                  @default(0.01)
  lastDecayAt    DateTime               @default(now())
  lastAccessedAt DateTime               @default(now())
  tombstone      Boolean                @default(false)
  createdAt      DateTime               @default(now())
  updatedAt      DateTime               @updatedAt
  session        Session?               @relation(fields: [sessionId], references: [id])
  tenant         Tenant                 @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user           User                   @relation(fields: [tenantId, userId], references: [tenantId, id], onDelete: Cascade)

  @@index([tenantId, userId])
  @@index([entityId])
  @@index([type])
  @@index([tombstone])
  @@index([confidence])
}

/// DreamFailure - Records failed dream attempts
model DreamFailure {
  id           String   @id @default(cuid())
  tenantId     String
  userId       String
  sessionId    String
  stage        Int      @default(0)
  errorMessage String
  errorStack   String?
  createdAt    DateTime @default(now())
  attemptCount Int      @default(0)
  jobId        String
  payload      Json?
  triggerType  String

  @@index([tenantId, userId])
  @@index([sessionId])
  @@index([createdAt])
}

/// RecentSummary - overwriteable recent focus summary for prompt hydration
model RecentSummary {
  id                   String   @id @default(cuid())
  tenantId             String   @map("tenant_id")
  userId               String   @map("user_id")
  scopeType            String   @map("scope_type")
  scopeKey             String   @map("scope_key")
  profile              String   @default("general")
  source               String?
  windowStart          DateTime? @map("window_start")
  windowEnd            DateTime? @map("window_end")
  lastMessageId        String?  @map("last_message_id")
  lastMessageCreatedAt DateTime? @map("last_message_created_at")
  summary              Json
  evidence             Json?
  messageCount         Int      @default(0) @map("message_count")
  version              Int      @default(1)
  createdAt            DateTime @default(now()) @map("created_at")
  updatedAt            DateTime @updatedAt @map("updated_at")
  tenant               Tenant   @relation(fields: [tenantId], references: [id], onDelete: Cascade)
  user                 User     @relation(fields: [tenantId, userId], references: [tenantId, id], onDelete: Cascade)

  @@unique([tenantId, userId, scopeType, scopeKey])
  @@index([tenantId, userId])
  @@index([updatedAt])
  @@map("recent_summaries")
}

/// DecisionSnapshot stores decisions, routing choices, and context assembly
/// Used for debugging hallucinations and understanding system behavior
model DecisionSnapshot {
  id                String   @id @default(cuid())
  traceId           String
  tenantId          String?
  userId            String?
  sessionId         String?
  smgDecision       Json?
  routerDecision    Json?
  systemPath        String?
  retrievedMemories Json?
  assembledContext  Json?
  processingTime    Int?
  createdAt         DateTime @default(now())

  @@index([traceId])
  @@index([createdAt])
  @@index([tenantId, userId])
}

model OutboxEvent {
  id          String    @id
  eventType   String
  payload     Json
  processed   Boolean   @default(false)
  processedAt DateTime?
  errorCount  Int       @default(0)
  lastError   String?
  createdAt   DateTime  @default(now())

  @@index([createdAt])
  @@index([eventType])
  @@index([processed])
}

/// ApiKey - API key for authenticating external API requests
/// Each key is bound to a specific tenant and has scoped permissions
model ApiKey {
  id           String        @id @default(cuid())
  tenantId     String
  name         String
  key          String        @unique
  keyPrefix    String
  scopes       ApiKeyScope[] @default([READ, WRITE])
  status       ApiKeyStatus  @default(ACTIVE)
  expiresAt    DateTime?
  lastUsedAt   DateTime?
  createdAt    DateTime      @default(now())
  createdBy    String?
  revokeReason String?
  revokedAt    DateTime?
  revokedBy    String?
  tenant       Tenant        @relation(fields: [tenantId], references: [id], onDelete: Cascade)

  @@index([key])
  @@index([tenantId])
  @@index([status])
}

enum TenantStatus {
  ACTIVE
  SUSPENDED
  DELETED
}

enum SessionStatus {
  ACTIVE
  PAUSED
  ENDED
}

enum KnowledgeType {
  fact
  experience
  goal
  preference
}

enum EpisodeType {
  qa
  decision
  preference_expression
  task
  social
  error_correction
  feedback
}

/// ApiKey scope - permission levels for API access
enum ApiKeyScope {
  READ
  WRITE
  DELETE
  ADMIN
}

/// ApiKey status
enum ApiKeyStatus {
  ACTIVE
  REVOKED
  EXPIRED
}
