datasource db {
  provider = "postgresql"
}

generator client {
  provider               = "prisma-client"
  output                 = "../generated/prisma"
  generatedFileExtension = "ts"
  importFileExtension    = "ts"
}

// Account, User, Session, and VerificationToken based on: https://authjs.dev/reference/adapter/prisma
model Account {
  id                    String        @id @default(cuid())
  createdAt             DateTime      @default(now())
  updatedAt             DateTime      @updatedAt
  userId                String
  provider              String
  type                  String        @default("oidc") // next-auth deprecated field
  providerAccountId     String
  refresh_token         String?       @db.Text
  refreshTokenExpiresAt DateTime?
  access_token          String?       @db.Text
  expires_at            DateTime?     @default(now())
  disconnectedAt        DateTime? // When OAuth tokens were invalidated (password change, revoked access)
  token_type            String?
  scope                 String?
  id_token              String?       @db.Text
  session_state         String?
  user                  User          @relation(fields: [userId], references: [id], onDelete: Cascade)
  emailAccount          EmailAccount?

  @@unique([provider, providerAccountId])
  @@index([userId])
}

// not in use. we only use jwt for sessions
model Session {
  id           String   @id @default(cuid())
  sessionToken String   @unique
  userId       String
  expires      DateTime
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
  ipAddress    String?
  userAgent    String?
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)

  activeOrganizationId String?
  activeOrganization   Organization? @relation(fields: [activeOrganizationId], references: [id], onDelete: SetNull)

  @@index([userId])
  @@index([activeOrganizationId])
}

model User {
  id            String    @id @default(cuid())
  createdAt     DateTime  @default(now())
  updatedAt     DateTime  @updatedAt
  name          String?
  email         String    @unique
  emailVerified Boolean?  @default(false)
  image         String?
  accounts      Account[]
  sessions      Session[]

  // additional fields
  completedOnboardingAt   DateTime? // questions about the user. e.g. their role
  onboardingAnswers       Json?
  lastLogin               DateTime?
  utms                    Json?
  errorMessages           Json? // eg. user set incorrect AI API key
  announcementDismissedAt DateTime?
  dismissedHints          String[]  @default([])

  // survey answers (extracted from onboardingAnswers for easier querying)
  surveyFeatures     String[] // multiple choice: features user is interested in
  surveyRole         String? // single choice: user's role. Now using `EmailAccount.role` instead
  surveyGoal         String? // single choice: what user wants to achieve
  surveyCompanySize  Int? // numeric company size: 1 (solo), 5 (2-10), 50 (11-100), 500 (101-1000), 1000 (1000+)
  surveySource       String? // single choice: how user heard about Inbox Zero
  surveyImprovements String? // open text: what user wants to improve

  // settings
  aiProvider    String?
  aiModel       String?
  aiApiKey      String?
  webhookSecret String?

  // referral system
  referralCode String? @unique // User's own referral code

  // premium can be shared among multiple users
  premiumId      String?
  premium        Premium? @relation(name: "userPremium", fields: [premiumId], references: [id])
  // only admin users can manage premium
  premiumAdminId String?
  premiumAdmin   Premium? @relation(fields: [premiumAdminId], references: [id])

  apiKeys ApiKey[]

  emailAccounts EmailAccount[]

  // Referral relationships
  referralsMade    Referral[] @relation("ReferrerUser")
  referralReceived Referral?  @relation("ReferredUser")
  scimProviders    ScimProvider[]
}

enum ApiKeyScope {
  STATS_READ
  RULES_READ
  RULES_WRITE
  SETTINGS_READ
  SETTINGS_WRITE
  ASSISTANT_CHAT
}

// Migrating over to the new settings model. Currently most settings are in the User model, but will be moved to this model in the future.
model EmailAccount {
  id        String   @id @default(cuid())
  email     String   @unique
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name  String? // Name associated with the Google account
  image String? // Profile image URL from the Google account

  about                          String?
  writingStyle                   String?
  signature                      String? // User's email signature from provider or manually set
  includeReferralSignature       Boolean   @default(false)
  watchEmailsExpirationDate      DateTime?
  watchEmailsSubscriptionId      String? // For Outlook subscription ID
  watchEmailsSubscriptionHistory Json? // Historical Outlook subscription IDs: [{ subscriptionId, createdAt, replacedAt }]
  lastSyncedHistoryId            String?
  behaviorProfile                Json?
  personaAnalysis                Json? // ai analysis of the user's persona
  role                           String? // the role confirmed by the user - previously `User.surveyRole`
  timezone                       String? // User's timezone (IANA tz database format, e.g., "America/Los_Angeles", "Asia/Jerusalem") - handles DST automatically
  calendarBookingLink            String? // User's calendar booking link

  statsEmailFrequency       Frequency            @default(WEEKLY)
  summaryEmailFrequency     Frequency            @default(WEEKLY)
  lastSummaryEmailAt        DateTime?
  coldEmailBlocker          ColdEmailSetting? // @deprecated
  coldEmailDigest           Boolean              @default(false) // @deprecated
  coldEmailPrompt           String? // @deprecated
  rulesPrompt               String? // @deprecated
  autoCategorizeSenders     Boolean              @default(false)
  multiRuleSelectionEnabled Boolean              @default(false)
  sensitiveDataPolicy       String?
  draftReplyConfidence      DraftReplyConfidence @default(ALL_EMAILS)
  draftCleanupDays          Int?                 @default(14)
  allowHiddenAiDraftLinks   Boolean              @default(false)
  rulesRevision             Int                  @default(0)

  meetingBriefingsEnabled       Boolean @default(false)
  meetingBriefingsMinutesBefore Int     @default(240) // 4 hours in minutes
  meetingBriefsSendEmail        Boolean @default(true)

  filingEnabled               Boolean @default(false)
  filingPrompt                String?
  filingConfirmationSendEmail Boolean @default(true)

  followUpAwaitingReplyDays Float?
  followUpNeedsReplyDays    Float?
  followUpAutoDraftEnabled  Boolean @default(true)

  digestSendEmail     Boolean   @default(true)
  digestSchedule      Schedule?
  learnedWritingStyle String?

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

  labels           Label[]
  rules            Rule[]
  executedRules    ExecutedRule[]
  newsletters      Newsletter[]
  coldEmails       ColdEmail[] // @deprecated - kept for backward compatibility during migration
  groups           Group[]
  categories       Category[]
  threadTrackers   ThreadTracker[]
  cleanupJobs      CleanupJob[]
  cleanupThreads   CleanupThread[]
  emailMessages    EmailMessage[]
  emailTokens      EmailToken[]
  knowledge        Knowledge[]
  replyMemories    ReplyMemory[]
  chats            Chat[]
  chatMemories     ChatMemory[]
  digests          Digest[]
  scheduledActions ScheduledAction[]
  responseTimes    ResponseTime[]
  actions          Action[]

  members                Member[]
  invitations            Invitation[]
  ssoproviders           SsoProvider[]
  calendarConnections    CalendarConnection[]
  bookingLinks           BookingLink[]
  bookings               Booking[]
  mcpConnections         McpConnection[]
  meetingBriefings       MeetingBriefing[]
  driveConnections       DriveConnection[]
  messagingChannels      MessagingChannel[]
  documentFilings        DocumentFiling[]
  filingFolders          FilingFolder[]
  apiKeys                ApiKey[]
  classificationFeedback ClassificationFeedback[]

  @@index([userId])
  @@index([lastSummaryEmailAt])
  @@index([watchEmailsSubscriptionId])
  @@index([watchEmailsSubscriptionHistory(ops: JsonbPathOps)], type: Gin)
}

