// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

enum ConnectionSyncStatus {
  SYNC_NEEDED
  IN_SYNC_QUEUE
  SYNCING
  SYNCED
  SYNCED_WITH_WARNINGS
  FAILED
}

enum StripeSubscriptionStatus {
  ACTIVE
  INACTIVE
}

enum ChatVisibility {
  PRIVATE
  PUBLIC
}

model Repo {
  id                 Int                    @id @default(autoincrement())
  name               String /// Full repo name, including the vcs hostname (ex. github.com/sourcebot-dev/sourcebot)
  displayName        String? /// Display name of the repo for UI (ex. sourcebot-dev/sourcebot)
  createdAt          DateTime               @default(now())
  updatedAt          DateTime               @updatedAt
  isFork             Boolean
  isArchived         Boolean
  isPublic           Boolean                @default(false)
  metadata           Json /// For schema see repoMetadataSchema in packages/backend/src/types.ts
  cloneUrl           String
  webUrl             String?
  connections        RepoToConnection[]
  imageUrl           String?

  permittedUsers     UserToRepoPermission[]
  permissionSyncJobs RepoPermissionSyncJob[]
  permissionSyncedAt    DateTime? /// When the permissions were last synced successfully.

  jobs RepoIndexingJob[]
  indexedAt          DateTime? /// When the repo was last indexed successfully.

  external_id           String /// The id of the repo in the external service
  external_codeHostType String /// The type of the external service (e.g., github, gitlab, etc.)
  external_codeHostUrl  String /// The base url of the external service (e.g., https://github.com)

  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  searchContexts SearchContext[]

  @@unique([external_id, external_codeHostUrl, orgId])
  @@index([orgId])
}

enum RepoIndexingJobStatus {
  PENDING
  IN_PROGRESS
  COMPLETED
  FAILED
}

enum RepoIndexingJobType {
  INDEX
  CLEANUP
}

model RepoIndexingJob {
  id          String                      @id @default(cuid())
  type        RepoIndexingJobType
  status      RepoIndexingJobStatus @default(PENDING)
  createdAt   DateTime                    @default(now())
  updatedAt   DateTime                    @updatedAt
  completedAt DateTime?

  errorMessage String?

  repo   Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
  repoId Int
}

enum RepoPermissionSyncJobStatus {
  PENDING
  IN_PROGRESS
  COMPLETED
  FAILED
}

model RepoPermissionSyncJob {
  id          String                      @id @default(cuid())
  status      RepoPermissionSyncJobStatus @default(PENDING)
  createdAt   DateTime                    @default(now())
  updatedAt   DateTime                    @updatedAt
  completedAt DateTime?

  errorMessage String?

  repo   Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
  repoId Int
}

model SearchContext {
  id Int @id @default(autoincrement())

  name        String
  description String?
  repos       Repo[]

  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  @@unique([name, orgId])
}

model Connection {
  id                 Int                  @id @default(autoincrement())
  name               String
  config             Json
  isDeclarative      Boolean              @default(false)
  createdAt          DateTime             @default(now())
  updatedAt          DateTime             @updatedAt
  /// When the connection was last synced successfully.
  syncedAt           DateTime?
  repos              RepoToConnection[]
  syncStatus         ConnectionSyncStatus @default(SYNC_NEEDED)
  syncStatusMetadata Json?

  // The type of connection (e.g., github, gitlab, etc.)
  connectionType String

  // The organization that owns this connection
  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  @@unique([name, orgId])
}

model RepoToConnection {
  addedAt DateTime @default(now())

  connection   Connection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
  connectionId Int

  repo   Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
  repoId Int

  @@id([connectionId, repoId])
  @@index([repoId, connectionId])
}

model Invite {
  /// The globally unique invite id
  id String @id @default(cuid())

  /// Time of invite creation
  createdAt DateTime @default(now())

  /// The email of the recipient of the invite
  recipientEmail String

  /// The user that created the invite
  host       User   @relation(fields: [hostUserId], references: [id], onDelete: Cascade)
  hostUserId String

  /// The organization the invite is for
  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  @@unique([recipientEmail, orgId])
}

model AccountRequest {
  id String @id @default(cuid())

  createdAt DateTime @default(now())

  requestedBy   User   @relation(fields: [requestedById], references: [id], onDelete: Cascade)
  requestedById String @unique

  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  @@unique([requestedById, orgId])
}

