generator client {
  provider = "prisma-client"
  output   = "../src/generated/prisma"
}

datasource db {
  provider = "sqlite"
}

// --- Enums (client-side only for SQLite — stored as TEXT) ---
// These enums are intentionally duplicated as Zod schemas in @naisys/hub-protocol
// and @naisys-supervisor/shared to avoid pulling heavy native deps (better-sqlite3,
// @prisma/client) into lightweight shared packages. The sync test in
// apps/supervisor/server/src/tests/hub-enum-sync.test.ts ensures they stay aligned.

enum ContextLogRole {
  NAISYS
  LLM
}

enum ContextLogSource {
  startPrompt
  endPrompt
  console
  llm
}

enum ContextLogType {
  comment
  error
  system
  tool
  compact
}

enum MessageKind {
  mail
  chat
}

enum RecipientType {
  to
  cc
  bcc
  from
}

enum AttachmentPurpose {
  mail
  context
}

enum HostType {
  naisys
  supervisor
}

enum ModelType {
  llm
  image
}

enum CostSource {
  console
  write_protection
  compact
  lynx
  look
  listen
  genimg
  websearch
  voice
}

// --- Models ---

model context_log {
  id            Int               @id @default(autoincrement())
  user_id       Int
  run_id        Int
  subagent_id   Int               @default(0)
  session_id    Int
  host_id       Int
  role          ContextLogRole
  source        ContextLogSource?
  type          ContextLogType?
  message       String
  attachment_id Int?
  created_at    DateTime          @default(now())
  users         users             @relation("context_log_user", fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  run_session   run_session       @relation(fields: [user_id, run_id, subagent_id, session_id], references: [user_id, run_id, subagent_id, session_id], onDelete: NoAction, onUpdate: NoAction)
  host          hosts             @relation(fields: [host_id], references: [id])
  attachment    attachments?      @relation(fields: [attachment_id], references: [id], onDelete: SetNull, onUpdate: NoAction)
  cursor_for    users[]           @relation("users_compact_log")

  @@index([user_id, run_id, subagent_id, session_id], map: "idx_context_log_user_run_subagent_session") // WHERE user_id, run_id, subagent_id, session_id in runsService.getContextLog
}

model costs {
  id                 Int         @id @default(autoincrement())
  user_id            Int
  run_id             Int
  subagent_id        Int         @default(0)
  session_id         Int
  host_id            Int
  source             CostSource
  model              String
  cost               Float?      @default(0)
  input_tokens       Int?        @default(0)
  output_tokens      Int?        @default(0)
  cache_write_tokens Int?        @default(0)
  cache_read_tokens  Int?        @default(0)
  created_at         DateTime    @default(now())
  users              users       @relation("costs_user", fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  run_session        run_session @relation(fields: [user_id, run_id, subagent_id, session_id], references: [user_id, run_id, subagent_id, session_id], onDelete: NoAction, onUpdate: NoAction)
  host               hosts       @relation(fields: [host_id], references: [id])

  @@index([created_at], map: "idx_costs_created_at") // ORDER BY created_at DESC, WHERE created_at >= in aggregate time range filtering
  @@index([user_id], map: "idx_costs_user_id") // Optional WHERE user_id filter in aggregate spend limit queries (parent + subagent costs both stamp parent's user_id, so this aggregates correctly)
  @@index([user_id, run_id, subagent_id, session_id, source, model], map: "idx_costs_user_run_subagent_session_source_model") // WHERE user_id, run_id, subagent_id, session_id, source, model in hubCostService findFirst
}

model mail_messages {
  id               Int                @id @default(autoincrement())
  from_user_id     Int
  host_id          Int? // Sender's host
  kind             MessageKind        @default(mail)
  participants     String // Sorted CSV of participant usernames, used as conversation lookup key
  subject          String
  body             String
  /// `"schedule:<name>"` for scheduler-fired admin chats; null otherwise.
  /// Drives per-(agent, schedule) dedupe and the UI badge.
  source           String?
  created_at       DateTime           @default(now())
  from_user        users              @relation("mail_messages_from_user", fields: [from_user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
  host             hosts?             @relation(fields: [host_id], references: [id])
  recipients       mail_recipients[]
  mail_attachments mail_attachments[]

  @@index([from_user_id], map: "idx_mail_messages_from_user_id") // WHERE from_user_id in sent messages filter
  @@index([created_at(sort: Desc)], map: "idx_mail_messages_created_at_desc") // ORDER BY created_at DESC in listMessages and searchMessages
  @@index([participants], map: "idx_mail_messages_participants") // WHERE participants in chat recent filtering
  @@index([source], map: "idx_mail_messages_source") // scheduler dedupe lookup by source tag
}

model attachments {
  id                       Int                        @id @default(autoincrement())
  public_id                String                     @unique
  filepath                 String
  filename                 String
  file_size                Int
  file_hash                String
  purpose                  AttachmentPurpose
  uploaded_by              Int?
  created_at               DateTime                   @default(now())
  uploader                 users?                     @relation("uploaded_attachments", fields: [uploaded_by], references: [id], onDelete: SetNull, onUpdate: NoAction)
  mail_attachments         mail_attachments[]
  context_logs             context_log[]
  user_startup_attachments user_startup_attachments[]
}

model user_startup_attachments {
  user_id       Int
  attachment_id Int
  path          String
  created_at    DateTime    @default(now())
  user          users       @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  attachment    attachments @relation(fields: [attachment_id], references: [id], onDelete: Cascade, onUpdate: NoAction)

  @@id([user_id, path])
  @@index([attachment_id], map: "idx_user_startup_attachments_attachment_id")
}

model mail_attachments {
  message_id    Int
  attachment_id Int
  created_at    DateTime      @default(now())
  message       mail_messages @relation(fields: [message_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  attachment    attachments   @relation(fields: [attachment_id], references: [id], onDelete: Cascade, onUpdate: NoAction)

  @@id([message_id, attachment_id])
}

model mail_recipients {
  id          Int           @id @default(autoincrement())
  message_id  Int
  user_id     Int
  type        RecipientType
  read_at     DateTime?
  read_run_id Int?
  archived_at DateTime?
  created_at  DateTime      @default(now())
  message     mail_messages @relation(fields: [message_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  user        users         @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)

  @@index([message_id, user_id], map: "idx_mail_recipients_message_user") // JOIN from mail_messages includes, WHERE message_id + user_id in read/archive updateMany
  @@index([user_id, message_id(sort: Desc)], map: "idx_mail_recipients_user_message_desc") // recipients.some({ user_id }) in mail list/search/unread filtering
  @@index([read_at, user_id], map: "idx_mail_recipients_read_at_user_id") // WHERE read_at IS NULL with distinct/select user_id for auto-start candidate scans
}

model users {
  id                       Int                        @id @default(autoincrement())
  uuid                     String                     @unique
  username                 String                     @unique
  title                    String
  api_key_hash             String?                    @unique
  lead_user_id             Int?
  config                   String                     @default("{}")
  /// Points at the most recent context_log row of type=compact. AGENT_START
  /// uses it as a cursor: summary text lives on that row, anything with a
  /// higher id is unsummarized activity.
  compact_log_id           Int?
  compact_log              context_log?               @relation("users_compact_log", fields: [compact_log_id], references: [id], onDelete: SetNull, onUpdate: NoAction)
  /// MIN(next fire) across enabled `config.schedules`; null when none.
  /// Indexed for the scheduler's `WHERE next_run_at <= now()` poll.
  next_run_at              DateTime?
  created_at               DateTime                   @default(now())
  updated_at               DateTime                   @updatedAt
  enabled                  Boolean                    @default(true)
  archived                 Boolean                    @default(false)
  lead_user                users?                     @relation("lead_to_subagents", fields: [lead_user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  subagents                users[]                    @relation("lead_to_subagents")
  mail_messages_sent       mail_messages[]            @relation("mail_messages_from_user")
  mail_recipients          mail_recipients[]
  run_sessions             run_session[]
  context_logs             context_log[]              @relation("context_log_user")
  costs                    costs[]                    @relation("costs_user")
  uploaded_attachments     attachments[]              @relation("uploaded_attachments")
  user_notifications       user_notifications?
  user_hosts               user_hosts[]
  config_revisions         config_revisions[]         @relation("config_revision_user")
  config_changes_made      config_revisions[]         @relation("config_revision_changed_by")
  user_startup_attachments user_startup_attachments[]
  // username has @unique which creates an implicit index

  @@index([next_run_at], map: "idx_users_next_run_at") // scheduler tick: WHERE next_run_at <= now()
}

model user_notifications {
  user_id               Int       @id
  latest_host_id        Int?
  latest_log_id         Int       @default(0)
  latest_mail_id        Int       @default(0)
  latest_chat_id        Int       @default(0)
  last_active           DateTime?
  cost_suspended_reason String?
  spend_limit_reset_at  DateTime?
  budget_left           Decimal?
  updated_at            DateTime  @updatedAt
  users                 users     @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  host                  hosts?    @relation(fields: [latest_host_id], references: [id])
}

model user_hosts {
  user_id    Int
  host_id    Int
  created_at DateTime @default(now())
  users      users    @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  host       hosts    @relation(fields: [host_id], references: [id])

  @@id([user_id, host_id])
}

model run_session {
  user_id       Int
  run_id        Int
  subagent_id   Int           @default(0)
  session_id    Int
  host_id       Int
  last_active   DateTime
  model_name    String
  latest_log_id Int           @default(0)
  total_lines   Int           @default(0)
  total_tokens  Int           @default(0)
  total_cost    Float         @default(0)
  /// `"manual"` | `"wake-on-message"` | `"schedule:<name>"`; null on legacy rows.
  triggered_by  String?
  created_at    DateTime      @default(now())
  updated_at    DateTime      @updatedAt
  users         users         @relation(fields: [user_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
  host          hosts         @relation(fields: [host_id], references: [id])
  context_logs  context_log[]
  costs         costs[]

  @@id([user_id, run_id, subagent_id, session_id])
  @@index([run_id(sort: Desc)], map: "idx_run_session_run_id_desc") // ORDER BY run_id DESC in hubRunService to find max run_id for new runs
  @@index([user_id, last_active(sort: Desc)], map: "idx_run_session_user_last_active_desc") // WHERE user_id ORDER BY last_active DESC in supervisor runsService pagination
}

model schema_version {
  id      Int      @id @default(1)
  version Int      @unique(map: "unq_schema_version_version")
  updated DateTime
}

model hosts {
  id                 Int                  @id @default(autoincrement())
  name               String               @unique(map: "unq_hosts_name") // NAISYS_HOSTNAME
  machine_id         String?              @unique(map: "unq_hosts_machine_id")
  restricted         Boolean              @default(false)
  host_type          HostType             @default(naisys)
  last_ip            String?
  last_version       String?
  environment        String? // JSON blob: { platform, osVersion, shell, arch, nodeVersion }
  last_active        DateTime?
  created_at         DateTime             @default(now())
  user_notifications user_notifications[]
  mail_messages      mail_messages[]
  run_sessions       run_session[]
  costs              costs[]
  context_logs       context_log[]
  user_hosts         user_hosts[]
}

model config_revisions {
  id            Int      @id @default(autoincrement())
  user_id       Int
  config        String
  changed_by_id Int
  created_at    DateTime @default(now())

  user       users @relation("config_revision_user", fields: [user_id], references: [id], onDelete: Cascade, onUpdate: NoAction)
  changed_by users @relation("config_revision_changed_by", fields: [changed_by_id], references: [id], onDelete: NoAction, onUpdate: NoAction)

  @@index([user_id, created_at(sort: Desc)], map: "idx_config_revisions_user_created_desc")
}

model variables {
  key             String   @id
  value           String
  export_to_shell Boolean  @default(false)
  sensitive       Boolean  @default(false)
  created_by      String
  updated_by      String
  created_at      DateTime @default(now())
  updated_at      DateTime @updatedAt
}

model models {
  id           Int       @id @default(autoincrement())
  key          String    @unique
  type         ModelType
  label        String
  version_name String
  is_builtin   Boolean   @default(false)
  is_custom    Boolean   @default(false)
  meta         String // JSON blob — type-specific properties validated by Zod
  created_at   DateTime  @default(now())
  updated_at   DateTime  @updatedAt
}