model Organization {
  id           String         @id @default(cuid())
  name         String
  slug         String         @unique
  logo         String?
  metadata     Json?
  createdAt    DateTime       @default(now())
  updatedAt    DateTime       @updatedAt
  members      Member[]
  SsoProvider  SsoProvider[]
  ScimProvider ScimProvider[]
  sessions     Session[]
  invitations  Invitation[]
}

model Member {
  id                     String       @id @default(cuid())
  organizationId         String
  emailAccountId         String
  role                   String       @default("member") // "admin" | "member"
  allowOrgAdminAnalytics Boolean      @default(false)
  createdAt              DateTime     @default(now())
  updatedAt              DateTime     @updatedAt
  organization           Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  emailAccount           EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([organizationId, emailAccountId])
  @@unique([emailAccountId])
}

model Invitation {
  id             String       @id @default(cuid())
  organizationId String
  organization   Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  email          String
  role           String?
  status         String
  expiresAt      DateTime
  inviterId      String
  inviter        EmailAccount @relation(fields: [inviterId], references: [id], onDelete: Cascade)

  @@index([organizationId])
  @@index([inviterId])
  @@map("invitation")
}

model Verification {
  id         String   @id @default(cuid())
  identifier String
  value      String
  expiresAt  DateTime
  createdAt  DateTime @default(now())
  updatedAt  DateTime @default(now()) @updatedAt

  @@map("verification")
}

model SsoProvider {
  id             String        @id @default(cuid())
  issuer         String
  oidcConfig     String?
  samlConfig     String?
  emailAccountId String?
  emailAccount   EmailAccount? @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  providerId     String        @unique
  organizationId String?
  organization   Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  domain         String

  @@index([emailAccountId])
  @@index([organizationId])
  @@map("ssoProvider")
}

model ScimProvider {
  id             String        @id @default(cuid())
  providerId     String        @unique
  scimToken      String        @unique
  organizationId String?
  organization   Organization? @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  userId         String?
  user           User?         @relation(fields: [userId], references: [id], onDelete: Cascade)
  createdAt      DateTime      @default(now())
  updatedAt      DateTime      @updatedAt

  @@index([organizationId])
  @@index([userId])
  @@map("scimProvider")
}

model Digest {
  id             String       @id @default(cuid())
  createdAt      DateTime     @default(now())
  updatedAt      DateTime     @updatedAt
  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  items          DigestItem[]
  sentAt         DateTime?
  status         DigestStatus @default(PENDING)

  @@index([emailAccountId])
}

model DigestItem {
  id          String          @id @default(cuid())
  createdAt   DateTime        @default(now())
  updatedAt   DateTime        @updatedAt
  messageId   String
  threadId    String
  content     String          @db.Text
  digestId    String
  digest      Digest          @relation(fields: [digestId], references: [id], onDelete: Cascade)
  actionId    String?
  action      ExecutedAction? @relation(fields: [actionId], references: [id], onDelete: Cascade)
  coldEmailId String? // @deprecated
  coldEmail   ColdEmail?      @relation(fields: [coldEmailId], references: [id]) // @deprecated

  @@unique([digestId, threadId, messageId])
  @@index([actionId])
  @@index([coldEmailId]) // @deprecated
}

model Schedule {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  intervalDays Int? // Total interval in days
  occurrences  Int? // Number of times within the interval

  // Bit mask for days of week (0b0000000 to 0b1111111)
  // Each bit represents a day (Sunday to Saturday)
  // e.g., 0b1000001 means Sunday and Saturday
  daysOfWeek Int? // 0-127 (2^7 - 1)

  // Time of day stored as DateTime with canonical date (1970-01-01)
  // Only the time portion is used, but DateTime preserves timezone info
  // Example: "1970-01-01T09:30:00Z", "1970-01-01T14:15:00Z"
  timeOfDay DateTime?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  lastOccurrenceAt DateTime?
  nextOccurrenceAt DateTime?

  @@unique([emailAccountId])
}