model Org {
  id          Int          @id @default(autoincrement())
  name        String
  domain      String       @unique
  createdAt   DateTime     @default(now())
  updatedAt   DateTime     @updatedAt
  members     UserToOrg[]
  connections Connection[]
  repos       Repo[]
  secrets     Secret[]
  apiKeys     ApiKey[]
  isOnboarded Boolean      @default(false)
  imageUrl    String?
  metadata    Json? // For schema see orgMetadataSchema in packages/web/src/types.ts

  memberApprovalRequired Boolean @default(true)

  stripeCustomerId         String?
  stripeSubscriptionStatus StripeSubscriptionStatus?
  stripeLastUpdatedAt      DateTime?

  /// List of pending invites to this organization
  invites Invite[]

  /// The invite id for this organization
  inviteLinkEnabled Boolean @default(false)
  inviteLinkId      String?

  audits Audit[]

  accountRequests AccountRequest[]

  searchContexts SearchContext[]

  chats Chat[]
}

enum OrgRole {
  OWNER
  MEMBER
  GUEST
}

model UserToOrg {
  joinedAt DateTime @default(now())

  /// The linked organization
  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  /// The linked user
  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId String

  role OrgRole @default(MEMBER)

  @@id([orgId, userId])
}

model Secret {
  orgId          Int
  key            String
  encryptedValue String
  iv             String

  createdAt DateTime @default(now())

  org Org @relation(fields: [orgId], references: [id], onDelete: Cascade)

  @@id([orgId, key])
}

model ApiKey {
  name String
  hash String @id @unique

  createdAt  DateTime  @default(now())
  lastUsedAt DateTime?

  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  createdBy   User   @relation(fields: [createdById], references: [id], onDelete: Cascade)
  createdById String
}

model Audit {
  id        String   @id @default(cuid())
  timestamp DateTime @default(now())

  action           String
  actorId          String
  actorType        String
  targetId         String
  targetType       String
  sourcebotVersion String
  metadata         Json?

  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  @@index([actorId, actorType, targetId, targetType, orgId])
  // Fast path for analytics queries – orgId is first because we assume most deployments are single tenant
  @@index([orgId, timestamp, action, actorId], map: "idx_audit_core_actions_full")
  // Fast path for analytics queries for a specific user
  @@index([actorId, timestamp], map: "idx_audit_actor_time_full")
}

// @see : https://authjs.dev/concepts/database-models#user
model User {
  id              String                 @id @default(cuid())
  name            String?
  email           String?                @unique
  hashedPassword  String?
  emailVerified   DateTime?
  image           String?
  accounts        Account[]
  orgs            UserToOrg[]
  accountRequest  AccountRequest?
  accessibleRepos UserToRepoPermission[]

  /// List of pending invites that the user has created
  invites Invite[]

  apiKeys ApiKey[]

  chats Chat[]

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  permissionSyncJobs UserPermissionSyncJob[]
  permissionSyncedAt DateTime?
}

enum UserPermissionSyncJobStatus {
  PENDING
  IN_PROGRESS
  COMPLETED
  FAILED
}

model UserPermissionSyncJob {
  id          String                      @id @default(cuid())
  status      UserPermissionSyncJobStatus @default(PENDING)
  createdAt   DateTime                    @default(now())
  updatedAt   DateTime                    @updatedAt
  completedAt DateTime?

  errorMessage String?

  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId String
}

model UserToRepoPermission {
  createdAt DateTime @default(now())

  repo   Repo @relation(fields: [repoId], references: [id], onDelete: Cascade)
  repoId Int

  user   User   @relation(fields: [userId], references: [id], onDelete: Cascade)
  userId String

  @@id([repoId, userId])
}

// @see : https://authjs.dev/concepts/database-models#account
model Account {
  id                String  @id @default(cuid())
  userId            String
  type              String
  provider          String
  providerAccountId String
  refresh_token     String?
  access_token      String?
  expires_at        Int?
  token_type        String?
  scope             String?
  id_token          String?
  session_state     String?

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  user User @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@unique([provider, providerAccountId])
}

// @see : https://authjs.dev/concepts/database-models#verificationtoken
model VerificationToken {
  identifier String
  token      String
  expires    DateTime

  @@unique([identifier, token])
}

model Chat {
  id String @id @default(cuid())

  name String?

  createdBy   User   @relation(fields: [createdById], references: [id], onDelete: Cascade)
  createdById String

  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  org   Org @relation(fields: [orgId], references: [id], onDelete: Cascade)
  orgId Int

  visibility ChatVisibility @default(PRIVATE)
  isReadonly Boolean        @default(false)

  messages Json // This is a JSON array of `Message` types from @ai-sdk/ui-utils.
}