model Premium {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  users  User[] @relation(name: "userPremium")
  admins User[]

  pendingInvites String[]

  // lemon squeezy
  lemonSqueezyRenewsAt           DateTime?
  lemonSqueezyCustomerId         Int?
  lemonSqueezySubscriptionId     Int?
  lemonSqueezySubscriptionItemId Int?
  lemonSqueezyOrderId            Int? // lifetime purchase is an order and not a subscription
  lemonSqueezyProductId          Int?
  lemonSqueezyVariantId          Int?
  lemonLicenseKey                String?
  lemonLicenseInstanceId         String?
  lemonSubscriptionStatus        String?

  // stripe
  stripeCustomerId              String?   @unique
  stripeSubscriptionId          String?   @unique
  stripeSubscriptionItemId      String?   @unique
  stripePriceId                 String?
  stripeProductId               String?
  stripeSubscriptionStatus      String? // The current status from Stripe (e.g., 'active', 'trialing', 'past_due', 'canceled', 'unpaid').
  stripeCancelAtPeriodEnd       Boolean? // If true, the subscription is set to cancel automatically at the end of the current billing period, rather than renew.
  stripeRenewsAt                DateTime? // Timestamp for when the current billing period ends and the subscription attempts renewal (if not canceling). Derived from `current_period_end`.
  stripeTrialEnd                DateTime? // Timestamp for when the free trial period ends (if applicable). Important for managing trial-to-paid transitions.
  stripeTrialConvertedAt        DateTime? // Timestamp for when a Stripe trial converted from `trialing` to `active`. Historical data.
  stripeCancellationInitiatedAt DateTime? // Timestamp for when the user actually initiated cancellation (clicked cancel). Distinct from `stripeCanceledAt`, which is when Stripe processes the scheduled cancellation at period end.
  stripeCanceledAt              DateTime? // Timestamp for when the subscription was definitively marked as canceled in Stripe (might be immediate or after period end). Historical data.
  stripeEndedAt                 DateTime? // Timestamp for when the subscription ended permanently for any reason (cancellation, final payment failure). Historical data.
  stripeAiOverageLastInvoiceId  String?
  stripeAiOverageLastPeriodEnd  DateTime?
  stripeAiOverageLastUnits      Int?

  // apple app store
  appleOriginalTransactionId       String?   @unique
  appleLatestTransactionId         String?   @unique
  appleProductId                   String?
  appleSubscriptionGroupIdentifier String?
  appleAppAccountToken             String?
  appleSubscriptionStatus          String? // The current App Store status (e.g. 'ACTIVE', 'EXPIRED', 'BILLING_GRACE_PERIOD', 'REVOKED').
  appleEnvironment                 String? // App Store environment (e.g. 'Sandbox' or 'Production').
  applePurchaseDate                DateTime?
  appleExpiresAt                   DateTime?
  appleRevokedAt                   DateTime?

  // manually granted by admin
  adminGrantExpiresAt DateTime?
  adminGrantTier      PremiumTier?

  tier PremiumTier?

  emailAccountsAccess Int?

  // unsubscribe/ai credits
  // if `unsubscribeMonth` not set to this month, set to current month
  // reset `unsubscribeCredits` each time month is changed
  unsubscribeMonth   Int? // 1-12
  unsubscribeCredits Int?
  aiMonth            Int? // 1-12
  aiCredits          Int?

  // Payment history
  payments Payment[]

  @@index([pendingInvites])
}

// not in use as it's only used for passwordless login
model VerificationToken {
  id         String   @id @default(cuid())
  createdAt  DateTime @default(now())
  updatedAt  DateTime @updatedAt
  identifier String
  token      String   @unique
  expires    DateTime

  @@unique([identifier, token])
}

model Label {
  id           String   @id @default(cuid())
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt
  gmailLabelId String
  name         String
  description  String? // used in prompts
  enabled      Boolean  @default(true)

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([gmailLabelId, emailAccountId])
  @@unique([name, emailAccountId])
  @@index([emailAccountId])
}

model Rule {
  id                String             @id @default(cuid())
  createdAt         DateTime           @default(now())
  updatedAt         DateTime           @updatedAt
  name              String
  actions           Action[]
  attachmentSources AttachmentSource[]
  enabled           Boolean            @default(true)
  automate          Boolean            @default(true) // @deprecated - No longer used. All rules are now automated. Kept for historical data only.
  runOnThreads      Boolean            @default(false) // if disabled, only runs on individual emails

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  executedRules          ExecutedRule[]
  classificationFeedback ClassificationFeedback[]

  // conditions: ai, group, static, category
  conditionalOperator LogicalOperator @default(AND)

  // ai conditions
  instructions String?

  // group condition
  groupId String? @unique
  group   Group?  @relation(fields: [groupId], references: [id], onDelete: Cascade)

  // static condition
  // automatically apply this rule if it matches a filter. supports regex
  from    String?
  to      String?
  subject String?
  body    String?

  // category condition
  // only apply to (or do not apply to) senders in these categories
  categoryFilterType CategoryFilterType? // deprecated
  categoryFilters    Category[] // deprecated

  systemType SystemType?

  promptText String? // natural language for this rule for prompt file. prompt file is combination of these fields

  history RuleHistory[]

  @@unique([id, emailAccountId])
  @@unique([name, emailAccountId])
  @@unique([emailAccountId, systemType])
  @@index([emailAccountId, enabled])
}

model Action {
  id                             String            @id @default(cuid())
  createdAt                      DateTime          @default(now())
  updatedAt                      DateTime          @updatedAt
  type                           ActionType
  ruleId                         String
  emailAccountId                 String
  rule                           Rule              @relation(fields: [ruleId, emailAccountId], references: [id, emailAccountId], onDelete: Cascade)
  emailAccount                   EmailAccount      @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  messagingChannelId             String?
  // Keep the channel owner separate so channel deletes can null the relation
  // without nulling Action.emailAccountId.
  messagingChannelEmailAccountId String?
  messagingChannel               MessagingChannel? @relation(fields: [messagingChannelId, messagingChannelEmailAccountId], references: [id, emailAccountId], onDelete: SetNull)

  label             String? // labelName - labelId is the source of truth, and we use it when set
  labelId           String? // Stable ID: Label ID (Gmail) or Category ID (Outlook)
  subject           String?
  content           String?
  to                String?
  cc                String?
  bcc               String?
  url               String?
  folderName        String?
  folderId          String?
  delayInMinutes    Int?
  staticAttachments Json? // AttachmentSourceInput[] — explicit files to attach for email actions

  @@index([ruleId])
  @@index([emailAccountId])
  @@index([messagingChannelId, messagingChannelEmailAccountId])
}

model RuleHistory {
  id          String   @id @default(cuid())
  createdAt   DateTime @default(now())
  ruleId      String
  rule        Rule     @relation(fields: [ruleId], references: [id], onDelete: Cascade)
  version     Int
  triggerType String // "ai_update" (AI), "manual_update" (user), "ai_creation" (AI), "manual_creation" (user), "system_creation" (system), "system_update" (system)
  promptText  String? // The prompt text that generated this version

  name                String
  instructions        String?
  enabled             Boolean
  automate            Boolean
  runOnThreads        Boolean
  conditionalOperator String
  from                String?
  to                  String?
  subject             String?
  body                String?
  categoryFilterType  String? // deprecated
  systemType          String?

  actions         Json
  categoryFilters Json? // deprecated

  @@unique([ruleId, version])
  @@index([ruleId, createdAt])
}

// Rule/Action models represent the rules and actions that the AI can take.
// ExecutedRule/ExecutedAction models represent the rules/actions that have been planned or executed by the AI.
model ExecutedRule {
  id            String             @id @default(cuid())
  createdAt     DateTime           @default(now())
  updatedAt     DateTime           @updatedAt
  threadId      String
  messageId     String
  status        ExecutedRuleStatus
  automated     Boolean
  reason        String?
  matchMetadata Json? // Stores structured match information (e.g., learned patterns, match types)

  // may be null if the rule was deleted
  ruleId String?
  rule   Rule?   @relation(fields: [ruleId], references: [id])

  // storing user here in case rule was deleted
  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  actionItems      ExecutedAction[]
  scheduledActions ScheduledAction[]

  @@index([emailAccountId, threadId, messageId, ruleId])
  @@index([emailAccountId, messageId])
  @@index([emailAccountId, status, createdAt])
  @@index([emailAccountId, createdAt])
  @@index([emailAccountId, id])
  @@index([ruleId])
}

model ExecutedAction {
  id                     String                  @id @default(cuid())
  createdAt              DateTime                @default(now())
  updatedAt              DateTime                @updatedAt
  type                   ActionType
  executedRuleId         String
  executedRule           ExecutedRule            @relation(fields: [executedRuleId], references: [id], onDelete: Cascade)
  messagingChannelId     String?
  messagingChannel       MessagingChannel?       @relation(fields: [messagingChannelId], references: [id], onDelete: SetNull)
  messagingMessageId     String?
  messagingMessageSentAt DateTime?
  messagingMessageStatus MessagingMessageStatus?

  // optional extra fields to be used with the action
  label               String?
  labelId             String? // Stable ID: Label ID (Gmail) or Category ID (Outlook)
  subject             String?
  content             String?
  to                  String?
  cc                  String?
  bcc                 String?
  url                 String?
  folderName          String?
  folderId            String?
  staticAttachments   Json? // AttachmentSourceInput[] — explicit files to attach for email actions
  selectedAttachments Json? // SelectedAttachment[] — AI-selected files for this specific execution

  // additional fields as a result of the action
  draftId              String? // Gmail draft ID created by DRAFT_EMAIL action
  draftStatus          DraftEmailStatus? // Last observed/inferred draft lifecycle status; provider state may be stale or ambiguous.
  wasDraftSent         Boolean? // @deprecated Use draftStatus. Kept for historical compatibility only.
  draftModelProvider   String?
  draftModelName       String?
  draftPipelineVersion Int?
  draftContextMetadata Json?
  draftSendLog         DraftSendLog? // Exists when an outbound reply was compared against this draft
  digestItems          DigestItem[] // Relation to digest items created by this action
  scheduledAction      ScheduledAction? // Reverse relation for delayed actions

  @@index([executedRuleId])
  @@index([messagingChannelId, createdAt])
}

model ScheduledAction {
  id               String                @id @default(cuid())
  createdAt        DateTime              @default(now())
  updatedAt        DateTime              @updatedAt
  executedRuleId   String
  actionType       ActionType
  messageId        String
  threadId         String
  scheduledFor     DateTime
  emailAccountId   String
  status           ScheduledActionStatus @default(PENDING)
  schedulingStatus SchedulingStatus      @default(PENDING)

  label               String?
  labelId             String? // Stable ID: Label ID (Gmail) or Category ID (Outlook)
  subject             String?
  content             String?
  to                  String?
  cc                  String?
  bcc                 String?
  url                 String?
  folderName          String?
  folderId            String?
  scheduledId         String?
  staticAttachments   Json? // AttachmentSourceInput[] — explicit files to attach for email actions
  selectedAttachments Json? // SelectedAttachment[] — AI-selected files for this specific execution

  executedAt       DateTime?
  executedActionId String?   @unique

  executedRule   ExecutedRule    @relation(fields: [executedRuleId], references: [id], onDelete: Cascade)
  executedAction ExecutedAction? @relation(fields: [executedActionId], references: [id], onDelete: Cascade)
  emailAccount   EmailAccount    @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([executedRuleId])
  @@index([status, scheduledFor])
  @@index([emailAccountId, messageId])
}

// Notes:
// In the past groups stood on their own. Now they are attached to a rule.
// A group without a rule does not do anything anymore. I may delete all detached groups in the future, and then make rule required
// "Prompt" is no longer in use. It was used to generate the group, but now it's based on the rule the group is attached to.
// "Name" is no longer in use although still required.
// If we really wanted we could remove Group and just have a relation between Rule and GroupItem, but leaving as is for now.
model Group {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  name      String
  prompt    String?

  items GroupItem[]
  rule  Rule?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([name, emailAccountId])
  @@index([emailAccountId])
}

model GroupItem {
  id        String        @id @default(cuid())
  createdAt DateTime      @default(now())
  updatedAt DateTime      @updatedAt
  groupId   String?
  group     Group?        @relation(fields: [groupId], references: [id], onDelete: Cascade)
  type      GroupItemType
  value     String // eg "@gmail.com", "matt@gmail.com", "Receipt from"
  exclude   Boolean       @default(false) // Whether this pattern should be excluded rather than included

  // Optional context for why/how this pattern was learned. 
  reason    String?
  threadId  String?
  messageId String?
  source    GroupItemSource? // provides value for UI/audit.

  @@unique([groupId, type, value])
}

model ClassificationFeedback {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())

  sender    String // normalized email address (lowercase)
  eventType ClassificationFeedbackEventType

  ruleId         String
  rule           Rule         @relation(fields: [ruleId], references: [id], onDelete: Cascade)
  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  threadId  String
  messageId String

  @@unique([emailAccountId, sender, ruleId, messageId, eventType])
  @@index([ruleId])
}

model Category {
  id          String   @id @default(cuid())
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
  name        String
  description String?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  emailSenders Newsletter[]
  rules        Rule[]

  @@unique([name, emailAccountId])
  @@index([emailAccountId])
}

// Represents a sender (`email`) that a user can unsubscribe from,
// or that our AI can mark as a cold email.
// `Newsletter` is a bad name for this. Will rename this model in the future.
model Newsletter {
  id        String            @id @default(cuid())
  createdAt DateTime          @default(now())
  updatedAt DateTime          @updatedAt
  email     String
  name      String?
  status    NewsletterStatus?

  // For learned patterns for rules
  patternAnalyzed Boolean   @default(false)
  lastAnalyzedAt  DateTime?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  categoryId String?
  category   Category? @relation(fields: [categoryId], references: [id])

  @@unique([email, emailAccountId])
  @@index([emailAccountId, status])
  @@index([categoryId])
}

// @deprecated - ColdEmail data is being migrated to GroupItem (learned patterns).
// This model is kept for backward compatibility during the migration period.
// Once all users have run the migration, this model can be deleted.
model ColdEmail {
  id        String           @id @default(cuid())
  createdAt DateTime         @default(now())
  updatedAt DateTime         @updatedAt
  fromEmail String
  messageId String?
  threadId  String?
  status    ColdEmailStatus?
  reason    String?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  digestItems DigestItem[]

  @@unique([emailAccountId, fromEmail])
  @@index([emailAccountId, status])
  @@index([emailAccountId, createdAt])
}

model EmailMessage {
  id              String   @id @default(cuid())
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  threadId        String
  messageId       String
  date            DateTime // date of the email
  from            String
  fromName        String? // sender's display name
  fromDomain      String
  to              String
  unsubscribeLink String?
  read            Boolean
  sent            Boolean
  draft           Boolean
  inbox           Boolean

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([emailAccountId, threadId, messageId])
  @@index([emailAccountId, threadId])
  @@index([emailAccountId, date])
  @@index([emailAccountId, from])
  @@index([emailAccountId, fromName])
}

model ResponseTime {
  id                String   @id @default(cuid())
  createdAt         DateTime @default(now())
  threadId          String
  sentMessageId     String
  receivedMessageId String
  responseTimeMins  Int // Denormalized: (sentAt - receivedAt) in minutes
  receivedAt        DateTime
  sentAt            DateTime

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([emailAccountId, sentMessageId])
  @@index([emailAccountId, sentAt])
}

model ThreadTracker {
  id        String            @id @default(cuid())
  createdAt DateTime          @default(now())
  updatedAt DateTime          @updatedAt
  sentAt    DateTime
  threadId  String
  messageId String
  resolved  Boolean           @default(false)
  type      ThreadTrackerType

  followUpAppliedAt DateTime?
  followUpDraftId   String?
  followUpNotifications Json?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([emailAccountId, threadId, messageId])
  @@index([emailAccountId, resolved])
  @@index([emailAccountId, resolved, sentAt, type])
  @@index([emailAccountId, type, resolved, sentAt])
  @@index([emailAccountId, type, resolved, followUpAppliedAt, sentAt])
}

model CleanupJob {
  id               String      @id @default(cuid())
  createdAt        DateTime    @default(now())
  updatedAt        DateTime    @updatedAt
  action           CleanAction @default(ARCHIVE)
  daysOld          Int         @default(7)
  instructions     String?
  skipReply        Boolean?
  skipStarred      Boolean?
  skipCalendar     Boolean?
  skipReceipt      Boolean?
  skipAttachment   Boolean?
  skipConversation Boolean?

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  threads CleanupThread[]

  @@index([emailAccountId])
}

model CleanupThread {
  id        String     @id @default(cuid())
  createdAt DateTime   @default(now())
  updatedAt DateTime   @updatedAt
  threadId  String
  archived  Boolean // this can also mean "mark as read". depends on CleanupJob.action
  jobId     String
  job       CleanupJob @relation(fields: [jobId], references: [id], onDelete: Cascade)

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([jobId])
  @@index([emailAccountId])
}

model Knowledge {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  title     String
  content   String

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([emailAccountId, title])
}

model ReplyMemory {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  content                String
  kind                   ReplyMemoryKind
  scopeType              ReplyMemoryScopeType
  scopeValue             String
  isLearnedStyleEvidence Boolean              @default(false)

  emailAccountId String
  emailAccount   EmailAccount        @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  sources        ReplyMemorySource[]

  @@unique([emailAccountId, kind, scopeType, scopeValue, content])
  @@index([emailAccountId, updatedAt])
  @@index([emailAccountId, scopeType, scopeValue])
}

model ReplyMemorySource {
  createdAt                     DateTime  @default(now())
  learnedWritingStyleAnalyzedAt DateTime?

  replyMemoryId String
  replyMemory   ReplyMemory @relation(fields: [replyMemoryId], references: [id], onDelete: Cascade)

  draftSendLogId String
  draftSendLog   DraftSendLog @relation(fields: [draftSendLogId], references: [id], onDelete: Cascade)

  @@id([replyMemoryId, draftSendLogId])
  @@index([draftSendLogId])
  @@index([replyMemoryId, learnedWritingStyleAnalyzedAt, createdAt])
}

model ApiKey {
  id         String        @id @default(cuid())
  createdAt  DateTime      @default(now())
  updatedAt  DateTime      @updatedAt
  name       String?
  hashedKey  String        @unique
  isActive   Boolean       @default(true)
  expiresAt  DateTime?
  lastUsedAt DateTime?
  scopes     ApiKeyScope[] @default([])

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

  emailAccountId String?
  emailAccount   EmailAccount? @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([userId, isActive])
  @@index([emailAccountId, isActive])
}

model EmailToken {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  token     String   @unique
  expiresAt DateTime
  // action    EmailTokenAction @default(UNSUBSCRIBE)

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([emailAccountId])
}

model Payment {
  id        String   @id @default(cuid())
  createdAt DateTime // from processor
  updatedAt DateTime // from processor

  // Relation to Premium
  premiumId String?
  premium   Premium? @relation(fields: [premiumId], references: [id], onDelete: SetNull)

  // Payment processor information
  processorType           ProcessorType @default(LEMON_SQUEEZY)
  processorId             String?       @unique // External payment ID from Stripe/Lemon Squeezy
  processorSubscriptionId String? // External subscription ID
  processorCustomerId     String? // External customer ID

  // Core payment information
  amount       Int // Total amount in cents
  currency     String // 3-letter currency code: USD, EUR, etc.
  status       String // paid, failed, refunded, etc.
  tax          Int
  taxInclusive Boolean

  // Refund information
  refunded       Boolean   @default(false)
  refundedAt     DateTime?
  refundedAmount Int? // in cents

  // Metadata
  billingReason String? // initial, renewal, update, etc.

  @@index([premiumId, createdAt])
}

model DraftSendLog {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())

  executedActionId String         @unique
  executedAction   ExecutedAction @relation(fields: [executedActionId], references: [id], onDelete: Cascade)

  sentMessageId           String
  similarityScore         Float // Similarity score (0.0 to 1.0) between original draft and sent message
  bodySimilarityScore     Float?
  bodySimilarityStatus    String?
  similarityMetadata      Json?
  sentText                String?
  replyMemorySentText     String?
  replyMemoryAttemptCount Int                 @default(0)
  replyMemoryProcessedAt  DateTime?
  replyMemorySources      ReplyMemorySource[]

  @@index([executedActionId])
  @@index([replyMemoryProcessedAt, replyMemoryAttemptCount, createdAt])
}

model Chat {
  id        String    @id @default(cuid())
  createdAt DateTime  @default(now())
  updatedAt DateTime  @updatedAt
  deletedAt DateTime?

  name String?

  compactionCount       Int  @default(0)
  lastSeenRulesRevision Int?

  messages       ChatMessage[]
  compactions    ChatCompaction[]
  memories       ChatMemory[]
  emailAccountId String
  emailAccount   EmailAccount     @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([emailAccountId])
  @@index([deletedAt])
}

model ChatMessage {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  role  String
  parts Json
  // attachments Json?

  chatId String
  chat   Chat   @relation(fields: [chatId], references: [id], onDelete: Cascade)

  @@index([chatId])
}

model ChatCompaction {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())

  summary                  String   @db.Text
  messageCount             Int
  compactedBeforeCreatedAt DateTime // boundary: messages with createdAt >= this were NOT compacted

  chatId String
  chat   Chat   @relation(fields: [chatId], references: [id], onDelete: Cascade)

  @@index([chatId])
}

model ChatMemory {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  content String

  chatId         String?
  chat           Chat?        @relation(fields: [chatId], references: [id], onDelete: SetNull)
  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([emailAccountId])
}

model CalendarConnection {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  provider     String // "google" or "microsoft"
  email        String // Google account email (e.g., "elie@gmail.com")
  accessToken  String?
  refreshToken String?
  expiresAt    DateTime?
  isConnected  Boolean   @default(true)

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  calendars Calendar[]

  @@unique([emailAccountId, provider, email]) // Allow multiple Google accounts per user
}

model Calendar {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  calendarId  String // External calendar ID from provider
  name        String
  description String?
  primary     Boolean @default(false)
  isEnabled   Boolean @default(true)
  timezone    String?

  connectionId String
  connection   CalendarConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)
  bookingLinks BookingLink[]

  @@unique([connectionId, calendarId])
}

model BookingLink {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  slug        String  @unique
  title       String
  description String?
  isActive    Boolean @default(true)

  durationMinutes      Int
  slotIntervalMinutes  Int
  locationType         BookingLinkLocationType @default(CUSTOM)
  locationValue        String?
  minimumNoticeMinutes Int                     @default(120)
  maxDaysAhead         Int                     @default(90)
  timezone             String

  emailAccountId        String       @unique // temporary: relax when we support multiple booking links per account
  emailAccount          EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  destinationCalendarId String?
  destinationCalendar   Calendar?    @relation(fields: [destinationCalendarId], references: [id], onDelete: SetNull)

  windows  BookingWindow[]
  bookings Booking[]

  @@index([destinationCalendarId])
}

model BookingWindow {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  weekday      Int
  startMinutes Int
  endMinutes   Int

  bookingLinkId String
  bookingLink   BookingLink @relation(fields: [bookingLinkId], references: [id], onDelete: Cascade)

  @@index([bookingLinkId])
}

model Booking {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  guestName           String
  guestEmail          String
  guestNote           String?
  startTime           DateTime
  endTime             DateTime
  status               BookingStatus @default(PENDING_PROVIDER_EVENT)
  provider             String?
  providerConnectionId String?
  providerCalendarId   String?
  providerEventId      String?
  videoConferenceLink  String?
  cancelTokenHash     String
  cancellationReason  String?
  idempotencyToken    String?

  bookingLinkId  String
  bookingLink    BookingLink  @relation(fields: [bookingLinkId], references: [id], onDelete: Cascade)
  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([bookingLinkId, idempotencyToken])
  @@index([bookingLinkId, startTime, endTime])
  @@index([emailAccountId])
}

model MeetingBriefing {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())

  calendarEventId String // External event ID from Google/Microsoft
  eventTitle      String
  eventStartTime  DateTime
  guestCount      Int
  status          MeetingBriefingStatus

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([emailAccountId, calendarEventId]) // Prevent duplicate briefings
  @@index([emailAccountId])
}

model MessagingChannel {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  provider    MessagingProvider
  isConnected Boolean           @default(true)

  // Provider workspace/account
  teamId         String // Slack workspace ID, Discord server ID, etc.
  teamName       String? // Display name
  providerUserId String? // Slack user ID of the person who authorized the connection
  botUserId      String? // Slack bot user ID (for mention stripping)

  // OAuth credentials (encrypted via prisma-extensions)
  accessToken  String?
  refreshToken String?
  expiresAt    DateTime?

  emailAccountId  String
  emailAccount    EmailAccount     @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  routes          MessagingRoute[]
  automationJobs  AutomationJob[]
  actions         Action[]
  executedActions ExecutedAction[]

  @@unique([emailAccountId, provider, teamId])
  @@unique([id, emailAccountId])
  @@index([emailAccountId])
  @@index([provider, teamId])
}

model MessagingRoute {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Which Inbox Zero feature this route is for.
  purpose MessagingRoutePurpose

  // Whether the provider-native destination is a direct message or channel.
  targetType MessagingRouteTargetType
  // Provider-native destination ID, for example a Slack channel ID.
  targetId   String

  messagingChannelId String
  messagingChannel   MessagingChannel @relation(fields: [messagingChannelId], references: [id], onDelete: Cascade)

  @@unique([messagingChannelId, purpose])
}

model AutomationJob {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name           String
  enabled        Boolean @default(true)
  prompt         String? @db.Text
  cronExpression String // UTC cron expression

  nextRunAt DateTime

  messagingChannelId String
  messagingChannel   MessagingChannel @relation(fields: [messagingChannelId, emailAccountId], references: [id, emailAccountId], onDelete: Cascade)

  emailAccountId String

  runs AutomationJobRun[]

  // Current product behavior is one automation job per account.
  // Remove this when we support managing multiple jobs per account.
  @@unique([emailAccountId])
  @@index([emailAccountId, enabled, nextRunAt])
  @@index([enabled, nextRunAt])
  @@index([messagingChannelId, emailAccountId])
}

model AutomationJobRun {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())

  status       AutomationJobRunStatus @default(PENDING)
  scheduledFor DateTime
  processedAt  DateTime?

  outboundMessage   String? @db.Text
  providerMessageId String? // Provider message ID (e.g. Slack `ts`)
  error             String? @db.Text

  automationJobId String
  automationJob   AutomationJob @relation(fields: [automationJobId], references: [id], onDelete: Cascade)

  @@unique([automationJobId, scheduledFor])
  @@index([automationJobId, createdAt])
  @@index([status, createdAt])
}

// Drive connection for document auto-organization (Google Drive or OneDrive/SharePoint)
// One connection per provider. But we can remove unique constraint in the future if we want
model DriveConnection {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  provider     String // "google" or "microsoft"
  email        String // can differ from emailAccount - e.g. connect work Drive to personal email
  accessToken  String?
  refreshToken String?
  expiresAt    DateTime?
  isConnected  Boolean   @default(true)

  emailAccountId String
  emailAccount   EmailAccount @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  documentFilings   DocumentFiling[]
  filingFolders     FilingFolder[]
  attachmentSources AttachmentSource[]

  @@unique([emailAccountId, provider])
  @@index([emailAccountId])
}

model FilingFolder {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  folderId   String
  folderName String
  folderPath String

  driveConnectionId String
  driveConnection   DriveConnection @relation(fields: [driveConnectionId], references: [id], onDelete: Cascade)
  emailAccountId    String
  emailAccount      EmailAccount    @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@unique([emailAccountId, folderId])
  @@index([driveConnectionId])
}

model DocumentFiling {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Source email attachment
  messageId    String
  attachmentId String
  filename     String

  // Result (after filing or prediction)
  folderId   String? // null if AI suggests creating a new folder
  folderPath String
  fileId     String?
  reasoning  String?
  confidence Float?

  status DocumentFilingStatus @default(FILED)

  wasAsked     Boolean   @default(false)
  wasCorrected Boolean   @default(false)
  originalPath String?
  correctedAt  DateTime?

  // Feedback (for preview and corrections)
  feedbackPositive Boolean?
  feedbackAt       DateTime?

  notificationMessageId String?   @unique
  notificationSentAt    DateTime?

  driveConnectionId String
  driveConnection   DriveConnection @relation(fields: [driveConnectionId], references: [id], onDelete: Cascade)
  emailAccountId    String
  emailAccount      EmailAccount    @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)

  @@index([emailAccountId, status])
  @@index([driveConnectionId])
  @@index([messageId])
  @@unique([emailAccountId, messageId, attachmentId])
}

model AttachmentSource {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name       String
  type       AttachmentSourceType
  sourceId   String
  sourcePath String?

  ruleId String
  rule   Rule   @relation(fields: [ruleId], references: [id], onDelete: Cascade)

  driveConnectionId String
  driveConnection   DriveConnection @relation(fields: [driveConnectionId], references: [id], onDelete: Cascade)

  documents AttachmentDocument[]

  @@unique([ruleId, driveConnectionId, type, sourceId])
  @@index([ruleId])
  @@index([driveConnectionId])
}

model AttachmentDocument {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  attachmentSourceId String
  attachmentSource   AttachmentSource @relation(fields: [attachmentSourceId], references: [id], onDelete: Cascade)

  fileId     String
  name       String
  mimeType   String
  modifiedAt DateTime?
  summary    String?   @db.Text
  content    String?   @db.Text
  metadata   Json?
  indexedAt  DateTime?
  error      String?   @db.Text

  @@unique([attachmentSourceId, fileId])
  @@index([attachmentSourceId, modifiedAt])
}

model Referral {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // The user who made the referral (referrer)
  referrerUserId String
  referrerUser   User   @relation(name: "ReferrerUser", fields: [referrerUserId], references: [id], onDelete: Cascade)

  // The user who was referred
  referredUserId String @unique // Each user can only be referred once
  referredUser   User   @relation(name: "ReferredUser", fields: [referredUserId], references: [id], onDelete: Cascade)

  // The referral code used (stored as string)
  referralCodeUsed String

  // Status tracking
  status ReferralStatus @default(PENDING)

  // Reward tracking - using Stripe balance transactions
  rewardGrantedAt            DateTime?
  stripeBalanceTransactionId String? // Store Stripe txn ID for reference
  rewardAmount               Int? // Amount in cents (e.g., 2000 for $20)

  @@index([referrerUserId])
  @@index([status])
}

model McpIntegration {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name String @unique // Lookup key to MCP_INTEGRATIONS config

  // OAuth registration metadata (captured during dynamic registration)
  // These represent what the credentials below were registered for
  registeredServerUrl        String? // Server URL credentials are registered with
  registeredAuthorizationUrl String? // OAuth authorization endpoint used
  registeredTokenUrl         String? // OAuth token endpoint used

  // OAuth client credentials (from dynamic registration - shared across all users)
  oauthClientId     String?
  oauthClientSecret String?

  connections McpConnection[]
}

model McpConnection {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name     String
  isActive Boolean @default(true)

  // Encrypted credentials
  accessToken  String?
  refreshToken String?
  apiKey       String?
  expiresAt    DateTime?

  integrationId  String
  integration    McpIntegration @relation(fields: [integrationId], references: [id], onDelete: Cascade)
  emailAccountId String?
  emailAccount   EmailAccount?  @relation(fields: [emailAccountId], references: [id], onDelete: Cascade)
  tools          McpTool[]

  @@unique([emailAccountId, integrationId])
}

model McpTool {
  id        String   @id @default(cuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  name        String
  description String?
  schema      Json?
  isEnabled   Boolean @default(true)

  connectionId String
  connection   McpConnection @relation(fields: [connectionId], references: [id], onDelete: Cascade)

  @@unique([connectionId, name])
}

enum ActionType {
  ARCHIVE
  LABEL
  REPLY
  SEND_EMAIL
  FORWARD
  DRAFT_EMAIL
  DRAFT_MESSAGING_CHANNEL
  NOTIFY_MESSAGING_CHANNEL
  MARK_SPAM
  CALL_WEBHOOK
  MARK_READ
  STAR
  // TRACK_THREAD // @deprecated - No longer used. We rely on rule SystemType instead to run this.
  DIGEST
  MOVE_FOLDER
  NOTIFY_SENDER // Sends notification from Inbox Zero (not user's email). Only for cold email rules.
  // SUMMARIZE
  // SNOOZE
  // ADD_TO_DO
  // INTEGRATION // for example, add to Notion
}

enum MessagingMessageStatus {
  SENT
  DRAFT_SENT
  DRAFT_EDITED
  DISMISSED
  EXPIRED
  FAILED
}

enum Frequency {
  NEVER
  DAILY
  WEEKLY
  // MONTHLY
  // ANNUALLY
}

enum DraftReplyConfidence {
  ALL_EMAILS
  STANDARD
  HIGH_CONFIDENCE
}

enum DraftEmailStatus {
  PENDING
  LIKELY_SENT
  REPLIED_WITHOUT_DRAFT
  CLEANED_UP_UNUSED
  MISSING_FROM_PROVIDER
}

enum ReplyMemoryKind {
  FACT
  PREFERENCE
  PROCEDURE
}

enum ReplyMemoryScopeType {
  GLOBAL
  SENDER
  DOMAIN
  TOPIC
}

enum NewsletterStatus {
  APPROVED
  UNSUBSCRIBED
  AUTO_ARCHIVED
}

enum ColdEmailStatus {
  AI_LABELED_COLD
  USER_REJECTED_COLD
}

enum DocumentFilingStatus {
  PROCESSING // Filing is currently being processed
  PENDING // Waiting for user input
  FILED // Document is filed (auto or after user confirmation)
  REJECTED // User said skip
  ERROR // Filing failed
  PREVIEW // Preview feedback only (not filed)
}

enum AttachmentSourceType {
  FILE
  FOLDER
}

// @deprecated - No longer used
enum ColdEmailSetting {
  DISABLED
  LIST
  LABEL
  ARCHIVE_AND_LABEL
  ARCHIVE_AND_READ_AND_LABEL
}

enum PremiumTier {
  BASIC_MONTHLY
  BASIC_ANNUALLY
  PRO_MONTHLY
  PRO_ANNUALLY
  STARTER_MONTHLY       @map("BUSINESS_MONTHLY")
  STARTER_ANNUALLY      @map("BUSINESS_ANNUALLY")
  PLUS_MONTHLY
  PLUS_ANNUALLY
  PROFESSIONAL_MONTHLY  @map("BUSINESS_PLUS_MONTHLY")
  PROFESSIONAL_ANNUALLY @map("BUSINESS_PLUS_ANNUALLY")
  COPILOT_MONTHLY
  LIFETIME
}

enum ExecutedRuleStatus {
  APPLIED
  APPLYING
  REJECTED // @deprecated - No longer created. Kept for historical data only.
  PENDING // @deprecated - No longer created. Kept for historical data only.
  SKIPPED
  ERROR
}

enum GroupItemType {
  FROM
  SUBJECT
  BODY
}

enum CategoryFilterType {
  INCLUDE
  EXCLUDE
}

enum LogicalOperator {
  AND
  OR
}

enum ThreadTrackerType {
  AWAITING // We're waiting for their reply
  NEEDS_REPLY // We need to reply to this
  NEEDS_ACTION // We need to do something else
}

enum ProcessorType {
  LEMON_SQUEEZY
  STRIPE
}

enum CleanAction {
  ARCHIVE
  MARK_READ
}

enum SystemType {
  // conversation trackers
  TO_REPLY
  FYI
  AWAITING_REPLY
  ACTIONED
  // cold email blocker
  COLD_EMAIL
  // other labels
  NEWSLETTER
  MARKETING
  CALENDAR
  RECEIPT
  NOTIFICATION
}

enum ReferralStatus {
  PENDING // Referral created, waiting for trial completion
  COMPLETED // Referral completed and reward granted
}

enum DigestStatus {
  PENDING
  PROCESSING
  SENT
  FAILED
}

enum ScheduledActionStatus {
  PENDING
  EXECUTING
  COMPLETED
  FAILED
  CANCELLED
}

enum SchedulingStatus {
  PENDING
  SCHEDULED
  FAILED
}

enum MeetingBriefingStatus {
  PENDING
  SENT
  FAILED
  SKIPPED
}

enum BookingLinkLocationType {
  IN_PERSON
  GOOGLE_MEET
  MICROSOFT_TEAMS
  PHONE
  CUSTOM
}

enum BookingStatus {
  PENDING_PROVIDER_EVENT
  CONFIRMED
  CANCELED
  FAILED
}

enum GroupItemSource {
  AI
  USER
  LABEL_REMOVED
  LABEL_ADDED
}

enum ClassificationFeedbackEventType {
  LABEL_ADDED
  LABEL_REMOVED
}

enum MessagingProvider {
  SLACK
  TEAMS
  TELEGRAM
}

enum MessagingRoutePurpose {
  RULE_NOTIFICATIONS
  SCHEDULED_CHECK_INS
  MEETING_BRIEFS
  DOCUMENT_FILINGS
  DIGESTS
  FOLLOW_UPS
}

enum MessagingRouteTargetType {
  CHANNEL
  DIRECT_MESSAGE
}

enum AutomationJobRunStatus {
  PENDING
  RUNNING
  SENT
  SKIPPED
  FAILED
}
