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

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

// ============================================================================
// AUTH & USERS
// ============================================================================

enum UserRole {
  SUPER_ADMIN
  INFLUENCE_ADMIN
  MAP_ADMIN
  BROADCAST_ADMIN
  CONTENT_ADMIN
  MEDIA_ADMIN
  PAYMENTS_ADMIN
  EVENTS_ADMIN
  SOCIAL_ADMIN
  POLLS_ADMIN
  ANALYTICS_ADMIN
  USER
  TEMP
}

enum UserStatus {
  ACTIVE
  INACTIVE
  SUSPENDED
  EXPIRED
  PENDING_VERIFICATION
  PENDING_APPROVAL
}

enum UserCreatedVia {
  ADMIN
  PUBLIC_SHIFT_SIGNUP
  STANDARD
  SELF_REGISTRATION
  QUICK_JOIN_INVITE
}

model User {
  id            String          @id @default(cuid())
  email         String          @unique
  password      String          // bcrypt hashed
  name          String?
  phone         String?
  pronouns      String?
  role          UserRole        @default(USER)
  roles         Json            @default("[]")    // Array of UserRole strings for multi-role support
  status        UserStatus      @default(ACTIVE)
  permissions   Json?           // Per-app granular permissions
  createdVia    UserCreatedVia  @default(STANDARD)
  expiresAt     DateTime?       // For temp users
  expireDays    Int?
  lastLoginAt   DateTime?
  emailVerified Boolean         @default(false)
  createdAt     DateTime        @default(now())
  updatedAt     DateTime        @updatedAt

  refreshTokens      RefreshToken[]
  campaignsCreated   Campaign[]             @relation("CampaignCreator")
  campaignsReviewed  Campaign[]             @relation("CampaignReviewer")
  campaignEmails     CampaignEmail[]        @relation("CampaignEmailSender")
  responses          RepresentativeResponse[] @relation("ResponseSubmitter")
  responseUpvotes    ResponseUpvote[]
  shiftSignups       ShiftSignup[]
  locationsCreated   Location[]             @relation("LocationCreator")
  locationsUpdated   Location[]             @relation("LocationUpdater")
  addressesCreated   Address[]              @relation("AddressCreator")
  addressesUpdated   Address[]              @relation("AddressUpdater")
  locationEdits      LocationHistory[]      @relation("LocationHistoryUser")
  cutsCreated        Cut[]                  @relation("CutCreator")
  canvassVisits      CanvassVisit[]         @relation("CanvassVisitor")
  canvassSessions    CanvassSession[]       @relation("CanvassSessions")
  trackingSessions   TrackingSession[]      @relation("TrackingSessions")
  templatesCreated         EmailTemplate[]        @relation("TemplatesCreated")
  templatesUpdated         EmailTemplate[]        @relation("TemplatesUpdated")
  templateVersionsCreated  EmailTemplateVersion[] @relation("TemplateVersionsCreated")
  templateTestsSent        EmailTemplateTestLog[] @relation("TemplateTestsSent")

  // Media API relations
  videosUploaded           Video[]                @relation("VideoUploader")
  videosLocked             Video[]                @relation("VideoLocker")
  videoViews               VideoView[]            @relation("VideoViews")
  videoScheduleHistory     VideoScheduleHistory[] @relation("VideoScheduleHistory")
  sessions                 Session[]              @relation("SessionUser")
  comments                 Comment[]              @relation("CommentUser")
  authTokens               AuthToken[]            @relation("AuthTokenUser")
  sessionBansMade          SessionBan[]           @relation("SessionBanner")
  commentModerations       CommentModeration[]    @relation("CommentModerator")
  emailVerificationTokens  EmailVerificationToken[] @relation("EmailVerificationTokens")
  passwordResetTokens      PasswordResetToken[]   @relation("PasswordResetTokens")
  emailChangeTokens        EmailChangeToken[]     @relation("EmailChangeTokens")
  achievements             UserAchievement[]      @relation("UserAchievements")
  stats                    UserStats?             @relation("UserStats")
  finishes                 UserFinish[]           @relation("UserFinishes")
  videoReactions           VideoReaction[]        @relation("VideoReactions")
  highlightCooldowns       HighlightCooldown[]    @relation("HighlightCooldowns")
  dailyActivity            UserDailyActivity[]    @relation("UserDailyActivity")
  chatThreadReadStatus     ChatThreadReadStatus[] @relation("ChatThreadReadStatus")
  moderationWordLists      ModerationWordList[]   @relation("ModerationWordListCreator")
  contentReportsSubmitted  ContentReport[]        @relation("ContentReportUser")
  contentReportsResolved   ContentReport[]        @relation("ContentReportResolver")
  playlists                Playlist[]             @relation("UserPlaylists")
  featuredPlaylists        FeaturedPlaylist[]     @relation("FeaturedPlaylistFeaturer")
  adImpressions            AdImpression[]         @relation("AdImpressionUser")
  adClicks                 AdClick[]              @relation("AdClickUser")
  friendships              Friendship[]           @relation("UserFriendships")
  friends                  Friendship[]           @relation("UserFriends")
  blocks                   UserBlock[]            @relation("UserBlocks")
  blockedBy                UserBlock[]            @relation("UserBlockedBy")
  pokesSent                Poke[]                 @relation("PokesSent")
  pokesReceived            Poke[]                 @relation("PokesReceived")
  recommendationsSent      VideoRecommendation[]  @relation("RecommendationsSent")
  recommendationsReceived  VideoRecommendation[]  @relation("RecommendationsReceived")
  presence                 UserPresence?          @relation("UserPresence")
  galleryImages            UserGalleryImage[]     @relation("UserGalleryImages")
  socialLinks              UserSocialLink[]       @relation("UserSocialLinks")
  privacySettings          PrivacySettings?       @relation("PrivacySettings")
  closeFriends             CloseFriend[]          @relation("CloseFriends")
  closeFriendOf            CloseFriend[]          @relation("CloseFriendOf")
  socialGroupMemberships   SocialGroupMember[]    @relation("SocialGroupMember")
  uploads                  UserUpload[]           @relation("UserUploads")
  uploadReviews            UserUpload[]           @relation("UserUploadReviewer")
  uploadInvites            UploadInvite[]         @relation("UploadInviteCreator")
  tagPreferences           UserTagPreference[]    @relation("UserTagPreferences")
  performerDiscrepancies   PerformerDiscrepancy[] @relation("PerformerDiscrepancyResolver")
  watchPartiesHosted       WatchPartySession[]    @relation("WatchPartyHost")
  watchPartyParticipations WatchPartyParticipant[] @relation("WatchPartyParticipant")
  watchPartyChatMessages   WatchPartyChatMessage[] @relation("WatchPartyChatUser")
  watchPartyReactions      WatchPartyReaction[]   @relation("WatchPartyReactionUser")
  watchPartyInvitesSent    WatchPartyInvite[]     @relation("WatchPartyInviter")
  watchPartyInvitesReceived WatchPartyInvite[]    @relation("WatchPartyInvitee")
  subscriptions            UserSubscription[]     @relation("UserSubscriptions")
  invoices                 Invoice[]              @relation("UserInvoices")
  payments                 Payment[]              @relation("UserPayments")
  paymentAudits            PaymentAuditLog[]      @relation("PaymentAuditUser")
  orders                   Order[]                @relation("UserOrders")
  notifications            Notification[]         @relation("UserNotifications")
  notificationPreferences  NotificationPreferences? @relation("NotificationPreferences")

  // Photo gallery relations
  photosUploaded           Photo[]                @relation("PhotoUploader")
  albumsCreated            PhotoAlbum[]           @relation("AlbumCreator")
  photoComments            PhotoComment[]         @relation("PhotoCommentUser")

  // SMS campaign relations
  smsContactListsCreated   SmsContactList[]       @relation("SmsContactListCreator")
  smsCampaignsCreated      SmsCampaign[]          @relation("SmsCampaignCreator")
  smsTemplatesCreated      SmsMessageTemplate[]   @relation("SmsTemplateCreator")

  // Donation pages
  donationPagesCreated     DonationPage[]         @relation("DonationPageCreator")

  // Meetings (Jitsi)
  meetingsCreated          Meeting[]              @relation("MeetingCreator")

  // People CRM
  contact                  Contact?               @relation("UserContact")

  // Scheduling polls
  schedulingPollsCreated   SchedulingPoll[]        @relation("PollCreator")
  schedulingPollVotes      SchedulingPollVote[]    @relation("PollVoter")
  schedulingPollComments   SchedulingPollComment[] @relation("PollCommenter")

  // Straw polls
  strawPollsCreated          StrawPoll[]            @relation("StrawPollCreator")
  strawPollVotes             StrawPollVote[]        @relation("StrawPollVoter")
  strawPollComments          StrawPollComment[]     @relation("StrawPollCommenter")
  strawPollChallengesSent    StrawPollChallenge[]   @relation("StrawPollChallenger")
  strawPollChallengesReceived StrawPollChallenge[]  @relation("StrawPollChallenged")

  // Participant needs
  participantNeeds         ParticipantNeeds?       @relation("UserParticipantNeeds")

  // Meeting agendas & action items
  agendasCreated           MeetingAgenda[]         @relation("AgendaCreator")
  minutesCreated           MeetingMinutes[]        @relation("MinutesCreator")
  minutesApproved          MeetingMinutes[]        @relation("MinutesApprover")
  actionItemsAssigned      ActionItem[]            @relation("ActionItemAssignee")
  actionItemsCreated       ActionItem[]            @relation("ActionItemCreator")

  // Referral system
  inviteCodesCreated       InviteCode[]            @relation("InviteCodesCreated")
  referralsMade            Referral[]              @relation("ReferralsMade")
  referredBy               Referral?               @relation("ReferredBy")

  // Impact Stories
  impactStoriesCreated     ImpactStory[]           @relation("ImpactStoryCreator")

  // Volunteer Spotlight
  spotlights               VolunteerSpotlight[]    @relation("SpotlightUser")
  spotlightNominations     VolunteerSpotlight[]    @relation("SpotlightNominator")
  spotlightApprovals       VolunteerSpotlight[]    @relation("SpotlightApprover")

  // Team Challenges
  challengesCreated        Challenge[]             @relation("ChallengesCreated")
  challengeTeamsCaptained  ChallengeTeam[]         @relation("ChallengeTeamsCaptained")
  challengeParticipations  ChallengeTeamMember[]   @relation("ChallengeParticipations")

  // Ticketed Events
  ticketedEventsCreated    TicketedEvent[]          @relation("EventCreator")
  ticketsHeld              Ticket[]                 @relation("TicketHolder")
  checkInsMade             CheckIn[]                @relation("CheckInUser")

  // Social Calendar
  calendarLayers           CalendarLayer[]          @relation("CalendarLayerOwner")
  calendarItems            CalendarItem[]           @relation("CalendarItemOwner")
  calendarFeeds            CalendarFeed[]           @relation("CalendarFeedOwner")
  sharedCalendarViewsOwned SharedCalendarView[]     @relation("SharedViewOwner")
  sharedCalendarMemberships SharedCalendarMember[]  @relation("SharedViewMember")
  sharedViewComments       SharedViewComment[]      @relation("SharedViewCommentUser")
  sharedViewReactions      SharedViewReaction[]     @relation("SharedViewReactionUser")
  calendarExportTokens     CalendarExportToken[]    @relation("CalendarExportTokenOwner")

  // Docs access & sharing
  docAccessPoliciesCreated DocAccessPolicy[]         @relation("DocAccessPolicyCreator")
  docShareLinksCreated     DocShareLink[]             @relation("DocShareLinkCreator")
  docWatches               DocWatch[]                 @relation("DocWatcher")

  // Petitions
  petitionsCreated         Petition[]                 @relation("PetitionCreator")
  petitionsReviewed        Petition[]                 @relation("PetitionReviewer")

  @@map("users")
}

model RefreshToken {
  id        String   @id @default(cuid())
  token     String   @unique
  userId    String
  user      User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  expiresAt DateTime
  createdAt DateTime @default(now())

  @@index([userId])
  @@map("refresh_tokens")
}

// ============================================================================
// INFLUENCE — CAMPAIGNS
// ============================================================================

enum CampaignStatus {
  DRAFT
  ACTIVE
  PAUSED
  ARCHIVED
}

enum CampaignModerationStatus {
  PENDING_REVIEW
  APPROVED
  REJECTED
  CHANGES_REQUESTED
}

enum GovernmentLevel {
  FEDERAL
  PROVINCIAL
  MUNICIPAL
  SCHOOL_BOARD
}

model Campaign {
  id                     String           @id @default(cuid())
  slug                   String           @unique
  title                  String
  description            String?          @db.Text
  emailSubject           String
  emailBody              String           @db.Text
  callToAction           String?          @db.Text
  coverPhoto             String?
  coverVideoId           Int?
  status                 CampaignStatus   @default(DRAFT)

  // Feature flags
  allowSmtpEmail         Boolean          @default(true)
  allowMailtoLink        Boolean          @default(true)
  collectUserInfo        Boolean          @default(true)
  showEmailCount         Boolean          @default(true)
  showCallCount          Boolean          @default(true)
  allowEmailEditing      Boolean          @default(false)
  allowCustomRecipients  Boolean          @default(false)
  showResponseWall       Boolean          @default(false)
  highlightCampaign      Boolean          @default(false)

  // Targeting
  targetGovernmentLevels GovernmentLevel[]

  // Creator
  createdByUserId        String?
  createdByUser          User?            @relation("CampaignCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  createdByUserEmail     String?
  createdByUserName      String?

  // User-generated campaign moderation
  isUserGenerated        Boolean                    @default(false)
  moderationStatus       CampaignModerationStatus?
  reviewedByUserId       String?
  reviewedByUser         User?                      @relation("CampaignReviewer", fields: [reviewedByUserId], references: [id], onDelete: SetNull)
  reviewedAt             DateTime?
  rejectionReason        String?                    @db.Text
  moderationNotes        String?                    @db.Text

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

  emails                 CampaignEmail[]
  responses              RepresentativeResponse[]
  customRecipients       CustomRecipient[]
  calls                  Call[]
  smsCampaigns           SmsCampaign[]          @relation("SmsCampaigns")
  stories                ImpactStory[]          @relation("CampaignStories")
  milestones             CampaignMilestone[]    @relation("CampaignMilestones")
  donationOrders         Order[]                @relation("CampaignDonations")
  petitions              Petition[]             @relation("PetitionLinkedCampaign")

  @@index([moderationStatus])
  @@index([isUserGenerated])
  @@map("campaigns")
}

// ============================================================================
// INFLUENCE — PETITIONS
// ============================================================================

enum PetitionStatus {
  DRAFT
  ACTIVE
  PAUSED
  CLOSED
  ARCHIVED
}

enum PetitionSignatureStatus {
  PENDING_VERIFICATION
  VERIFIED
  UNVERIFIED
  REJECTED
}

model Petition {
  id                        String                    @id @default(cuid())
  slug                      String                    @unique
  title                     String
  description               String?                   @db.Text

  // Goal and progress
  signatureGoal             Int?
  showProgress              Boolean                   @default(true)
  showSignatureCount        Boolean                   @default(true)
  showSignerNames           Boolean                   @default(true)
  signatureCountOffset      Int                       @default(0)

  // Form fields
  requireName               Boolean                   @default(true)
  requireEmail              Boolean                   @default(true)
  requirePostalCode         Boolean                   @default(false)
  requirePhone              Boolean                   @default(false)
  allowComment              Boolean                   @default(true)
  commentLabel              String?

  // Email confirmation
  requireEmailConfirmation  Boolean                   @default(false)
  confirmationEmailSubject  String?
  confirmationEmailBody     String?                   @db.Text

  // Presentation
  coverPhoto                String?
  coverVideoId              Int?
  callToAction              String?                   @db.Text
  thankYouMessage           String?                   @db.Text
  highlightPetition         Boolean                   @default(false)

  // Linked campaign (post-sign CTA)
  linkedCampaignId          String?
  linkedCampaign            Campaign?                 @relation("PetitionLinkedCampaign", fields: [linkedCampaignId], references: [id], onDelete: SetNull)

  // Status and moderation
  status                    PetitionStatus            @default(DRAFT)
  isUserGenerated           Boolean                   @default(false)
  moderationStatus          CampaignModerationStatus?
  rejectionReason           String?                   @db.Text
  moderationNotes           String?                   @db.Text

  // Creator
  createdByUserId           String?
  createdByUser             User?                     @relation("PetitionCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  createdByUserEmail        String?
  createdByUserName         String?

  // Reviewer
  reviewedByUserId          String?
  reviewedByUser            User?                     @relation("PetitionReviewer", fields: [reviewedByUserId], references: [id], onDelete: SetNull)
  reviewedAt                DateTime?

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

  signatures                PetitionSignature[]

  @@index([status])
  @@index([isUserGenerated])
  @@index([highlightPetition])
  @@index([linkedCampaignId])
  @@map("petitions")
}

model PetitionSignature {
  id                String                   @id @default(cuid())
  petitionId        String
  petition          Petition                 @relation(fields: [petitionId], references: [id], onDelete: Cascade)

  // Signer info
  signerName        String?
  signerEmail       String?
  signerPostalCode  String?
  signerPhone       String?
  signerComment     String?                  @db.Text
  isAnonymous       Boolean                  @default(false)
  displayName       String?

  // Status and verification
  status            PetitionSignatureStatus  @default(UNVERIFIED)
  verificationToken String?                  @unique
  verificationSentAt DateTime?
  verifiedAt        DateTime?

  // CRM link
  contactId         String?
  contact           Contact?                 @relation("PetitionSignatureContact", fields: [contactId], references: [id], onDelete: SetNull)

  // Geo (from IP via MaxMind)
  signerIp          String?
  geoCountry        String?
  geoRegion         String?
  geoCity           String?

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

  @@unique([petitionId, signerEmail])
  @@index([petitionId])
  @@index([signerEmail])
  @@index([petitionId, status])
  @@index([contactId])
  @@map("petition_signatures")
}

// ============================================================================
// INFLUENCE — REPRESENTATIVES
// ============================================================================

model Representative {
  id                    String    @id @default(cuid())
  postalCode            String
  name                  String?
  email                 String?
  districtName          String?
  electedOffice         String?
  partyName             String?
  representativeSetName String?
  url                   String?
  photoUrl              String?
  offices               Json?     // JSON array of office contact info
  cachedAt              DateTime  @default(now())

  @@index([postalCode])
  @@map("representatives")
}

// ============================================================================
// INFLUENCE — CAMPAIGN EMAILS
// ============================================================================

enum EmailMethod {
  SMTP
  MAILTO
}

enum CampaignEmailStatus {
  QUEUED
  SENT
  FAILED
  CLICKED
  USER_INFO_CAPTURED
}

model CampaignEmail {
  id               String              @id @default(cuid())
  campaignId       String
  campaign         Campaign            @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  campaignSlug     String

  // Sender info
  userId           String?
  user             User?               @relation("CampaignEmailSender", fields: [userId], references: [id], onDelete: SetNull)
  userEmail        String?
  userName         String?
  userPostalCode   String?

  // Recipient info
  recipientEmail   String
  recipientName    String?
  recipientTitle   String?
  recipientLevel   GovernmentLevel?

  emailMethod      EmailMethod
  subject          String
  message          String              @db.Text
  status           CampaignEmailStatus @default(SENT)
  senderIp         String?
  sentAt           DateTime            @default(now())

  @@index([campaignId])
  @@index([campaignSlug])
  @@index([userPostalCode])
  @@index([sentAt])
  @@map("campaign_emails")
}

// ============================================================================
// INFLUENCE — REPRESENTATIVE RESPONSES (Response Wall)
// ============================================================================

enum ResponseType {
  EMAIL
  LETTER
  PHONE_CALL
  MEETING
  SOCIAL_MEDIA
  OTHER
}

enum ResponseStatus {
  PENDING
  APPROVED
  REJECTED
}

model RepresentativeResponse {
  id                   String           @id @default(cuid())
  campaignId           String
  campaign             Campaign         @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  campaignSlug         String

  representativeName   String
  representativeTitle  String?
  representativeLevel  GovernmentLevel
  representativeEmail  String?

  responseType         ResponseType
  responseText         String           @db.Text
  userComment          String?          @db.Text
  screenshotUrl        String?

  // Submitter info
  submittedByUserId    String?
  submittedByUser      User?            @relation("ResponseSubmitter", fields: [submittedByUserId], references: [id], onDelete: SetNull)
  submittedByName      String?
  submittedByEmail     String?
  isAnonymous          Boolean          @default(false)

  // Moderation
  status               ResponseStatus   @default(PENDING)

  // Verification
  isVerified           Boolean          @default(false)
  verificationToken    String?
  verificationSentAt   DateTime?
  verifiedAt           DateTime?
  verifiedBy           String?

  upvoteCount          Int              @default(0)
  submittedIp          String?
  createdAt            DateTime         @default(now())
  updatedAt            DateTime         @updatedAt

  upvotes              ResponseUpvote[]

  @@index([campaignId])
  @@index([campaignSlug])
  @@index([representativeName])
  @@map("representative_responses")
}

model ResponseUpvote {
  id         String  @id @default(cuid())
  responseId String
  response   RepresentativeResponse @relation(fields: [responseId], references: [id], onDelete: Cascade)
  userId     String?
  user       User?   @relation(fields: [userId], references: [id], onDelete: SetNull)
  userEmail  String?
  upvotedIp  String?

  @@unique([responseId, userId])
  @@unique([responseId, upvotedIp])
  @@map("response_upvotes")
}

// ============================================================================
// INFLUENCE — CUSTOM RECIPIENTS
// ============================================================================

model CustomRecipient {
  id                    String   @id @default(cuid())
  campaignId            String
  campaign              Campaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  campaignSlug          String
  recipientName         String
  recipientEmail        String
  recipientTitle        String?
  recipientOrganization String?
  notes                 String?  @db.Text
  isActive              Boolean  @default(true)
  createdAt             DateTime @default(now())
  updatedAt             DateTime @updatedAt

  @@index([campaignId])
  @@map("custom_recipients")
}

// ============================================================================
// INFLUENCE — POSTAL CODE CACHE
// ============================================================================

model PostalCodeCache {
  id          String    @id @default(cuid())
  postalCode  String    @unique
  city        String?
  province    String?
  centroidLat Decimal?  @db.Decimal(10, 8)
  centroidLng Decimal?  @db.Decimal(11, 8)
  lastUpdated DateTime  @default(now())

  @@map("postal_code_cache")
}

// ============================================================================
// INFLUENCE — EMAIL LOG & VERIFICATION
// ============================================================================

model EmailLog {
  id             String   @id @default(cuid())
  recipientEmail String
  senderName     String
  senderEmail    String
  subject        String?
  message        String?  @db.Text
  postalCode     String?
  status         String   @default("sent") // sent, failed, previewed
  senderIp       String?
  sentAt         DateTime @default(now())

  @@map("email_logs")
}

model EmailVerification {
  id               String   @id @default(cuid())
  token            String   @unique
  email            String
  tempCampaignData String?  @db.Text // JSON
  createdAt        DateTime @default(now())
  expiresAt        DateTime
  used             Boolean  @default(false)

  @@map("email_verifications")
}

// ============================================================================
// INFLUENCE — CALL TRACKING
// ============================================================================

model Call {
  id                  String    @id @default(cuid())
  representativeName  String
  representativeTitle String?
  phoneNumber         String
  officeType          String?
  callerName          String?
  callerEmail         String?
  postalCode          String?
  campaignId          String?
  campaign            Campaign? @relation(fields: [campaignId], references: [id], onDelete: SetNull)
  campaignSlug        String?
  callerIp            String?
  calledAt            DateTime  @default(now())

  @@index([campaignId])
  @@map("calls")
}

// ============================================================================
// MAP — LOCATIONS
// ============================================================================

enum SupportLevel {
  LEVEL_1 @map("1")
  LEVEL_2 @map("2")
  LEVEL_3 @map("3")
  LEVEL_4 @map("4")
}

enum GeocodeProvider {
  GOOGLE
  MAPBOX
  NOMINATIM
  PHOTON
  LOCATIONIQ
  ARCGIS
  UNKNOWN
}

enum BuildingType {
  SINGLE_FAMILY
  MULTI_UNIT
  MIXED_USE
  COMMERCIAL
}

model Location {
  id                  String           @id @default(cuid())
  latitude            Decimal          @db.Decimal(10, 8)  // Required (was nullable)
  longitude           Decimal          @db.Decimal(11, 8)  // Required (was nullable)

  // Building-level data
  address             String           // Base street address (no unit number)
  postalCode          String?
  province            String?
  federalDistrict     String?
  buildingUse         Int?             // NAR BU_USE: 1=Residential, 2=Partial, 3=Non-Residential, 4=Unknown

  // NAR + building metadata
  locGuid             String?          @unique
  buildingType        BuildingType     @default(SINGLE_FAMILY)
  totalUnits          Int              @default(1)
  buildingNotes       String?          @db.Text  // Access codes, manager contact, etc.

  // Geocoding
  geocodeConfidence   Int?             // 0-100
  geocodeProvider     GeocodeProvider?

  // Audit
  createdByUserId     String?
  createdByUser       User?            @relation("LocationCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  updatedByUserId     String?
  updatedByUser       User?            @relation("LocationUpdater", fields: [updatedByUserId], references: [id], onDelete: SetNull)

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

  // Relations
  addresses           Address[]
  history             LocationHistory[]

  @@index([latitude, longitude])
  @@index([latitude])
  @@index([longitude])
  @@index([postalCode])
  @@map("locations")
}

model Address {
  id                  String           @id @default(cuid())
  locationId          String
  location            Location         @relation(fields: [locationId], references: [id], onDelete: Cascade)

  // Unit identification
  unitNumber          String?
  addrGuid            String?          @unique  // NAR ADDR_GUID

  // Occupant/contact info (per-unit)
  firstName           String?
  lastName            String?
  email               String?
  phone               String?

  // Canvassing data (per-unit)
  supportLevel        SupportLevel?
  sign                Boolean          @default(false)
  signSize            String?
  notes               String?          @db.Text

  // Audit
  createdByUserId     String?
  createdByUser       User?            @relation("AddressCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  updatedByUserId     String?
  updatedByUser       User?            @relation("AddressUpdater", fields: [updatedByUserId], references: [id], onDelete: SetNull)
  createdAt           DateTime         @default(now())
  updatedAt           DateTime         @updatedAt

  // Relations
  canvassVisits       CanvassVisit[]
  contactAddresses    ContactAddress[]

  @@index([locationId])
  @@index([locationId, id])
  @@index([locationId, unitNumber])
  @@map("addresses")
}

// ============================================================================
// MAP — LOCATION HISTORY
// ============================================================================

enum LocationHistoryAction {
  CREATED
  UPDATED
  GEOCODED
  BULK_GEOCODED
  MOVED_ON_MAP
  IMPORTED_CSV
  IMPORTED_NAR
}

model LocationHistory {
  id         String                 @id @default(cuid())
  locationId String
  location   Location               @relation(fields: [locationId], references: [id], onDelete: Cascade)
  userId     String?
  user       User?                  @relation("LocationHistoryUser", fields: [userId], references: [id], onDelete: SetNull)
  action     LocationHistoryAction
  field      String?                // Which field changed
  oldValue   String?                @db.Text
  newValue   String?                @db.Text
  metadata   Json?                  // Provider, confidence, etc.
  createdAt  DateTime               @default(now())

  @@index([locationId])
  @@index([userId])
  @@index([createdAt])
  @@map("location_history")
}

// ============================================================================
// MAP — SHIFTS
// ============================================================================

enum ShiftStatus {
  OPEN
  FULL
  CANCELLED
}

enum RecurrenceFrequency {
  DAILY
  WEEKLY
  MONTHLY
}

model Shift {
  id               String      @id @default(cuid())
  title            String
  description      String?     @db.Text
  date             DateTime    @db.Date
  startTime        String      // HH:MM format
  endTime          String      // HH:MM format
  location         String?
  maxVolunteers    Int
  currentVolunteers Int        @default(0)
  status           ShiftStatus @default(OPEN)
  isPublic         Boolean     @default(false)
  cutId            String?
  cut              Cut?        @relation(fields: [cutId], references: [id], onDelete: SetNull)

  // Repeating shift series
  seriesId         String?
  series           ShiftSeries? @relation(fields: [seriesId], references: [id], onDelete: SetNull)
  isException      Boolean     @default(false)

  // Gancio event sync
  gancioEventId    Int?

  // Video briefing meeting
  meetingId        String?     @unique
  meeting          Meeting?    @relation("ShiftMeeting", fields: [meetingId], references: [id], onDelete: SetNull)

  createdBy        String?
  createdAt        DateTime    @default(now())
  updatedAt        DateTime    @updatedAt

  signups          ShiftSignup[]
  canvassVisits    CanvassVisit[]
  canvassSessions  CanvassSession[]

  // Scheduling poll conversion
  convertedFromPoll SchedulingPoll? @relation("PollConvertedShift")

  // Meeting agenda
  agenda           MeetingAgenda?  @relation("ShiftAgenda")

  @@index([cutId])
  @@index([seriesId])
  @@map("shifts")
}

enum SignupStatus {
  CONFIRMED
  CANCELLED
}

enum SignupSource {
  AUTHENTICATED
  PUBLIC
  ADMIN
  POLL_CONVERSION
}

model ShiftSignup {
  id           String       @id @default(cuid())
  shiftId      String
  shift        Shift        @relation(fields: [shiftId], references: [id], onDelete: Cascade)
  shiftTitle   String?
  userId       String?
  user         User?        @relation(fields: [userId], references: [id], onDelete: SetNull)
  userEmail    String
  userName     String?
  userPhone    String?
  signupDate   DateTime     @default(now())
  status       SignupStatus @default(CONFIRMED)
  signupSource SignupSource @default(AUTHENTICATED)

  @@unique([shiftId, userEmail])
  @@index([shiftId])
  @@map("shift_signups")
}

// ============================================================================
// MAP — SHIFT SERIES (Repeating Shifts)
// ============================================================================

model ShiftSeries {
  id                   String              @id @default(cuid())
  title                String
  description          String?             @db.Text
  startTime            String              // HH:MM format
  endTime              String              // HH:MM format
  location             String?
  maxVolunteers        Int
  isPublic             Boolean             @default(false)
  cutId                String?
  cut                  Cut?                @relation(fields: [cutId], references: [id], onDelete: SetNull)

  // Recurrence rules
  frequency            RecurrenceFrequency
  daysOfWeek           Json?               // Array of day numbers: [1,3,5] for Mon/Wed/Fri
  startDate            DateTime            @db.Date
  endDate              DateTime?           @db.Date

  // Metadata
  createdBy            String?
  createdAt            DateTime            @default(now())
  updatedAt            DateTime            @updatedAt

  // Relations
  shifts               Shift[]

  @@index([cutId])
  @@map("shift_series")
}

// ============================================================================
// MAP — CUTS (Geographic Polygon Overlays)
// ============================================================================

enum CutCategory {
  CUSTOM
  WARD
  NEIGHBORHOOD
  DISTRICT
}

model Cut {
  id                   String       @id @default(cuid())
  name                 String
  description          String?      @db.Text
  color                String       @default("#3388ff")
  opacity              Decimal      @default(0.3) @db.Decimal(3, 2)
  category             CutCategory?
  isPublic             Boolean      @default(false)
  isOfficial           Boolean      @default(false)
  geojson              String       @db.Text  // GeoJSON polygon data
  bounds               String?      @db.Text  // Bounding box JSON
  showLocations        Boolean      @default(true)
  exportEnabled        Boolean      @default(true)
  assignedTo           String?
  filterSettings       Json?        // JSON filter configuration
  lastCanvassed        DateTime?
  completionPercentage Int          @default(0)

  createdByUserId      String?
  createdByUser        User?        @relation("CutCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  createdAt            DateTime     @default(now())
  updatedAt            DateTime     @updatedAt

  shifts               Shift[]
  shiftSeries          ShiftSeries[]
  canvassSessions      CanvassSession[]

  @@map("cuts")
}

// ============================================================================
// MAP — SETTINGS
// ============================================================================

model MapSettings {
  id               String   @id @default(cuid())
  latitude         Decimal? @db.Decimal(10, 8)
  longitude        Decimal? @db.Decimal(11, 8)
  zoom             Int?
  walkSheetTitle   String?
  walkSheetSubtitle String?
  walkSheetFooter  String?  @db.Text
  qrCode1Url       String?
  qrCode1Label     String?
  qrCode2Url       String?
  qrCode2Label     String?
  qrCode3Url       String?
  qrCode3Label     String?
  publicMapEnabled       Boolean  @default(false)
  publicShowLocations    Boolean  @default(true)
  publicShowSupportLevels Boolean @default(true)
  publicShowCuts         Boolean  @default(true)
  publicShowEvents       Boolean  @default(true)
  publicShowAddresses    Boolean  @default(true)
  publicShowSignInfo     Boolean  @default(true)
  createdBy        String?
  createdAt        DateTime @default(now())
  updatedAt        DateTime @updatedAt

  @@map("map_settings")
}

// ============================================================================
// SITE SETTINGS (Singleton — branding, theme, feature toggles)
// ============================================================================

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

  // Organization
  organizationName       String   @default("Changemaker Lite")
  organizationShortName  String   @default("CML")
  organizationLogoUrl    String?
  organizationFaviconUrl String?

  // Admin theme
  adminColorPrimary      String   @default("#9d4edd")
  adminColorBgBase       String   @default("#1a1025")

  // Public theme
  publicColorPrimary     String   @default("#3498db")
  publicColorBgBase      String   @default("#0d1b2a")
  publicColorBgContainer String   @default("#1b2838")
  publicHeaderGradient   String   @default("linear-gradient(135deg, #005a9c 0%, #007acc 100%)")

  // Text
  footerText             String   @default("Powered by Changemaker Lite")
  loginSubtitle          String   @default("Admin")
  homepageTagline        String?  @map("homepage_tagline")

  // Email branding
  emailFromName          String   @default("Changemaker Lite")

  // SMTP configuration (overrides env vars when set; empty/0 = use env fallback)
  smtpHost               String   @default("")
  smtpPort               Int      @default(0)
  smtpUser               String   @default("")
  smtpPass               String   @default("")
  smtpFromAddress        String   @default("")
  smtpActiveProvider     String   @default("mailhog")  // "mailhog" | "production"
  emailTestMode          Boolean  @default(true)
  testEmailRecipient     String   @default("")

  // Registration settings
  enablePublicRegistration    Boolean  @default(true)
  enableEmailVerification     Boolean  @default(true)
  autoApproveVerifiedUsers    Boolean  @default(true)

  // Feature toggles
  enableInfluence        Boolean  @default(true)
  enableMap              Boolean  @default(false)
  enableNewsletter       Boolean  @default(true)
  enableLandingPages     Boolean  @default(true)
  enableMediaFeatures    Boolean  @default(true) @map("enable_media_features")
  enablePayments         Boolean  @default(false)
  enableGalleryAds       Boolean  @default(false) @map("enable_gallery_ads")
  enableChat             Boolean  @default(false) @map("enable_chat")
  enableEvents           Boolean  @default(false) @map("enable_events")
  enableDocsComments     Boolean  @default(false) @map("enable_docs_comments")
  enableSms              Boolean  @default(false) @map("enable_sms")
  enablePeople           Boolean  @default(false) @map("enable_people")
  enableSocial           Boolean  @default(false) @map("enable_social")
  enableMeet             Boolean  @default(false) @map("enable_meet")
  enableMeetingPlanner   Boolean  @default(false) @map("enable_meeting_planner")
  enableTicketedEvents   Boolean  @default(false) @map("enable_ticketed_events")
  enableSocialCalendar   Boolean  @default(false) @map("enable_social_calendar")
  enablePolls             Boolean  @default(false) @map("enable_polls")
  enableAnalytics         Boolean  @default(false) @map("enable_analytics")
  analyticsRetentionDays  Int      @default(90)    @map("analytics_retention_days")
  analyticsGeoEnabled     Boolean  @default(true)  @map("analytics_geo_enabled")
  trackAuthenticatedUsers Boolean  @default(true)  @map("track_authenticated_users")
  enablePetitions        Boolean  @default(false) @map("enable_petitions")
  enableDocsCollaboration Boolean @default(false) @map("enable_docs_collaboration")
  requireEventApproval   Boolean  @default(true)  @map("require_event_approval")
  autoSyncPeopleToMap    Boolean  @default(false) @map("auto_sync_people_to_map")

  // SMS connection config (overrides env vars when non-empty)
  smsTermuxApiUrl        String   @default("") @map("sms_termux_api_url")
  smsTermuxApiKey        String   @default("") @map("sms_termux_api_key")       // Encrypted at rest
  smsTailscaleApiKey     String   @default("") @map("sms_tailscale_api_key")    // Encrypted at rest
  smsTailscaleTailnet    String   @default("") @map("sms_tailscale_tailnet")
  smsTailscaleDeviceId   String   @default("") @map("sms_tailscale_device_id")
  smsTailscaleDeviceName String   @default("") @map("sms_tailscale_device_name")

  // Gitea Docs Comments (overrides env vars when set; empty = use env fallback)
  giteaApiToken          String   @default("")   // Encrypted at rest — Personal Access Token
  giteaCommentsRepoOwner String   @default("")
  giteaCommentsRepoName  String   @default("docs-comments")
  giteaOauthClientId     String   @default("")
  giteaOauthClientSecret String   @default("")   // Encrypted at rest
  giteaSetupComplete     Boolean  @default(false) @map("gitea_setup_complete")

  // Notification settings
  notifyAdminShiftSignup        Boolean  @default(true)
  notifyAdminResponseSubmitted  Boolean  @default(true)
  notifyAdminSignRequested      Boolean  @default(true)
  notifyAdminPetitionMilestone  Boolean  @default(false) @map("notify_admin_petition_milestone")
  notifyAdminShiftCancellation  Boolean  @default(true)
  notifyVolunteerSessionSummary Boolean  @default(true)
  notifyVolunteerCancellation   Boolean  @default(true)
  notifyVolunteerShiftReminder  Boolean  @default(true)
  notifyVolunteerShiftThankYou  Boolean  @default(true)

  // SMS notification settings
  smsShiftReminders         Boolean  @default(false) @map("sms_shift_reminders")
  smsShiftReminderHours     Int      @default(24) @map("sms_shift_reminder_hours")
  smsShiftSignupConfirm     Boolean  @default(false) @map("sms_shift_signup_confirm")
  smsVolunteerWelcome       Boolean  @default(false) @map("sms_volunteer_welcome")

  // Re-engagement settings
  notifyVolunteerReengagement   Boolean  @default(false) @map("notify_volunteer_reengagement")
  reengagementInactiveDays      Int      @default(30) @map("reengagement_inactive_days")
  reengagementCooldownDays      Int      @default(30) @map("reengagement_cooldown_days")

  // Auto-upgrade settings
  enableAutoUpgrade         Boolean  @default(false) @map("enable_auto_upgrade")
  autoUpgradeSchedule       String   @default("daily-3am") @map("auto_upgrade_schedule")
  autoUpgradePullServices   Boolean  @default(false) @map("auto_upgrade_pull_services")
  notifyAdminAutoUpgrade    Boolean  @default(true)  @map("notify_admin_auto_upgrade")
  useRegistryForUpgrade     Boolean  @default(false) @map("use_registry_for_upgrade")
  giteaRegistryUrl          String   @default("gitea.bnkops.com/admin") @map("gitea_registry_url")

  // Navigation configuration (JSON: { items: NavItem[] })
  navConfig              Json?    @map("nav_config")

  // User Provisioning (centralized user management across services)
  enableUserProvisioning    Boolean  @default(false) @map("enable_user_provisioning")
  provisionGitea            Boolean  @default(false) @map("provision_gitea")
  provisionGiteaTiming      String   @default("lazy") @map("provision_gitea_timing")       // 'lazy' | 'eager'
  provisionVaultwarden      Boolean  @default(false) @map("provision_vaultwarden")
  provisionVaultwardenTiming String  @default("lazy") @map("provision_vaultwarden_timing")  // 'lazy' | 'eager'
  provisionListmonk         Boolean  @default(true)  @map("provision_listmonk")
  provisionListmonkTiming   String   @default("eager") @map("provision_listmonk_timing")   // 'lazy' | 'eager'

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

  @@map("site_settings")
}

// ============================================================================
// EMAIL TEMPLATES
// ============================================================================

enum EmailTemplateCategory {
  INFLUENCE
  MAP
  SYSTEM
  PAYMENT
}

enum EmailTemplateVariableType {
  TEXT
  VIDEO
}

model EmailTemplate {
  id                String                  @id @default(cuid())
  key               String                  @unique // e.g., "campaign-email"
  name              String                  // Display name
  description       String?                 @db.Text
  category          EmailTemplateCategory   // INFLUENCE | MAP | SYSTEM
  subjectLine       String                  // Template with {{VAR}} support
  htmlContent       String                  @db.Text
  textContent       String                  @db.Text
  isSystem          Boolean                 @default(false)  // Prevent deletion
  isActive          Boolean                 @default(true)

  variables         EmailTemplateVariable[]
  versions          EmailTemplateVersion[]
  testLogs          EmailTemplateTestLog[]

  createdByUserId   String
  createdBy         User                    @relation("TemplatesCreated", fields: [createdByUserId], references: [id])
  updatedByUserId   String?
  updatedBy         User?                   @relation("TemplatesUpdated", fields: [updatedByUserId], references: [id])
  createdAt         DateTime                @default(now())
  updatedAt         DateTime                @updatedAt

  @@index([category])
  @@index([isActive])
  @@map("email_templates")
}

model EmailTemplateVariable {
  id            String                      @id @default(cuid())
  templateId    String
  template      EmailTemplate               @relation(fields: [templateId], references: [id], onDelete: Cascade)

  key           String         // e.g., "USER_NAME"
  label         String         // e.g., "User Name"
  description   String?        @db.Text // e.g., "Name of the user sending the email"
  type          EmailTemplateVariableType   @default(TEXT)  // TEXT | VIDEO
  videoId       Int?           // Optional FK to videos (not enforced, separate Media API DB)
  isRequired    Boolean        @default(true)
  isConditional Boolean        @default(false)  // Used in {{#if}} blocks
  sampleValue   String?        @db.Text // e.g., "John Doe"
  sortOrder     Int            @default(0)

  @@unique([templateId, key])
  @@index([templateId])
  @@index([type])
  @@map("email_template_variables")
}

model EmailTemplateVersion {
  id              String         @id @default(cuid())
  templateId      String
  template        EmailTemplate  @relation(fields: [templateId], references: [id], onDelete: Cascade)

  versionNumber   Int            // Auto-increment per template
  subjectLine     String
  htmlContent     String         @db.Text
  textContent     String         @db.Text
  changeNotes     String?        @db.Text

  createdByUserId String
  createdBy       User           @relation("TemplateVersionsCreated", fields: [createdByUserId], references: [id])
  createdAt       DateTime       @default(now())

  @@unique([templateId, versionNumber])
  @@index([templateId, createdAt(sort: Desc)])
  @@map("email_template_versions")
}

model EmailTemplateTestLog {
  id               String         @id @default(cuid())
  templateId       String
  template         EmailTemplate  @relation(fields: [templateId], references: [id], onDelete: Cascade)

  recipientEmail   String
  testData         Json           // Sample variable values used
  success          Boolean
  errorMessage     String?        @db.Text
  messageId        String?        // Nodemailer message ID

  sentByUserId     String
  sentBy           User           @relation("TemplateTestsSent", fields: [sentByUserId], references: [id])
  sentAt           DateTime       @default(now())

  @@index([templateId, sentAt(sort: Desc)])
  @@map("email_template_test_logs")
}

// ============================================================================
// LANDING PAGES
// ============================================================================

enum EditorMode {
  VISUAL
  CODE
}

enum MkdocsExportMode {
  THEMED      // extends main.html, content block only
  STANDALONE  // full HTML document, no Jinja2 inheritance
}

model LandingPage {
  id           String   @id @default(cuid())
  slug         String   @unique
  title        String
  description  String?  @db.Text
  blocks       Json     // JSON from GrapesJS editor
  htmlOutput   String?  @db.Text
  cssOutput    String?  @db.Text
  editorMode   EditorMode @default(VISUAL)
  mkdocsPath       String?          // Path in mkdocs/overrides/
  mkdocsStubPath   String?          // Path to .md stub in mkdocs/docs/
  mkdocsExportMode MkdocsExportMode @default(THEMED)
  mkdocsHideNav    Boolean          @default(true)
  mkdocsHideToc    Boolean          @default(true)
  mkdocsSkipExport Boolean          @default(false)
  published        Boolean          @default(false)
  listed           Boolean          @default(false)
  seoTitle     String?
  seoDescription String? @db.Text
  seoImage     String?
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@map("landing_pages")
}

model PageBlock {
  id           String   @id @default(cuid())
  type         String   // hero, text, image, cta, features, testimonials, form
  label        String
  schema       Json     // Block configuration schema
  defaults     Json     // Default values
  thumbnail    String?
  category     String?
  sortOrder    Int      @default(0)
  createdAt    DateTime @default(now())
  updatedAt    DateTime @updatedAt

  @@map("page_blocks")
}

// ============================================================================
// MAP — CANVASSING
// ============================================================================

enum VisitOutcome {
  NOT_HOME
  REFUSED
  MOVED
  ALREADY_VOTED
  SPOKE_WITH
  LEFT_LITERATURE
  COME_BACK_LATER
}

enum CanvassSessionStatus {
  ACTIVE
  COMPLETED
  ABANDONED
}

model CanvassSession {
  id             String               @id @default(cuid())
  userId         String
  user           User                 @relation("CanvassSessions", fields: [userId], references: [id], onDelete: Cascade)
  cutId          String
  cut            Cut                  @relation(fields: [cutId], references: [id], onDelete: Cascade)
  shiftId        String?
  shift          Shift?               @relation(fields: [shiftId], references: [id], onDelete: SetNull)
  status         CanvassSessionStatus @default(ACTIVE)
  startedAt      DateTime             @default(now())
  endedAt        DateTime?
  startLatitude  Decimal?             @db.Decimal(10, 8)
  startLongitude Decimal?             @db.Decimal(11, 8)

  visits           CanvassVisit[]
  trackingSession  TrackingSession?

  @@index([userId])
  @@index([cutId])
  @@index([shiftId])
  @@map("canvass_sessions")
}

model CanvassVisit {
  id              String         @id @default(cuid())
  addressId       String         // Changed from locationId
  address         Address        @relation(fields: [addressId], references: [id], onDelete: Cascade)
  userId          String
  user            User           @relation("CanvassVisitor", fields: [userId], references: [id], onDelete: Cascade)
  shiftId         String?
  shift           Shift?         @relation(fields: [shiftId], references: [id], onDelete: SetNull)
  sessionId       String?
  session         CanvassSession? @relation(fields: [sessionId], references: [id], onDelete: SetNull)
  outcome         VisitOutcome
  supportLevel    SupportLevel?
  signRequested   Boolean        @default(false)
  signSize        String?
  notes           String?        @db.Text
  durationSeconds Int?
  visitedAt       DateTime       @default(now())

  @@index([addressId])  // Changed from locationId
  @@index([addressId, visitedAt(sort: Desc)])  // For distinct + orderBy queries
  @@index([userId])
  @@index([shiftId])
  @@index([sessionId])
  @@index([visitedAt])
  @@map("canvass_visits")
}

// ============================================================================
// MAP — GPS TRACKING
// ============================================================================

enum TrackPointEvent {
  LOCATION_ADDED
  VISIT_RECORDED
  SESSION_STARTED
  SESSION_ENDED
}

model TrackingSession {
  id                String          @id @default(cuid())
  userId            String
  user              User            @relation("TrackingSessions", fields: [userId], references: [id], onDelete: Cascade)
  canvassSessionId  String?         @unique
  canvassSession    CanvassSession? @relation(fields: [canvassSessionId], references: [id], onDelete: SetNull)
  startedAt         DateTime        @default(now())
  endedAt           DateTime?
  isActive          Boolean         @default(true)
  totalPoints       Int             @default(0)
  totalDistanceM    Float           @default(0)
  lastLatitude      Decimal?        @db.Decimal(10, 8)
  lastLongitude     Decimal?        @db.Decimal(11, 8)
  lastRecordedAt    DateTime?
  trackPoints       TrackPoint[]

  @@index([userId])
  @@index([isActive])
  @@index([isActive, lastRecordedAt])
  @@map("tracking_sessions")
}

model TrackPoint {
  id                String           @id @default(cuid())
  trackingSessionId String
  trackingSession   TrackingSession  @relation(fields: [trackingSessionId], references: [id], onDelete: Cascade)
  latitude          Decimal          @db.Decimal(10, 8)
  longitude         Decimal          @db.Decimal(11, 8)
  accuracy          Float?
  recordedAt        DateTime         @default(now())
  eventType         TrackPointEvent?

  @@index([trackingSessionId, recordedAt])
  @@index([recordedAt])
  @@map("track_points")
}
// Enums
enum DirectoryType {
  studios
  gifs
  private
  inbox
  curated
  playback
  compilations
  videos
  highlights
}

enum ResourceCategory {
  gpu_ai
  gpu_encode
  cpu
}

enum JobStatus {
  pending
  queued
  running
  completed
  failed
  cancelled
}

enum ReactionType {
  like
  love
  laugh
  wow
  sad
  angry
}

enum WordFilterLevel {
  low
  medium
  high
  custom
}

enum ReportType {
  inappropriate
  spam
  copyright
  illegal
  false_info
  other
}

enum ReportStatus {
  pending
  reviewed
  actioned
  dismissed
}

enum FriendshipStatus {
  pending
  accepted
  declined
}

enum SocialPlatform {
  twitter
  instagram
  onlyfans
  fansly
  reddit
  discord
  tiktok
  youtube
  twitch
  snapchat
  linktree
  custom
}

enum UserUploadStatus {
  pending
  approved
  rejected
}

enum UploadInviteStatus {
  active
  inactive
  expired
}

enum DigestStatus {
  pending
  scene_detection
  extracting
  analyzing
  face_detection
  transcribing
  segmenting
  synthesizing
  completed
  failed
  cancelled
}

enum ClipType {
  hook
  intro
  action
  climax
  highlight
}

enum ClipStatus {
  pending
  processing
  completed
  failed
}

enum ClipSource {
  machine
  manual
}

enum CaptionPosition {
  bottom
  top
}

enum CaptionSize {
  small
  medium
  large
}

enum SegmentType {
  scene
  tag_change
  vocal
  fixed_interval
}

enum VocalCategory {
  dialogue
  dirty_talk
  climax
  interview
  moan
}

enum SuggestedTagStatus {
  pending
  approved
  rejected
  mapped
}

enum WatchPartyStatus {
  active
  completed
  cancelled
}

enum PipelineStatus {
  draft
  active
  paused
  completed
  failed
}

enum PipelineStepStatus {
  pending
  running
  completed
  failed
  skipped
}

enum SubscriptionStatus {
  none
  active
  grace_period
  delinquent
  lifetime
  cancelled
}

enum InvoiceStatus {
  pending
  paid
  failed
  refunded
}

enum PaymentStatus {
  pending
  succeeded
  failed
  refunded
}

enum PaymentMethod {
  card
  bank_transfer
  crypto
  stripe
}

enum ProductType {
  DIGITAL
  EVENT
  DONATION
}

enum OrderStatus {
  PENDING
  COMPLETED
  FAILED
  REFUNDED
  DISPUTED
}

enum NotificationType {
  friend_request
  friend_accepted
  poke
  comment
  upload_approved
  upload_rejected
  achievement
  system
  group_call
  impact_story
  referral_completed
  challenge_update
  shared_view_invite
  shared_view_accepted
  calendar_event_invite
  // Operational notification types
  shift_signup_confirmed
  shift_reminder
  shift_cancelled
  canvass_session_summary
  reengagement
  // Straw poll notification types
  poll_closed
  poll_results_available
  poll_challenge
}

// ============================================================================
// CORE VIDEO LIBRARY
// ============================================================================

model Video {
  id                     Int       @id @default(autoincrement())
  path                   String    @unique
  filename               String
  producer               String?
  creator                String?
  title                  String?
  durationSeconds        Int?      @map("duration_seconds")
  quality                String?
  orientation            String?
  hasAudio               Boolean?  @default(true) @map("has_audio")
  fileSize               BigInt?   @map("file_size")
  fileHash               String?   @map("file_hash")
  width                  Int?
  height                 Int?
  lastValidated          DateTime? @map("last_validated")
  isValid                Boolean?  @default(true) @map("is_valid")
  thumbnailPath          String?   @map("thumbnail_path")
  createdAt              DateTime  @default(now()) @map("created_at")
  tags                   Json?
  directoryType          DirectoryType? @map("directory_type")
  // Historical engagement stats
  publicViewCount        Int?      @map("public_view_count")
  publicUpvoteCount      Int?      @map("public_upvote_count")
  publicCommentCount     Int?      @map("public_comment_count")
  publicCompletionCount  Int?      @map("public_completion_count")
  publicTotalWatchTime   Int?      @map("public_total_watch_time")
  movedFromPublicAt      DateTime? @map("moved_from_public_at")
  // Name standardization tracking
  originalFilename       String?   @map("original_filename")
  originalPath           String?   @map("original_path")
  standardizedAt         DateTime? @map("standardized_at")

  // Publishing system (replaces PublicMedia)
  isPublished            Boolean   @default(false) @map("is_published")
  publishedAt            DateTime? @map("published_at")
  category               String?   // videos|curated|compilations|playback|highlights
  isShort                Boolean   @default(false) @map("is_short")

  // Moderation system
  isLocked               Boolean   @default(false) @map("is_locked")
  lockedAt               DateTime? @map("locked_at")
  lockedById             String?   @map("locked_by_id")

  // Engagement counters
  viewCount              Int       @default(0) @map("view_count")
  upvoteCount            Int       @default(0) @map("upvote_count")
  commentCount           Int       @default(0) @map("comment_count")
  finishCount            Int       @default(0) @map("finish_count")
  totalWatchTime         Int       @default(0) @map("total_watch_time")

  // Scheduled publishing
  scheduledPublishAt     DateTime? @map("scheduled_publish_at")
  scheduledUnpublishAt   DateTime? @map("scheduled_unpublish_at")

  // Enhanced analytics
  uniqueViewers          Int       @default(0) @map("unique_viewers")
  totalWatchTimeSeconds  Int       @default(0) @map("total_watch_time_seconds")
  averageWatchTimeSeconds Decimal  @default(0) @map("average_watch_time_seconds") @db.Decimal(10, 2)
  completionRate         Decimal   @default(0) @map("completion_rate") @db.Decimal(5, 2)

  // Content gating
  accessLevel            String    @default("free") @map("access_level") // free|member|premium

  // Ordering
  position               Int?      @default(0)

  // Uploader tracking
  uploaderId             String?   @map("uploader_id")

  // Relations
  uploader               User?     @relation("VideoUploader", fields: [uploaderId], references: [id])
  locker                 User?     @relation("VideoLocker", fields: [lockedById], references: [id])
  upvotes                Upvote[]
  comments               Comment[]
  views                  View[]
  reactions              VideoReaction[]
  finishes               UserFinish[]
  contentReports         ContentReport[]
  playlistVideos         PlaylistVideo[]
  publicMediaTags        PublicMediaTag[]       @relation("PublicMediaTags")
  publicMediaPerformers  PublicMediaPerformer[] @relation("PublicMediaPerformers")
  recommendations        VideoRecommendation[]
  videoDigests           VideoDigest[]
  digestVideoTags        DigestVideoTag[]
  digestSelectedClips    DigestSelectedClip[]
  digestGeneratedScenes  DigestGeneratedScene[]
  digestOutputFolders    DigestOutputFolder[]
  digestCompilations     DigestCompilation[]
  videoSceneCuts         VideoSceneCut[]
  videoTagTimeline       VideoTagTimeline[]
  videoSegments          VideoSegment[]
  videoTags              VideoTag[]
  digestClipTags         DigestClipTag[]
  tagGenerationJobs      TagGenerationJob[]
  videoOcrResults        VideoOcrResult[]
  videoViews             VideoView[]
  videoEvents            VideoEvent[]
  scheduleHistory        VideoScheduleHistory[]

  @@index([orientation], map: "idx_orientation")
  @@index([producer], map: "idx_producer")
  @@index([isValid], map: "idx_is_valid")
  @@index([directoryType], map: "idx_directory_type")
  @@index([durationSeconds, fileSize, width, height], map: "idx_videos_fingerprint")
  @@index([directoryType, isValid, orientation], map: "idx_videos_directory_valid_orientation")
  @@index([isPublished, isLocked], map: "idx_videos_published_locked")
  @@index([category, isPublished], map: "idx_videos_category_published")
  @@index([isShort, isPublished, isLocked], map: "idx_videos_short_published")
  @@index([uploaderId], map: "idx_videos_uploader")
  @@map("videos")
}

model Compilation {
  id              Int       @id @default(autoincrement())
  filename        String
  path            String?
  durationSeconds Int?      @map("duration_seconds")
  videoIds        Json?     @map("video_ids")
  settings        Json?
  createdAt       DateTime  @default(now()) @map("created_at")

  @@map("compilations")
}

model Job {
  id               Int              @id @default(autoincrement())
  type             String
  status           JobStatus?       @default(pending)
  progress         Int?             @default(0)
  log              String?
  params           Json?
  startedAt        DateTime?        @map("started_at")
  completedAt      DateTime?        @map("completed_at")
  createdAt        DateTime         @default(now()) @map("created_at")
  // Queue management
  resourceCategory ResourceCategory? @default(cpu) @map("resource_category")
  vramRequired     Int?             @default(0) @map("vram_required")
  queuePosition    Int?             @map("queue_position")
  waitingReason    String?          @map("waiting_reason")
  priority         Int?             @default(5)
  // Pipeline integration
  pipelineId       Int?             @map("pipeline_id")
  pipelineStepId   Int?             @map("pipeline_step_id")

  // Relations
  pipeline         Pipeline?        @relation(fields: [pipelineId], references: [id])
  pipelineStep     PipelineStep?    @relation(fields: [pipelineStepId], references: [id])
  videoDigest      VideoDigest?
  tagGenerationJobs TagGenerationJob[]

  @@index([status, priority, createdAt], map: "idx_jobs_queue")
  @@index([resourceCategory, status], map: "idx_jobs_resource")
  @@index([pipelineId], map: "idx_jobs_pipeline")
  @@map("jobs")
}

// ============================================================================
// PUBLIC GALLERY (DEPRECATED - Consolidated into Video model)
// ============================================================================

// NOTE: PublicMedia model has been removed - all functionality consolidated into Video model
// with isPublished, category, isLocked, and engagement counter fields.
// Migration path: All existing PublicMedia data should be migrated to Video table with
// isPublished=true before dropping this table.

// COMMENTED OUT - SCHEDULED FOR DELETION AFTER DATA MIGRATION
// model PublicMedia {
//   id              Int       @id @default(autoincrement())
//   path            String    @unique
//   filename        String
//   category        String
//   durationSeconds Int?      @map("duration_seconds")
//   quality         String?
//   orientation     String?
//   thumbnailPath   String?   @map("thumbnail_path")
//   fileSize        BigInt?   @map("file_size")
//   viewCount       Int?      @default(0) @map("view_count")
//   upvoteCount     Int?      @default(0) @map("upvote_count")
//   commentCount    Int?      @default(0) @map("comment_count")
//   finishCount     Int?      @default(0) @map("finish_count")
//   totalWatchTime  Int?      @default(0) @map("total_watch_time")
//   createdAt       DateTime  @default(now()) @map("created_at")
//   isLocked        Boolean?  @default(false) @map("is_locked")
//   lockedAt        DateTime? @map("locked_at")
//   lockedBy        String?   @map("locked_by")
//   position        Int?      @default(0)
//   uploaderId      String?   @map("uploader_id")
//
//   @@index([category], map: "idx_public_media_category")
//   @@index([path], map: "idx_public_media_path")
//   @@index([isLocked], map: "idx_public_media_is_locked")
//   @@index([position], map: "idx_public_media_position")
//   @@index([uploaderId], map: "idx_public_media_uploader")
//   @@index([category, createdAt], map: "idx_public_media_category_date")
//   @@index([orientation], map: "idx_public_media_orientation")
//   @@index([category, isLocked, createdAt], map: "idx_public_media_category_locked_date")
//   @@map("public_media")
// }

model Session {
  id             String    @id
  createdAt      DateTime  @default(now()) @map("created_at")
  lastSeenAt     DateTime? @map("last_seen_at")
  // Device tracking
  ipAddress      String?   @map("ip_address")
  userAgent      String?   @map("user_agent")
  deviceType     String?   @map("device_type")
  browser        String?
  os             String?
  // Geography
  country        String?
  countryName    String?   @map("country_name")
  region         String?
  city           String?
  timezone       String?
  latitude       Float?    @db.Real
  longitude      Float?    @db.Real
  // User correlation
  userId         String?   @map("user_id")
  // Analytics
  firstSeenAt    DateTime? @map("first_seen_at")
  visitCount     Int?      @default(1) @map("visit_count")

  // Relations
  user           User?     @relation("SessionUser", fields: [userId], references: [id])
  upvotes        Upvote[]
  comments       Comment[]
  views          View[]
  sessionBans    SessionBan[]
  contentReports ContentReport[]
  playlistViews  PlaylistView[]
  adImpressions  AdImpression[]
  adClicks       AdClick[]
  userFinishes   UserFinish[]

  // Photo gallery relations
  photoUpvotes   PhotoUpvote[]    @relation("SessionPhotoUpvotes")
  photoComments  PhotoComment[]   @relation("SessionPhotoComments")
  photoReactions PhotoReaction[]  @relation("SessionPhotoReactions")

  @@index([userId], map: "idx_sessions_user_id")
  @@index([country], map: "idx_sessions_country")
  @@map("sessions")
}

model Upvote {
  id        Int      @id @default(autoincrement())
  mediaId   Int      @map("media_id")
  sessionId String   @map("session_id")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  media     Video   @relation(fields: [mediaId], references: [id])
  session   Session @relation(fields: [sessionId], references: [id])

  @@index([mediaId, sessionId], map: "idx_upvotes_unique")
  @@index([mediaId], map: "idx_upvotes_media")
  @@map("upvotes")
}

model Comment {
  id               Int       @id @default(autoincrement())
  mediaId          Int       @map("media_id")
  sessionId        String    @map("session_id")
  userId           String?   @map("user_id")
  content          String
  createdAt        DateTime  @default(now()) @map("created_at")
  // Content safety fields
  safetyStatus     String?   @default("pending") @map("safety_status")
  safetyCheckedAt  DateTime? @map("safety_checked_at")
  safetyCategories Json?     @map("safety_categories")
  safetyReasoning  String?   @map("safety_reasoning")
  // Hidden comment fields
  isHidden         Boolean?  @default(false) @map("is_hidden")
  hiddenAt         DateTime? @map("hidden_at")
  hiddenReason     String?   @map("hidden_reason")
  moderationNotes  String?   @map("moderation_notes")

  // Relations
  media            Video @relation(fields: [mediaId], references: [id])
  session          Session     @relation(fields: [sessionId], references: [id])
  user             User?       @relation("CommentUser", fields: [userId], references: [id])
  moderation       CommentModeration?
  watchPartyMessages WatchPartyChatMessage[]

  @@index([mediaId], map: "idx_comments_media")
  @@index([sessionId], map: "idx_comments_session")
  @@index([userId], map: "idx_comments_user")
  @@index([safetyStatus], map: "idx_comments_safety_status")
  @@index([isHidden], map: "idx_comments_is_hidden")
  @@map("comments")
}

model View {
  id               Int      @id @default(autoincrement())
  mediaId          Int      @map("media_id")
  sessionId        String   @map("session_id")
  watchTimeSeconds Int?     @default(0) @map("watch_time_seconds")
  lastUpdated      DateTime @default(now()) @map("last_updated")
  createdAt        DateTime @default(now()) @map("created_at")

  // Relations
  media            Video   @relation(fields: [mediaId], references: [id])
  session          Session @relation(fields: [sessionId], references: [id])

  @@index([mediaId, sessionId], map: "idx_views_unique")
  @@index([mediaId], map: "idx_views_media")
  @@map("views")
}

// ============================================================================
// USER MANAGEMENT (EXCLUDING v2Users and mediaUsers)
// ============================================================================

model AuthToken {
  id        Int      @id @default(autoincrement())
  userId    String   @map("user_id")
  token     String   @unique
  type      String
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  user      User     @relation("AuthTokenUser", fields: [userId], references: [id])

  @@index([token], map: "idx_auth_tokens_token")
  @@index([userId], map: "idx_auth_tokens_user")
  @@map("auth_tokens")
}

model SessionBan {
  id        Int       @id @default(autoincrement())
  sessionId String    @map("session_id")
  reason    String?
  bannedBy  String?   @map("banned_by")
  createdAt DateTime  @default(now()) @map("created_at")
  expiresAt DateTime? @map("expires_at")

  // Relations
  session   Session   @relation(fields: [sessionId], references: [id])
  banner    User?     @relation("SessionBanner", fields: [bannedBy], references: [id])

  @@index([sessionId], map: "idx_session_bans_session")
  @@map("session_bans")
}

model CommentModeration {
  id          Int       @id @default(autoincrement())
  commentId   Int       @unique @map("comment_id")
  status      String    @default("pending")
  moderatedBy String?   @map("moderated_by")
  moderatedAt DateTime? @map("moderated_at")
  reason      String?

  // Relations
  comment     Comment   @relation(fields: [commentId], references: [id])
  moderator   User?     @relation("CommentModerator", fields: [moderatedBy], references: [id])

  @@index([commentId], map: "idx_comment_moderation_comment")
  @@index([status], map: "idx_comment_moderation_status")
  @@map("comment_moderation")
}

model EmailVerificationToken {
  id        Int      @id @default(autoincrement())
  userId    String   @map("user_id")
  token     String   @unique
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  user      User     @relation("EmailVerificationTokens", fields: [userId], references: [id])

  @@index([token], map: "idx_email_verification_tokens_token")
  @@index([userId], map: "idx_email_verification_tokens_user")
  @@map("email_verification_tokens")
}

model PasswordResetToken {
  id        Int       @id @default(autoincrement())
  userId    String    @map("user_id")
  token     String    @unique
  expiresAt DateTime  @map("expires_at")
  createdAt DateTime  @default(now()) @map("created_at")
  usedAt    DateTime? @map("used_at")

  // Relations
  user      User      @relation("PasswordResetTokens", fields: [userId], references: [id])

  @@index([token], map: "idx_password_reset_tokens_token")
  @@index([userId], map: "idx_password_reset_tokens_user")
  @@map("password_reset_tokens")
}

model EmailChangeToken {
  id        Int      @id @default(autoincrement())
  userId    String   @map("user_id")
  newEmail  String   @map("new_email")
  token     String   @unique
  expiresAt DateTime @map("expires_at")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  user      User     @relation("EmailChangeTokens", fields: [userId], references: [id])

  @@index([token], map: "idx_email_change_tokens_token")
  @@index([userId], map: "idx_email_change_tokens_user")
  @@map("email_change_tokens")
}

// ============================================================================
// ACHIEVEMENTS & DASHBOARD
// ============================================================================

model UserAchievement {
  id            Int      @id @default(autoincrement())
  userId        String   @map("user_id")
  achievementId String   @map("achievement_id")
  unlockedAt    DateTime @map("unlocked_at")
  progress      Int?     @default(0)
  notified      Boolean? @default(false)

  // Relations
  user          User     @relation("UserAchievements", fields: [userId], references: [id])

  @@unique([userId, achievementId], map: "idx_user_achievements_unique")
  @@index([userId], map: "idx_user_achievements_user")
  @@map("user_achievements")
}

model UserStats {
  id                    Int       @id @default(autoincrement())
  userId                String    @unique @map("user_id")
  totalWatchTimeSeconds Int?      @default(0) @map("total_watch_time_seconds")
  totalVideosWatched    Int?      @default(0) @map("total_videos_watched")
  totalUpvotesGiven     Int?      @default(0) @map("total_upvotes_given")
  totalCommentsMade     Int?      @default(0) @map("total_comments_made")
  totalFinishes         Int?      @default(0) @map("total_finishes")
  currentDayStreak      Int?      @default(0) @map("current_day_streak")
  longestDayStreak      Int?      @default(0) @map("longest_day_streak")
  lastActiveDate        String?   @map("last_active_date")
  longestSingleSession  Int?      @default(0) @map("longest_single_session")
  categoriesCompleted   Json?     @map("categories_completed")
  nightOwlCount         Int?      @default(0) @map("night_owl_count")
  earlyBirdCount        Int?      @default(0) @map("early_bird_count")
  updatedAt             DateTime? @map("updated_at")

  // Relations
  user                  User      @relation("UserStats", fields: [userId], references: [id])

  @@map("user_stats")
}

model UserFinish {
  id        Int       @id @default(autoincrement())
  userId    String    @map("user_id")
  mediaId   Int?      @map("media_id")
  sessionId String?   @map("session_id")
  createdAt DateTime  @map("created_at")

  // Relations
  user      User     @relation("UserFinishes", fields: [userId], references: [id])
  media     Video?   @relation(fields: [mediaId], references: [id])
  session   Session? @relation(fields: [sessionId], references: [id])

  @@index([userId], map: "idx_user_finishes_user")
  @@index([createdAt], map: "idx_user_finishes_date")
  @@map("user_finishes")
}

model VideoReaction {
  id             Int      @id @default(autoincrement())
  userId         String   @map("user_id")
  mediaId        Int      @map("media_id")
  reactionType   ReactionType @map("reaction_type")
  videoTimestamp Int      @map("video_timestamp")
  createdAt      DateTime @default(now()) @map("created_at")

  // Relations
  user           User  @relation("VideoReactions", fields: [userId], references: [id])
  media          Video @relation(fields: [mediaId], references: [id])

  @@index([userId, mediaId, reactionType], map: "idx_video_reactions_user_media_type")
  @@index([mediaId, videoTimestamp], map: "idx_video_reactions_media_timestamp")
  @@index([mediaId], map: "idx_video_reactions_media")
  @@index([createdAt], map: "idx_video_reactions_created")
  @@map("video_reactions")
}

model HighlightCooldown {
  id              Int      @id @default(autoincrement())
  userId          String   @map("user_id")
  lastGeneratedAt DateTime @map("last_generated_at")

  // Relations
  user            User     @relation("HighlightCooldowns", fields: [userId], references: [id])

  @@index([userId], map: "idx_highlight_cooldowns_user")
  @@map("highlight_cooldowns")
}

model UserDailyActivity {
  id                Int       @id @default(autoincrement())
  userId            String    @map("user_id")
  activityDate      String    @map("activity_date")
  watchTimeSeconds  Int?      @default(0) @map("watch_time_seconds")
  videosWatched     Int?      @default(0) @map("videos_watched")
  firstActivityHour Int?      @map("first_activity_hour")
  createdAt         DateTime? @map("created_at")

  // Relations
  user              User      @relation("UserDailyActivity", fields: [userId], references: [id])

  @@index([userId, activityDate], map: "idx_user_daily_activity_unique")
  @@map("user_daily_activity")
}

model ChatThreadReadStatus {
  id         Int      @id @default(autoincrement())
  userId     String   @map("user_id")
  mediaId    Int      @map("media_id")
  lastSeenAt DateTime @map("last_seen_at")

  // Relations
  user       User     @relation("ChatThreadReadStatus", fields: [userId], references: [id])

  @@index([userId, mediaId], map: "idx_chat_thread_read_unique")
  @@index([userId], map: "idx_chat_thread_read_user")
  @@map("chat_thread_read_status")
}

// ============================================================================
// APP SETTINGS & MODERATION
// ============================================================================

model AppSetting {
  key       String   @id
  value     String
  updatedAt DateTime @default(now()) @map("updated_at")

  @@map("app_settings")
}

model RateLimit {
  id      Int      @id @default(autoincrement())
  key     String   @unique
  count   Int      @default(1)
  resetAt DateTime @map("reset_at")

  @@unique([key], map: "idx_rate_limits_key")
  @@index([resetAt], map: "idx_rate_limits_reset_at")
  @@map("rate_limits")
}

model ModerationWordList {
  id        Int       @id @default(autoincrement())
  level     WordFilterLevel
  word      String
  createdAt DateTime  @default(now()) @map("created_at")
  createdBy String?   @map("created_by")

  // Relations
  creator   User?     @relation("ModerationWordListCreator", fields: [createdBy], references: [id])

  @@index([level], map: "idx_moderation_word_lists_level")
  @@index([word], map: "idx_moderation_word_lists_word")
  @@map("moderation_word_lists")
}

model ContentReport {
  id              Int          @id @default(autoincrement())
  mediaId         Int          @map("media_id")
  sessionId       String?      @map("session_id")
  userId          String?      @map("user_id")
  reportType      ReportType   @map("report_type")
  description     String?
  status          ReportStatus @default(pending)
  resolvedBy      String?      @map("resolved_by")
  resolvedAt      DateTime?    @map("resolved_at")
  resolutionNotes String?      @map("resolution_notes")
  createdAt       DateTime     @default(now()) @map("created_at")

  // Relations
  media           Video        @relation(fields: [mediaId], references: [id])
  session         Session?     @relation(fields: [sessionId], references: [id])
  user            User?        @relation("ContentReportUser", fields: [userId], references: [id])
  resolver        User?        @relation("ContentReportResolver", fields: [resolvedBy], references: [id])

  @@index([mediaId], map: "idx_content_reports_media")
  @@index([status], map: "idx_content_reports_status")
  @@index([sessionId], map: "idx_content_reports_session")
  @@index([createdAt], map: "idx_content_reports_created")
  @@map("content_reports")
}

// ============================================================================
// PLAYLISTS
// ============================================================================

model Playlist {
  id                   Int       @id @default(autoincrement())
  userId               String    @map("user_id")
  name                 String
  description          String?
  isPublic             Boolean?  @default(false) @map("is_public")
  shareToken           String?   @unique @map("share_token")
  thumbnailMediaId     Int?      @map("thumbnail_media_id")
  videoCount           Int?      @default(0) @map("video_count")
  totalDurationSeconds Int?      @default(0) @map("total_duration_seconds")
  viewCount            Int?      @default(0) @map("view_count")
  createdAt            DateTime  @default(now()) @map("created_at")
  updatedAt            DateTime? @map("updated_at")

  // Relations
  user                 User      @relation("UserPlaylists", fields: [userId], references: [id])
  videos               PlaylistVideo[]
  featured             FeaturedPlaylist?
  views                PlaylistView[]

  @@unique([userId, name], map: "idx_playlists_user_name")
  @@index([userId], map: "idx_playlists_user")
  @@index([isPublic], map: "idx_playlists_public")
  @@index([shareToken], map: "idx_playlists_share_token")
  @@map("playlists")
}

model PlaylistVideo {
  id         Int      @id @default(autoincrement())
  playlistId Int      @map("playlist_id")
  mediaId    Int      @map("media_id")
  position   Int      @default(0)
  addedAt    DateTime @default(now()) @map("added_at")

  // Relations
  playlist   Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
  media      Video    @relation(fields: [mediaId], references: [id])

  @@index([playlistId], map: "idx_playlist_videos_playlist")
  @@index([mediaId], map: "idx_playlist_videos_media")
  @@index([playlistId, mediaId], map: "idx_playlist_videos_unique")
  @@map("playlist_videos")
}

model FeaturedPlaylist {
  id         Int       @id @default(autoincrement())
  playlistId Int       @unique @map("playlist_id")
  position   Int       @default(0)
  featuredBy String?   @map("featured_by")
  featuredAt DateTime? @map("featured_at")

  // Relations
  playlist   Playlist  @relation(fields: [playlistId], references: [id], onDelete: Cascade)
  featurer   User?     @relation("FeaturedPlaylistFeaturer", fields: [featuredBy], references: [id])

  @@index([position], map: "idx_featured_playlists_position")
  @@map("featured_playlists")
}

model PlaylistView {
  id         Int      @id @default(autoincrement())
  playlistId Int      @map("playlist_id")
  sessionId  String   @map("session_id")
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  playlist   Playlist @relation(fields: [playlistId], references: [id], onDelete: Cascade)
  session    Session  @relation(fields: [sessionId], references: [id])

  @@index([playlistId], map: "idx_playlist_views_playlist")
  @@index([playlistId, sessionId], map: "idx_playlist_views_unique")
  @@map("playlist_views")
}

// ============================================================================
// ADVERTISEMENTS
// ============================================================================

model Ad {
  id               Int            @id @default(autoincrement())
  type             String
  variant          String?
  imagePath        String?        @map("image_path")
  linkUrl          String?        @map("link_url")
  title            String?
  subtitle         String?        @db.Text
  ctaText          String?        @map("cta_text")
  ctaStyle         String?        @default("primary") @map("cta_style")
  bgColor          String?        @map("bg_color")
  iconEmoji        String?        @map("icon_emoji")
  isSystemAd       Boolean        @default(false) @map("is_system_ad")
  frequency        Int            @default(6)
  visibility       String         @default("everyone")
  isActive         Boolean?       @default(true) @map("is_active")
  position         Int?           @default(0)
  impressionCount  Int?           @default(0) @map("impression_count")
  clickCount       Int?           @default(0) @map("click_count")
  startDate        DateTime?      @map("start_date")
  endDate          DateTime?      @map("end_date")
  placements       Json?          @default("[]")
  productId        String?        @unique @map("product_id")
  createdAt        DateTime       @default(now()) @map("created_at")
  updatedAt        DateTime?      @map("updated_at")

  // Relations
  impressions      AdImpression[]
  clicks           AdClick[]
  product          Product?       @relation(fields: [productId], references: [id], onDelete: SetNull)

  @@index([type], map: "idx_ads_type")
  @@index([isActive], map: "idx_ads_is_active")
  @@index([visibility], map: "idx_ads_visibility")
  @@map("ads")
}

model AdImpression {
  id        Int      @id @default(autoincrement())
  adId      Int      @map("ad_id")
  sessionId String?  @map("session_id")
  userId    String?  @map("user_id")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  ad        Ad       @relation(fields: [adId], references: [id], onDelete: Cascade)
  session   Session? @relation(fields: [sessionId], references: [id])
  user      User?    @relation("AdImpressionUser", fields: [userId], references: [id])

  @@index([adId], map: "idx_ad_impressions_ad")
  @@index([sessionId], map: "idx_ad_impressions_session")
  @@index([createdAt], map: "idx_ad_impressions_date")
  @@map("ad_impressions")
}

model AdClick {
  id        Int      @id @default(autoincrement())
  adId      Int      @map("ad_id")
  sessionId String?  @map("session_id")
  userId    String?  @map("user_id")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  ad        Ad       @relation(fields: [adId], references: [id], onDelete: Cascade)
  session   Session? @relation(fields: [sessionId], references: [id])
  user      User?    @relation("AdClickUser", fields: [userId], references: [id])

  @@index([adId], map: "idx_ad_clicks_ad")
  @@index([sessionId], map: "idx_ad_clicks_session")
  @@index([createdAt], map: "idx_ad_clicks_date")
  @@map("ad_clicks")
}

// ============================================================================
// FRIENDS & SOCIAL
// ============================================================================

model Friendship {
  id         Int              @id @default(autoincrement())
  userId     String           @map("user_id")
  friendId   String           @map("friend_id")
  status     FriendshipStatus @default(pending)
  createdAt  DateTime         @default(now()) @map("created_at")
  acceptedAt DateTime?        @map("accepted_at")

  // Relations
  user       User             @relation("UserFriendships", fields: [userId], references: [id])
  friend     User             @relation("UserFriends", fields: [friendId], references: [id])

  @@index([userId, friendId], map: "idx_friendships_user_friend")
  @@index([userId], map: "idx_friendships_user")
  @@index([friendId], map: "idx_friendships_friend")
  @@index([status], map: "idx_friendships_status")
  @@map("friendships")
}

model UserBlock {
  id            Int      @id @default(autoincrement())
  userId        String   @map("user_id")
  blockedUserId String   @map("blocked_user_id")
  createdAt     DateTime @default(now()) @map("created_at")

  // Relations
  user          User     @relation("UserBlocks", fields: [userId], references: [id])
  blockedUser   User     @relation("UserBlockedBy", fields: [blockedUserId], references: [id])

  @@index([userId, blockedUserId], map: "idx_user_blocks_unique")
  @@index([userId], map: "idx_user_blocks_user")
  @@map("user_blocks")
}

model Poke {
  id         Int      @id @default(autoincrement())
  fromUserId String   @map("from_user_id")
  toUserId   String   @map("to_user_id")
  isRead     Boolean? @default(false) @map("is_read")
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  from       User     @relation("PokesSent", fields: [fromUserId], references: [id])
  to         User     @relation("PokesReceived", fields: [toUserId], references: [id])

  @@index([toUserId], map: "idx_pokes_to_user")
  @@index([fromUserId], map: "idx_pokes_from_user")
  @@map("pokes")
}

model VideoRecommendation {
  id         Int      @id @default(autoincrement())
  fromUserId String   @map("from_user_id")
  toUserId   String   @map("to_user_id")
  mediaId    Int      @map("media_id")
  message    String?
  isRead     Boolean? @default(false) @map("is_read")
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  from       User  @relation("RecommendationsSent", fields: [fromUserId], references: [id])
  to         User  @relation("RecommendationsReceived", fields: [toUserId], references: [id])
  media      Video @relation(fields: [mediaId], references: [id])

  @@index([toUserId], map: "idx_video_recommendations_to_user")
  @@index([fromUserId], map: "idx_video_recommendations_from_user")
  @@index([mediaId], map: "idx_video_recommendations_media")
  @@map("video_recommendations")
}

model UserPresence {
  id                Int       @id @default(autoincrement())
  userId            String    @unique @map("user_id")
  isOnline          Boolean?  @default(false) @map("is_online")
  currentMediaId    Int?      @map("current_media_id")
  lastActivityAt    DateTime? @map("last_activity_at")
  lastVideoChangeAt DateTime? @map("last_video_change_at")

  // Relations
  user              User      @relation("UserPresence", fields: [userId], references: [id])

  @@index([isOnline], map: "idx_user_presence_online")
  @@index([userId], map: "idx_user_presence_user")
  @@map("user_presence")
}

model UserGalleryImage {
  id               Int      @id @default(autoincrement())
  userId           String   @map("user_id")
  filename         String
  originalFilename String?  @map("original_filename")
  position         Int      @default(0)
  uploadedAt       DateTime @default(now()) @map("uploaded_at")

  // Relations
  user             User     @relation("UserGalleryImages", fields: [userId], references: [id])

  @@index([userId], map: "idx_user_gallery_user")
  @@index([userId, position], map: "idx_user_gallery_position")
  @@map("user_gallery_images")
}

model UserSocialLink {
  id          Int            @id @default(autoincrement())
  userId      String         @map("user_id")
  platform    SocialPlatform
  url         String
  displayName String?        @map("display_name")
  position    Int            @default(0)
  createdAt   DateTime       @default(now()) @map("created_at")

  // Relations
  user        User           @relation("UserSocialLinks", fields: [userId], references: [id])

  @@index([userId], map: "idx_user_social_links_user")
  @@index([userId, position], map: "idx_user_social_links_position")
  @@map("user_social_links")
}

model PrivacySettings {
  id                       Int       @id @default(autoincrement())
  userId                   String    @unique @map("user_id")
  showOnlineStatus         Boolean?  @default(true) @map("show_online_status")
  showCurrentlyWatching    Boolean?  @default(true) @map("show_currently_watching")
  showInFriendActivity     Boolean?  @default(true) @map("show_in_friend_activity")
  anonymizePublicComments  Boolean?  @default(false) @map("anonymize_public_comments")
  hidePublicReactions      Boolean?  @default(false) @map("hide_public_reactions")
  hidePublicFinishes       Boolean?  @default(false) @map("hide_public_finishes")
  allowFriendRequests      Boolean?  @default(true) @map("allow_friend_requests")
  closeFriendsOnlyWatching Boolean?  @default(false) @map("close_friends_only_watching")
  showOnLeaderboard        Boolean?  @default(true) @map("show_on_leaderboard")
  createdAt                DateTime  @default(now()) @map("created_at")
  updatedAt                DateTime? @map("updated_at")

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

  @@index([userId], map: "idx_privacy_settings_user")
  @@map("privacy_settings")
}

model CloseFriend {
  id            Int      @id @default(autoincrement())
  userId        String   @map("user_id")
  closeFriendId String   @map("close_friend_id")
  addedAt       DateTime @default(now()) @map("added_at")

  // Relations
  user          User     @relation("CloseFriends", fields: [userId], references: [id], onDelete: Cascade)
  closeFriend   User     @relation("CloseFriendOf", fields: [closeFriendId], references: [id], onDelete: Cascade)

  @@unique([userId, closeFriendId], map: "idx_close_friends_unique")
  @@index([userId], map: "idx_close_friends_user")
  @@map("close_friends")
}

// ============================================================================
// SOCIAL GROUPS
// ============================================================================

enum SocialGroupType {
  SHIFT_TEAM
  CAMPAIGN_TEAM
  CUSTOM
}

model SocialGroup {
  id          String             @id @default(cuid())
  name        String
  type        SocialGroupType
  referenceId String?            @map("reference_id")
  meetingId   String?            @unique @map("meeting_id")
  meeting     Meeting?           @relation("GroupMeeting", fields: [meetingId], references: [id], onDelete: SetNull)
  createdAt   DateTime           @default(now()) @map("created_at")
  members     SocialGroupMember[]

  @@unique([type, referenceId], map: "idx_social_groups_type_ref")
  @@map("social_groups")
}

model SocialGroupMember {
  id       String      @id @default(cuid())
  groupId  String      @map("group_id")
  group    SocialGroup @relation(fields: [groupId], references: [id], onDelete: Cascade)
  userId   String      @map("user_id")
  user     User        @relation("SocialGroupMember", fields: [userId], references: [id], onDelete: Cascade)
  joinedAt DateTime    @default(now()) @map("joined_at")

  @@unique([groupId, userId], map: "idx_social_group_members_unique")
  @@index([userId], map: "idx_social_group_members_user")
  @@map("social_group_members")
}

// ============================================================================
// USER UPLOADS
// ============================================================================

model UserUpload {
  id               Int              @id @default(autoincrement())
  userId           String           @map("user_id")
  filename         String
  originalFilename String?          @map("original_filename")
  path             String
  durationSeconds  Int?             @map("duration_seconds")
  quality          String?
  orientation      String?
  fileSize         BigInt?          @map("file_size")
  thumbnailPath    String?          @map("thumbnail_path")
  status           UserUploadStatus @default(pending)
  reviewedBy       String?          @map("reviewed_by")
  reviewedAt       DateTime?        @map("reviewed_at")
  reviewNotes      String?          @map("review_notes")
  publicMediaId    Int?             @map("public_media_id")
  uploadInviteId   Int?             @map("upload_invite_id")
  createdAt        DateTime         @default(now()) @map("created_at")

  // Relations
  user             User             @relation("UserUploads", fields: [userId], references: [id])
  reviewer         User?            @relation("UserUploadReviewer", fields: [reviewedBy], references: [id])
  uploadInvite     UploadInvite?    @relation(fields: [uploadInviteId], references: [id])
  suggestedTags    UserUploadSuggestedTag[]

  @@index([userId], map: "idx_user_uploads_user")
  @@index([status], map: "idx_user_uploads_status")
  @@index([createdAt], map: "idx_user_uploads_created")
  @@index([uploadInviteId], map: "idx_user_uploads_invite")
  @@map("user_uploads")
}

model UploadInvite {
  id          Int                @id @default(autoincrement())
  code        String             @unique
  label       String?
  createdBy   String             @map("created_by")
  status      UploadInviteStatus @default(active)
  maxUploads  Int?               @map("max_uploads")
  uploadCount Int                @default(0) @map("upload_count")
  expiresAt   DateTime?          @map("expires_at")
  createdAt   DateTime           @default(now()) @map("created_at")

  // Relations
  creator     User               @relation("UploadInviteCreator", fields: [createdBy], references: [id])
  uploads     UserUpload[]

  @@index([code], map: "idx_upload_invites_code")
  @@index([status], map: "idx_upload_invites_status")
  @@index([createdBy], map: "idx_upload_invites_created_by")
  @@map("upload_invites")
}

// ============================================================================
// TAG SYSTEM
// ============================================================================

model TagCategory {
  id           Int      @id @default(autoincrement())
  name         String   @unique
  displayOrder Int      @default(0) @map("display_order")
  createdAt    DateTime @default(now()) @map("created_at")

  // Relations
  tags         Tag[]

  @@index([displayOrder], map: "idx_tag_categories_display_order")
  @@map("tag_categories")
}

model Tag {
  id           Int      @id @default(autoincrement())
  categoryId   Int      @map("category_id")
  name         String
  displayOrder Int      @default(0) @map("display_order")
  createdAt    DateTime @default(now()) @map("created_at")

  // Relations
  category     TagCategory @relation(fields: [categoryId], references: [id])
  publicMedia  PublicMediaTag[]
  userUploadSuggestions UserUploadSuggestedTag[]
  userPreferences UserTagPreference[]

  @@index([categoryId], map: "idx_tags_category")
  @@index([displayOrder], map: "idx_tags_display_order")
  @@index([categoryId, name], map: "idx_tags_unique_name")
  @@map("tags")
}

model PublicMediaTag {
  id      Int      @id @default(autoincrement())
  mediaId Int      @map("media_id")
  tagId   Int      @map("tag_id")
  addedAt DateTime @default(now()) @map("added_at")

  // Relations
  media   Video @relation("PublicMediaTags", fields: [mediaId], references: [id])
  tag     Tag   @relation(fields: [tagId], references: [id])

  @@index([mediaId], map: "idx_public_media_tags_media")
  @@index([tagId], map: "idx_public_media_tags_tag")
  @@index([mediaId, tagId], map: "idx_public_media_tags_unique")
  @@map("public_media_tags")
}

model UserUploadSuggestedTag {
  id          Int      @id @default(autoincrement())
  uploadId    Int      @map("upload_id")
  tagId       Int      @map("tag_id")
  suggestedAt DateTime @default(now()) @map("suggested_at")

  // Relations
  upload      UserUpload @relation(fields: [uploadId], references: [id])
  tag         Tag        @relation(fields: [tagId], references: [id])

  @@index([uploadId], map: "idx_user_upload_suggested_tags_upload")
  @@index([tagId], map: "idx_user_upload_suggested_tags_tag")
  @@index([uploadId, tagId], map: "idx_user_upload_suggested_tags_unique")
  @@map("user_upload_suggested_tags")
}

model UserTagPreference {
  id      Int      @id @default(autoincrement())
  userId  String   @map("user_id")
  tagId   Int      @map("tag_id")
  savedAt DateTime @default(now()) @map("saved_at")

  // Relations
  user    User     @relation("UserTagPreferences", fields: [userId], references: [id])
  tag     Tag      @relation(fields: [tagId], references: [id])

  @@index([userId], map: "idx_user_tag_preferences_user")
  @@index([tagId], map: "idx_user_tag_preferences_tag")
  @@index([userId, tagId], map: "idx_user_tag_preferences_unique")
  @@map("user_tag_preferences")
}

model PublicMediaPerformer {
  id          Int      @id @default(autoincrement())
  mediaId     Int      @map("media_id")
  performerId Int      @map("performer_id")
  addedAt     DateTime @default(now()) @map("added_at")

  // Relations
  media       Video   @relation("PublicMediaPerformers", fields: [mediaId], references: [id])
  performer   Creator @relation(fields: [performerId], references: [id])

  @@unique([mediaId, performerId], map: "idx_public_media_performers_unique")
  @@index([mediaId], map: "idx_public_media_performers_media")
  @@index([performerId], map: "idx_public_media_performers_performer")
  @@map("public_media_performers")
}

// ============================================================================
// VIDEO DIGEST SYSTEM
// ============================================================================

model VideoDigest {
  id             Int           @id @default(autoincrement())
  videoId        Int           @map("video_id")
  jobId          Int?          @unique @map("job_id")
  status         DigestStatus  @default(pending)
  progress       Int?          @default(0)
  frameCount     Int?          @map("frame_count")
  config         Json?
  frameAnalyses  Json?         @map("frame_analyses")
  transcript     Json?
  tags           Json?
  suggestedClips Json?         @map("suggested_clips")
  adCutSpec      Json?         @map("ad_cut_spec")
  stageResults   Json?         @map("stage_results")
  createdAt      DateTime      @default(now()) @map("created_at")
  startedAt      DateTime?     @map("started_at")
  completedAt    DateTime?     @map("completed_at")
  error          String?

  // Relations
  video          Video         @relation(fields: [videoId], references: [id])
  job            Job?          @relation(fields: [jobId], references: [id])
  digestVideoTags DigestVideoTag[]
  selectedClips  DigestSelectedClip[]
  generatedClips DigestGeneratedClip[]
  outputFolders  DigestOutputFolder[]
  compilations   DigestCompilation[]
  generatedScenes DigestGeneratedScene[]
  suggestedTags  DigestSuggestedTag[]

  @@index([videoId], map: "idx_video_digests_video")
  @@index([status], map: "idx_video_digests_status")
  @@index([createdAt], map: "idx_video_digests_created")
  @@map("video_digests")
}

model DigestVideoTag {
  id         Int      @id @default(autoincrement())
  digestId   Int      @map("digest_id")
  videoId    Int      @map("video_id")
  category   String
  value      String
  confidence Int?
  source     String?  @default("digest")
  evidence   Json?
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  digest     VideoDigest @relation(fields: [digestId], references: [id])
  video      Video       @relation(fields: [videoId], references: [id])

  @@index([digestId], map: "idx_digest_video_tags_digest")
  @@index([videoId], map: "idx_digest_video_tags_video")
  @@index([category], map: "idx_digest_video_tags_category")
  @@index([value], map: "idx_digest_video_tags_value")
  @@index([category, value], map: "idx_digest_video_tags_cat_val")
  @@map("digest_video_tags")
}

model DigestSelectedClip {
  id                Int        @id @default(autoincrement())
  digestId          Int        @map("digest_id")
  videoId           Int        @map("video_id")
  clipType          ClipType   @map("clip_type")
  startTime         Int        @map("start_time")
  endTime           Int        @map("end_time")
  duration          Int
  reason            String?
  interestScore     Int?       @map("interest_score")
  position          String?
  transcriptHint    String?    @map("transcript_hint")
  tags              Json?
  source            ClipSource @default(machine)
  isIncluded        Int        @default(1) @map("is_included")
  isHook            Int        @default(0) @map("is_hook")
  sequenceOrder     Int        @default(0) @map("sequence_order")
  hookSourceClipId  Int?       @map("hook_source_clip_id")
  createdAt         DateTime   @default(now()) @map("created_at")

  // Relations
  digest            VideoDigest @relation(fields: [digestId], references: [id])
  video             Video       @relation(fields: [videoId], references: [id])
  generatedClips    DigestGeneratedClip[]

  @@index([digestId], map: "idx_digest_selected_clips_digest")
  @@index([videoId], map: "idx_digest_selected_clips_video")
  @@index([clipType], map: "idx_digest_selected_clips_type")
  @@index([source], map: "idx_digest_selected_clips_source")
  @@index([sequenceOrder], map: "idx_digest_selected_clips_sequence")
  @@index([isHook], map: "idx_digest_selected_clips_is_hook")
  @@index([hookSourceClipId], map: "idx_digest_selected_clips_hook_source")
  @@map("digest_selected_clips")
}

model DigestGeneratedClip {
  id                        Int             @id @default(autoincrement())
  selectedClipId            Int             @map("selected_clip_id")
  digestId                  Int             @map("digest_id")
  folderId                  Int?            @map("folder_id")
  clipPath                  String?         @map("clip_path")
  gifPath                   String?         @map("gif_path")
  status                    ClipStatus      @default(pending)
  error                     String?
  createdAt                 DateTime        @default(now()) @map("created_at")
  completedAt               DateTime?       @map("completed_at")
  publishedToPublicMediaId  Int?            @map("published_to_public_media_id")
  publishedAt               DateTime?       @map("published_at")

  // Relations
  selectedClip              DigestSelectedClip @relation(fields: [selectedClipId], references: [id])
  digest                    VideoDigest        @relation(fields: [digestId], references: [id])
  folder                    DigestOutputFolder? @relation(fields: [folderId], references: [id])

  @@index([selectedClipId], map: "idx_digest_generated_clips_selected")
  @@index([digestId], map: "idx_digest_generated_clips_digest")
  @@index([status], map: "idx_digest_generated_clips_status")
  @@index([folderId], map: "idx_digest_generated_clips_folder")
  @@map("digest_generated_clips")
}

model DigestCompilation {
  id               Int       @id @default(autoincrement())
  digestId         Int       @map("digest_id")
  videoId          Int       @map("video_id")
  folderId         Int?      @map("folder_id")
  filename         String
  name             String?
  path             String
  durationSeconds  Int?      @map("duration_seconds")
  orientation      String?
  status           String    @default("pending")
  error            String?
  hasCaptions      Int       @default(0) @map("has_captions")
  captionStyle     Json?     @map("caption_style")
  closingAdPath    String?   @map("closing_ad_path")
  closingAdDuration Int?     @map("closing_ad_duration")
  tags             Json?
  createdAt        DateTime  @default(now()) @map("created_at")
  completedAt      DateTime? @map("completed_at")

  // Relations
  digest           VideoDigest        @relation(fields: [digestId], references: [id])
  video            Video              @relation(fields: [videoId], references: [id])
  folder           DigestOutputFolder? @relation(fields: [folderId], references: [id])

  @@index([digestId], map: "idx_digest_compilations_digest")
  @@index([videoId], map: "idx_digest_compilations_video")
  @@index([status], map: "idx_digest_compilations_status")
  @@index([folderId], map: "idx_digest_compilations_folder")
  @@map("digest_compilations")
}

model DigestOutputFolder {
  id               Int       @id @default(autoincrement())
  digestId         Int       @map("digest_id")
  videoId          Int       @map("video_id")
  folderPath       String    @map("folder_path")
  folderName       String    @map("folder_name")
  folderType       String    @default("clips") @map("folder_type")
  clipCount        Int?      @default(0) @map("clip_count")
  compilationCount Int?      @default(0) @map("compilation_count")
  totalSize        Int?      @map("total_size")
  createdAt        DateTime  @default(now()) @map("created_at")

  // Relations
  digest           VideoDigest @relation(fields: [digestId], references: [id])
  video            Video       @relation(fields: [videoId], references: [id])
  generatedClips   DigestGeneratedClip[]
  generatedScenes  DigestGeneratedScene[]
  compilations     DigestCompilation[]

  @@index([digestId], map: "idx_digest_output_folders_digest")
  @@index([videoId], map: "idx_digest_output_folders_video")
  @@index([folderPath], map: "idx_digest_output_folders_path")
  @@map("digest_output_folders")
}

model DigestGeneratedScene {
  id                       Int       @id @default(autoincrement())
  digestId                 Int       @map("digest_id")
  videoId                  Int       @map("video_id")
  folderId                 Int?      @map("folder_id")
  sceneNumber              Int       @map("scene_number")
  scenePath                String?   @map("scene_path")
  startTime                Float     @map("start_time") @db.Real
  endTime                  Float     @map("end_time") @db.Real
  duration                 Float     @db.Real
  tags                     Json?
  dominantPosition         String?   @map("dominant_position")
  createdAt                DateTime  @default(now()) @map("created_at")
  publishedToPublicMediaId Int?      @map("published_to_public_media_id")
  publishedAt              DateTime? @map("published_at")

  // Relations
  digest                   VideoDigest        @relation(fields: [digestId], references: [id])
  video                    Video              @relation(fields: [videoId], references: [id])
  folder                   DigestOutputFolder? @relation(fields: [folderId], references: [id])

  @@index([digestId], map: "idx_digest_generated_scenes_digest")
  @@index([videoId], map: "idx_digest_generated_scenes_video")
  @@index([folderId], map: "idx_digest_generated_scenes_folder")
  @@index([scenePath], map: "idx_digest_generated_scenes_path")
  @@map("digest_generated_scenes")
}

model VideoSceneCut {
  id                      Int      @id @default(autoincrement())
  videoId                 Int      @map("video_id")
  cuts                    Json
  sceneCount              Int      @map("scene_count")
  duration                Float    @db.Real
  detector                String   @default("content")
  threshold               Float    @default(27.0) @db.Real
  transnetCuts            Json?    @map("transnet_cuts")
  pyscenedetectCuts       Json?    @map("pyscenedetect_cuts")
  clipCuts                Json?    @map("clip_cuts")
  mergedCuts              Json?    @map("merged_cuts")
  analysisMetadata        Json?    @map("analysis_metadata")
  createdAt               DateTime @default(now()) @map("created_at")

  // Relations
  video                   Video    @relation(fields: [videoId], references: [id])

  @@index([videoId], map: "idx_video_scene_cuts_video")
  @@index([detector], map: "idx_video_scene_cuts_detector")
  @@map("video_scene_cuts")
}

model VideoTagTimeline {
  id         Int      @id @default(autoincrement())
  videoId    Int      @map("video_id")
  category   String
  value      String
  startTime  Float    @map("start_time") @db.Real
  endTime    Float    @map("end_time") @db.Real
  confidence Int?
  source     String?  @default("digest")
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  video      Video    @relation(fields: [videoId], references: [id])

  @@index([videoId], map: "idx_video_tag_timeline_video")
  @@index([category], map: "idx_video_tag_timeline_category")
  @@index([value], map: "idx_video_tag_timeline_value")
  @@index([category, value], map: "idx_video_tag_timeline_cat_val")
  @@index([videoId, startTime], map: "idx_video_tag_timeline_time")
  @@map("video_tag_timeline")
}

model VideoSegment {
  id              Int          @id @default(autoincrement())
  videoId         Int          @map("video_id")
  segmentType     SegmentType  @map("segment_type")
  startTime       Float        @map("start_time") @db.Real
  endTime         Float        @map("end_time") @db.Real
  duration        Float        @db.Real
  tags            Json?
  vocalCategory   VocalCategory? @map("vocal_category")
  transcript      String?
  dominantPosition String?     @map("dominant_position")
  interestScore   Int?         @map("interest_score")
  createdAt       DateTime     @default(now()) @map("created_at")

  // Relations
  video           Video        @relation(fields: [videoId], references: [id])

  @@index([videoId], map: "idx_video_segments_video")
  @@index([segmentType], map: "idx_video_segments_type")
  @@index([vocalCategory], map: "idx_video_segments_vocal_category")
  @@index([videoId, startTime], map: "idx_video_segments_time")
  @@map("video_segments")
}

model VideoTag {
  id         Int      @id @default(autoincrement())
  videoId    Int      @map("video_id")
  category   String
  value      String
  confidence Int?
  source     String?  @default("digest")
  evidence   Json?
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  video      Video    @relation(fields: [videoId], references: [id])

  @@index([videoId], map: "idx_video_tags_video")
  @@index([category], map: "idx_video_tags_category")
  @@index([value], map: "idx_video_tags_value")
  @@index([category, value], map: "idx_video_tags_cat_val")
  @@map("video_tags")
}

model DigestSuggestedTag {
  id           Int                @id @default(autoincrement())
  digestId     Int                @map("digest_id")
  category     String
  value        String
  confidence   Int?
  evidence     Json?
  status       SuggestedTagStatus @default(pending)
  mappedTagId  Int?               @map("mapped_tag_id")
  createdAt    DateTime           @default(now()) @map("created_at")
  reviewedAt   DateTime?          @map("reviewed_at")

  // Relations
  digest       VideoDigest        @relation(fields: [digestId], references: [id])

  @@index([digestId], map: "idx_digest_suggested_tags_digest")
  @@index([status], map: "idx_digest_suggested_tags_status")
  @@index([category], map: "idx_digest_suggested_tags_category")
  @@index([mappedTagId], map: "idx_digest_suggested_tags_mapped")
  @@map("digest_suggested_tags")
}

model DigestClipTag {
  id         Int      @id @default(autoincrement())
  digestId   Int      @map("digest_id")
  clipId     Int      @map("clip_id")
  videoId    Int      @map("video_id")
  category   String
  value      String
  confidence Int?
  source     String?  @default("clip_analysis")
  evidence   Json?
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  video      Video    @relation(fields: [videoId], references: [id])

  @@index([digestId], map: "idx_digest_clip_tags_digest")
  @@index([clipId], map: "idx_digest_clip_tags_clip")
  @@index([videoId], map: "idx_digest_clip_tags_video")
  @@index([category], map: "idx_digest_clip_tags_category")
  @@index([value], map: "idx_digest_clip_tags_value")
  @@map("digest_clip_tags")
}

// ============================================================================
// WATCH PARTY SYSTEM
// ============================================================================

model WatchPartySession {
  id          Int               @id @default(autoincrement())
  hostUserId  String            @map("host_user_id")
  mediaId     Int               @map("media_id")
  status      WatchPartyStatus  @default(active)
  inviteCode  String            @unique @map("invite_code")
  currentTime Int               @default(0) @map("current_time")
  isPlaying   Boolean           @default(false) @map("is_playing")
  createdAt   DateTime          @default(now()) @map("created_at")
  endedAt     DateTime?         @map("ended_at")

  // Relations
  host        User              @relation("WatchPartyHost", fields: [hostUserId], references: [id])
  participants WatchPartyParticipant[]
  messages    WatchPartyChatMessage[]
  reactions   WatchPartyReaction[]
  invites     WatchPartyInvite[]

  @@index([hostUserId], map: "idx_watch_party_sessions_host")
  @@index([mediaId], map: "idx_watch_party_sessions_media")
  @@index([status], map: "idx_watch_party_sessions_status")
  @@index([inviteCode], map: "idx_watch_party_sessions_invite")
  @@map("watch_party_sessions")
}

model WatchPartyParticipant {
  id        Int      @id @default(autoincrement())
  sessionId Int      @map("session_id")
  userId    String   @map("user_id")
  joinedAt  DateTime @default(now()) @map("joined_at")
  leftAt    DateTime? @map("left_at")

  // Relations
  session   WatchPartySession @relation(fields: [sessionId], references: [id])
  user      User              @relation("WatchPartyParticipant", fields: [userId], references: [id])

  @@index([sessionId], map: "idx_watch_party_participants_session")
  @@index([userId], map: "idx_watch_party_participants_user")
  @@map("watch_party_participants")
}

model WatchPartyChatMessage {
  id        Int      @id @default(autoincrement())
  sessionId Int      @map("session_id")
  userId    String   @map("user_id")
  commentId Int      @map("comment_id")
  createdAt DateTime @default(now()) @map("created_at")

  // Relations
  session   WatchPartySession @relation(fields: [sessionId], references: [id])
  user      User              @relation("WatchPartyChatUser", fields: [userId], references: [id])
  comment   Comment           @relation(fields: [commentId], references: [id])

  @@index([sessionId], map: "idx_watch_party_chat_session")
  @@index([userId], map: "idx_watch_party_chat_user")
  @@map("watch_party_chat_messages")
}

model WatchPartyReaction {
  id             Int      @id @default(autoincrement())
  sessionId      Int      @map("session_id")
  userId         String   @map("user_id")
  reactionType   ReactionType @map("reaction_type")
  videoTimestamp Int      @map("video_timestamp")
  createdAt      DateTime @default(now()) @map("created_at")

  // Relations
  session        WatchPartySession @relation(fields: [sessionId], references: [id])
  user           User              @relation("WatchPartyReactionUser", fields: [userId], references: [id])

  @@index([sessionId], map: "idx_watch_party_reactions_session")
  @@index([userId], map: "idx_watch_party_reactions_user")
  @@map("watch_party_reactions")
}

model WatchPartyInvite {
  id        Int       @id @default(autoincrement())
  sessionId Int       @map("session_id")
  userId    String    @map("user_id")
  invitedBy String    @map("invited_by")
  status    String    @default("pending")
  createdAt DateTime  @default(now()) @map("created_at")
  acceptedAt DateTime? @map("accepted_at")

  // Relations
  session   WatchPartySession @relation(fields: [sessionId], references: [id])
  user      User              @relation("WatchPartyInvitee", fields: [userId], references: [id])
  inviter   User              @relation("WatchPartyInviter", fields: [invitedBy], references: [id])

  @@index([sessionId], map: "idx_watch_party_invites_session")
  @@index([userId], map: "idx_watch_party_invites_user")
  @@map("watch_party_invites")
}

// ============================================================================
// TAG GENERATION JOBS
// ============================================================================

model TagGenerationJob {
  id                Int       @id @default(autoincrement())
  videoId           Int       @map("video_id")
  jobId             Int?      @map("job_id")
  status            String    @default("pending")
  progress          Int?      @default(0)
  generatedTags     Json?     @map("generated_tags")
  rawResponse       String?   @map("raw_response")
  error             String?
  createdAt         DateTime  @default(now()) @map("created_at")
  startedAt         DateTime? @map("started_at")
  completedAt       DateTime? @map("completed_at")

  // Relations
  video             Video     @relation(fields: [videoId], references: [id])
  job               Job?      @relation(fields: [jobId], references: [id])

  @@index([videoId], map: "idx_tag_generation_jobs_video")
  @@index([status], map: "idx_tag_generation_jobs_status")
  @@map("tag_generation_jobs")
}

// ============================================================================
// CREATORS & PERFORMERS
// ============================================================================

model Creator {
  id                     Int      @id @default(autoincrement())
  name                   String   @unique
  stage_name             String?  @map("stage_name")
  performerGender        String?  @map("performer_gender")
  faceEmbedding          Json?    @map("face_embedding")
  referenceImagePath     String?  @map("reference_image_path")
  performerCategory      String?  @map("performer_category")
  status                 String   @default("active")
  createdAt              DateTime @default(now()) @map("created_at")
  updatedAt              DateTime? @map("updated_at")

  // Relations
  publicMediaPerformers  PublicMediaPerformer[]
  performerFaces         PerformerFace[]
  performerDiscrepancies PerformerDiscrepancy[]

  @@index([name], map: "idx_creators_name")
  @@index([performerCategory], map: "idx_creators_category")
  @@map("creators")
}

model PerformerFace {
  id                Int      @id @default(autoincrement())
  performerId       Int      @map("performer_id")
  sourcePath        String   @map("source_path")
  frameTimestamp    Float?   @map("frame_timestamp") @db.Real
  bbox              Json
  embedding         Json
  gender            Int
  age               Int
  detScore          Float    @map("det_score") @db.Real
  verifiedByUser    Boolean  @default(false) @map("verified_by_user")
  confidence        Float?   @db.Real
  isReferenceImage  Boolean  @default(false) @map("is_reference_image")
  createdAt         DateTime @default(now()) @map("created_at")

  // Relations
  performer         Creator  @relation(fields: [performerId], references: [id])

  @@index([performerId], map: "idx_performer_faces_performer")
  @@index([sourcePath], map: "idx_performer_faces_source")
  @@index([isReferenceImage], map: "idx_performer_faces_reference")
  @@map("performer_faces")
}

model PerformerDiscrepancy {
  id                      Int       @id @default(autoincrement())
  videoPath               String    @map("video_path")
  frameTimestamp          Float     @map("frame_timestamp") @db.Real
  assignedPerformerId     Int       @map("assigned_performer_id")
  detectedPerformerId     Int?      @map("detected_performer_id")
  similarity              Float     @map("similarity") @db.Real
  faceData                Json      @map("face_data")
  resolutionStatus        String    @default("pending") @map("resolution_status")
  resolutionAction        String?   @map("resolution_action")
  resolutionNotes         String?   @map("resolution_notes")
  resolvedBy              String?   @map("resolved_by")
  resolvedAt              DateTime? @map("resolved_at")
  createdAt               DateTime  @default(now()) @map("created_at")

  // Relations
  assignedPerformer       Creator   @relation(fields: [assignedPerformerId], references: [id])
  resolver                User?     @relation("PerformerDiscrepancyResolver", fields: [resolvedBy], references: [id])

  @@index([assignedPerformerId], map: "idx_performer_discrepancies_assigned")
  @@index([detectedPerformerId], map: "idx_performer_discrepancies_detected")
  @@index([resolutionStatus], map: "idx_performer_discrepancies_status")
  @@map("performer_discrepancies")
}

// ============================================================================
// PIPELINE SYSTEM
// ============================================================================

model Pipeline {
  id              Int            @id @default(autoincrement())
  name            String
  description     String?
  status          PipelineStatus @default(draft)
  config          Json?
  templateId      Int?           @map("template_id")
  createdAt       DateTime       @default(now()) @map("created_at")
  startedAt       DateTime?      @map("started_at")
  completedAt     DateTime?      @map("completed_at")
  error           String?

  // Relations
  template        PipelineTemplate? @relation(fields: [templateId], references: [id])
  steps           PipelineStep[]
  jobs            Job[]

  @@index([status], map: "idx_pipelines_status")
  @@index([templateId], map: "idx_pipelines_template")
  @@map("pipelines")
}

model PipelineStep {
  id             Int                @id @default(autoincrement())
  pipelineId     Int                @map("pipeline_id")
  stepName       String             @map("step_name")
  stepType       String             @map("step_type")
  sequenceOrder  Int                @map("sequence_order")
  status         PipelineStepStatus @default(pending)
  config         Json?
  inputs         Json?
  outputs        Json?
  dependsOn      Json?              @map("depends_on")
  error          String?
  createdAt      DateTime           @default(now()) @map("created_at")
  startedAt      DateTime?          @map("started_at")
  completedAt    DateTime?          @map("completed_at")

  // Relations
  pipeline       Pipeline           @relation(fields: [pipelineId], references: [id])
  jobs           Job[]
  events         PipelineStepEvent[]

  @@index([pipelineId], map: "idx_pipeline_steps_pipeline")
  @@index([status], map: "idx_pipeline_steps_status")
  @@index([sequenceOrder], map: "idx_pipeline_steps_sequence")
  @@map("pipeline_steps")
}

model PipelineStepEvent {
  id         Int          @id @default(autoincrement())
  stepId     Int          @map("step_id")
  eventType  String       @map("event_type")
  message    String?
  data       Json?
  createdAt  DateTime     @default(now()) @map("created_at")

  // Relations
  step       PipelineStep @relation(fields: [stepId], references: [id])

  @@index([stepId], map: "idx_pipeline_step_events_step")
  @@index([eventType], map: "idx_pipeline_step_events_type")
  @@map("pipeline_step_events")
}

model ResourceSnapshot {
  id          Int      @id @default(autoincrement())
  timestamp   DateTime @default(now())
  cpuPercent  Float?   @map("cpu_percent") @db.Real
  memoryMb    Int?     @map("memory_mb")
  gpuPercent  Float?   @map("gpu_percent") @db.Real
  vramMb      Int?     @map("vram_mb")
  diskUsageMb Int?     @map("disk_usage_mb")
  activeJobs  Int?     @map("active_jobs")
  queuedJobs  Int?     @map("queued_jobs")
  metadata    Json?

  @@index([timestamp], map: "idx_resource_snapshots_timestamp")
  @@map("resource_snapshots")
}

model PipelineTemplate {
  id               Int       @id @default(autoincrement())
  name             String
  description      String?
  category         String?
  defaultConfig    Json?     @map("default_config")
  stepDefinitions  Json      @map("step_definitions")
  isPublic         Boolean   @default(false) @map("is_public")
  createdAt        DateTime  @default(now()) @map("created_at")
  updatedAt        DateTime? @map("updated_at")

  // Relations
  pipelines        Pipeline[]

  @@index([category], map: "idx_pipeline_templates_category")
  @@index([isPublic], map: "idx_pipeline_templates_public")
  @@map("pipeline_templates")
}

// ============================================================================
// SUBSCRIPTION & PAYMENTS
// ============================================================================

model SubscriptionPlan {
  id                  Int      @id @default(autoincrement())
  name                String
  priceCAD            Int      @map("price_cad")
  durationDays        Int      @map("duration_days")
  features            Json?
  isActive            Boolean  @default(true) @map("is_active")
  createdAt           DateTime @default(now()) @map("created_at")

  // Stripe integration
  stripeProductId     String?  @map("stripe_product_id")
  stripePriceId       String?  @map("stripe_price_id")
  stripeYearlyPriceId String?  @map("stripe_yearly_price_id")
  yearlyPriceCAD      Int?     @map("yearly_price_cad")
  description         String?  @db.Text
  tier                Int      @default(0)
  displayOrder        Int      @default(0) @map("display_order")

  // Page content fields
  slug              String?  @unique
  coverPhoto        String?  @map("cover_photo")
  coverVideoId      Int?     @map("cover_video_id")
  richDescription   String?  @db.Text @map("rich_description")
  ctaText           String?  @map("cta_text")
  ctaSubtext        String?  @map("cta_subtext")
  highlightPlan     Boolean  @default(false) @map("highlight_plan")

  // Relations
  subscriptions UserSubscription[]

  @@map("subscription_plans")
}

model UserSubscription {
  id                    Int                @id @default(autoincrement())
  userId                String             @map("user_id")
  planId                Int                @map("plan_id")
  status                SubscriptionStatus @default(active)
  startDate             DateTime           @map("start_date")
  endDate               DateTime           @map("end_date")
  cancelledAt           DateTime?          @map("cancelled_at")
  createdAt             DateTime           @default(now()) @map("created_at")

  // Stripe integration
  stripeSubscriptionId  String?  @unique @map("stripe_subscription_id")
  stripeCustomerId      String?  @map("stripe_customer_id")
  currentPeriodEnd      DateTime? @map("current_period_end")
  cancelAtPeriodEnd     Boolean  @default(false) @map("cancel_at_period_end")

  // Relations
  user           User               @relation("UserSubscriptions", fields: [userId], references: [id])
  plan           SubscriptionPlan   @relation(fields: [planId], references: [id])

  @@index([userId], map: "idx_user_subscriptions_user")
  @@index([planId], map: "idx_user_subscriptions_plan")
  @@index([status], map: "idx_user_subscriptions_status")
  @@map("user_subscriptions")
}

model Invoice {
  id                Int           @id @default(autoincrement())
  userId            String        @map("user_id")
  subscriptionId    Int?          @map("subscription_id")
  amountCAD         Int           @map("amount_cad")
  status            InvoiceStatus @default(pending)
  issuedAt          DateTime      @default(now()) @map("issued_at")
  paidAt            DateTime?     @map("paid_at")
  dueDate           DateTime      @map("due_date")
  description       String?
  metadata          Json?

  // Stripe integration
  stripeInvoiceId   String?       @unique @map("stripe_invoice_id")
  type              String        @default("subscription") @map("invoice_type") // subscription|product|donation

  // Relations
  user              User          @relation("UserInvoices", fields: [userId], references: [id])
  payments          Payment[]

  @@index([userId], map: "idx_invoices_user")
  @@index([status], map: "idx_invoices_status")
  @@index([issuedAt], map: "idx_invoices_issued")
  @@map("invoices")
}

model Payment {
  id                        Int           @id @default(autoincrement())
  invoiceId                 Int           @map("invoice_id")
  userId                    String        @map("user_id")
  amountCAD                 Int           @map("amount_cad")
  method                    PaymentMethod
  status                    PaymentStatus @default(pending)
  externalId                String?       @map("external_id")
  metadata                  Json?
  processedAt               DateTime?     @map("processed_at")
  createdAt                 DateTime      @default(now()) @map("created_at")

  // Stripe integration
  stripePaymentIntentId     String?       @unique @map("stripe_payment_intent_id")
  stripeCheckoutSessionId   String?       @map("stripe_checkout_session_id")

  // Relations
  invoice           Invoice       @relation(fields: [invoiceId], references: [id])
  user              User          @relation("UserPayments", fields: [userId], references: [id])
  auditLogs         PaymentAuditLog[]

  @@index([invoiceId], map: "idx_payments_invoice")
  @@index([userId], map: "idx_payments_user")
  @@index([status], map: "idx_payments_status")
  @@map("payments")
}

model PaymentAuditLog {
  id         Int      @id @default(autoincrement())
  paymentId  Int?     @map("payment_id")
  orderId    String?  @map("order_id")
  action     String
  oldStatus  String?  @map("old_status")
  newStatus  String?  @map("new_status")
  userId     String?  @map("user_id")
  metadata   Json?
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  payment    Payment? @relation(fields: [paymentId], references: [id])
  order      Order?   @relation(fields: [orderId], references: [id])
  user       User?    @relation("PaymentAuditUser", fields: [userId], references: [id])

  @@index([paymentId], map: "idx_payment_audit_log_payment")
  @@index([orderId], map: "idx_payment_audit_log_order")
  @@index([action], map: "idx_payment_audit_log_action")
  @@index([createdAt], map: "idx_payment_audit_log_created")
  @@map("payment_audit_log")
}

model Product {
  id               String      @id @default(cuid())
  slug             String      @unique
  title            String
  description      String?     @db.Text
  priceCAD         Int         @map("price_cad")
  type             ProductType
  stripeProductId  String?     @map("stripe_product_id")
  stripePriceId    String?     @map("stripe_price_id")
  isActive         Boolean     @default(true) @map("is_active")
  imageUrl         String?     @map("image_url")
  photoId          Int?        @map("photo_id")
  videoId          Int?        @map("video_id")
  galleryPhotoIds  Json?       @map("gallery_photo_ids")
  downloadUrl      String?     @map("download_url")
  metadata         Json?
  maxPurchases     Int?        @map("max_purchases")
  purchaseCount    Int         @default(0) @map("purchase_count")
  createdByUserId  String?     @map("created_by_user_id")
  createdAt        DateTime    @default(now()) @map("created_at")
  updatedAt        DateTime    @updatedAt @map("updated_at")

  orders           Order[]
  galleryAd        Ad?

  @@index([type], map: "idx_products_type")
  @@index([isActive], map: "idx_products_active")
  @@map("products")
}

model Order {
  id                        String      @id @default(cuid())
  userId                    String?     @map("user_id")
  productId                 String?     @map("product_id")
  amountCAD                 Int         @map("amount_cad")
  status                    OrderStatus @default(PENDING)
  stripeCheckoutSessionId   String?     @unique @map("stripe_checkout_session_id")
  stripePaymentIntentId     String?     @map("stripe_payment_intent_id")
  type                      String      @default("product") @map("order_type") // product|donation

  // Buyer info (for guests)
  buyerEmail                String      @map("buyer_email")
  buyerName                 String?     @map("buyer_name")

  // Donation-specific
  donorMessage              String?     @db.Text @map("donor_message")
  isAnonymous               Boolean     @default(false) @map("is_anonymous")

  completedAt               DateTime?   @map("completed_at")
  createdAt                 DateTime    @default(now()) @map("created_at")
  updatedAt                 DateTime    @updatedAt @map("updated_at")

  // Relations
  user                      User?           @relation("UserOrders", fields: [userId], references: [id])
  product                   Product?        @relation(fields: [productId], references: [id])
  donationPageId            String?         @map("donation_page_id")
  donationPage              DonationPage?   @relation("DonationPageOrders", fields: [donationPageId], references: [id], onDelete: SetNull)
  influenceCampaignId       String?         @map("influence_campaign_id")
  influenceCampaign         Campaign?       @relation("CampaignDonations", fields: [influenceCampaignId], references: [id], onDelete: SetNull)
  tickets                   Ticket[]        @relation("TicketOrder")
  auditLogs                 PaymentAuditLog[]

  @@index([userId], map: "idx_orders_user")
  @@index([productId], map: "idx_orders_product")
  @@index([status], map: "idx_orders_status")
  @@index([type], map: "idx_orders_type")
  @@index([donationPageId], map: "idx_orders_donation_page")
  @@index([influenceCampaignId], map: "idx_orders_influence_campaign")
  @@map("orders")
}

enum DonationPageStatus {
  DRAFT
  ACTIVE
  PAUSED
  ARCHIVED
}

model DonationPage {
  id                String              @id @default(cuid())
  slug              String              @unique
  title             String
  description       String?             @db.Text
  status            DonationPageStatus  @default(DRAFT)

  // Donation config (per-page, mirrors PaymentSettings fields)
  suggestedAmounts  Json                @default("[1000, 2500, 5000, 10000]")
  minimumAmount     Int                 @default(500)       @map("minimum_amount")
  thankYouMessage   String              @default("Thank you for your support!") @db.Text @map("thank_you_message")

  // Media
  coverPhoto        String?             @map("cover_photo")
  coverVideoId      Int?                @map("cover_video_id")

  // Display options
  highlightPage     Boolean             @default(false) @map("highlight_page")
  showDonorCount    Boolean             @default(true)  @map("show_donor_count")
  showTotalRaised   Boolean             @default(false) @map("show_total_raised")
  goalAmount        Int?                @map("goal_amount")

  // Creator tracking
  createdByUserId   String?             @map("created_by_user_id")
  createdByUser     User?               @relation("DonationPageCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)

  createdAt         DateTime            @default(now()) @map("created_at")
  updatedAt         DateTime            @updatedAt @map("updated_at")

  orders            Order[]             @relation("DonationPageOrders")

  @@index([status])
  @@map("donation_pages")
}

model PaymentSettings {
  id                       String   @id @default(cuid())
  stripeSecretKey          String   @default("") @map("stripe_secret_key")
  stripePublishableKey     String   @default("") @map("stripe_publishable_key")
  stripeWebhookSecret      String   @default("") @map("stripe_webhook_secret")
  defaultCurrency          String   @default("cad") @map("default_currency")

  // Donation settings
  enableDonations          Boolean  @default(true) @map("enable_donations")
  donationSuggestedAmounts Json     @default("[1000, 2500, 5000, 10000]") @map("donation_suggested_amounts")
  donationMinimum          Int      @default(500) @map("donation_minimum")
  donationPageTitle        String   @default("Support Our Work") @map("donation_page_title")
  donationPageDescription  String?  @db.Text @map("donation_page_description")
  thankYouMessage          String   @default("Thank you for your support!") @db.Text @map("thank_you_message")

  createdAt                DateTime @default(now()) @map("created_at")
  updatedAt                DateTime @updatedAt @map("updated_at")

  @@map("payment_settings")
}

// ============================================================================
// NOTIFICATIONS
// ============================================================================

model Notification {
  id         Int              @id @default(autoincrement())
  userId     String           @map("user_id")
  type       NotificationType
  title      String
  message    String
  metadata   Json?
  isRead     Boolean          @default(false) @map("is_read")
  createdAt  DateTime         @default(now()) @map("created_at")
  readAt     DateTime?        @map("read_at")

  // Relations
  user       User             @relation("UserNotifications", fields: [userId], references: [id])

  @@index([userId], map: "idx_notifications_user")
  @@index([isRead], map: "idx_notifications_read")
  @@index([type], map: "idx_notifications_type")
  @@index([createdAt], map: "idx_notifications_created")
  @@map("notifications")
}

model NotificationPreferences {
  id                      Int       @id @default(autoincrement())
  userId                  String    @unique @map("user_id")
  enableFriendRequests    Boolean   @default(true) @map("enable_friend_requests")
  enableComments          Boolean   @default(true) @map("enable_comments")
  enableUploadApprovals   Boolean   @default(true) @map("enable_upload_approvals")
  enableAchievements      Boolean   @default(true) @map("enable_achievements")
  enableSystemUpdates     Boolean   @default(true) @map("enable_system_updates")
  emailNotifications      Boolean   @default(false) @map("email_notifications")
  digestFrequency         String    @default("none") @map("digest_frequency") // "none" | "daily" | "weekly"
  lastDigestSentAt        DateTime? @map("last_digest_sent_at")
  createdAt               DateTime  @default(now()) @map("created_at")
  updatedAt               DateTime? @map("updated_at")

  // Relations
  user                    User      @relation("NotificationPreferences", fields: [userId], references: [id])

  @@index([userId], map: "idx_notification_preferences_user")
  @@map("notification_preferences")
}

// ============================================================================
// GEO-BLOCKING
// ============================================================================

model GeoBlockingRule {
  id           Int      @id @default(autoincrement())
  countryCode  String   @map("country_code")
  action       String   // 'block' | 'allow'
  reason       String?
  isActive     Boolean  @default(true) @map("is_active")
  createdAt    DateTime @default(now()) @map("created_at")
  updatedAt    DateTime? @map("updated_at")

  @@index([countryCode], map: "idx_geo_blocking_country")
  @@index([isActive], map: "idx_geo_blocking_active")
  @@map("geo_blocking_rules")
}

// ============================================================================
// PUBLISHED INBOX FILES
// ============================================================================

model PublishedInboxFile {
  id                       Int       @id @default(autoincrement())
  originalInboxPath        String    @map("original_inbox_path")
  publishedToPublicMediaId Int       @map("published_to_public_media_id")
  publishedAt              DateTime  @map("published_at")
  movedToPath              String?   @map("moved_to_path")
  metadataSnapshot         Json?     @map("metadata_snapshot")

  @@index([originalInboxPath], map: "idx_published_inbox_files_inbox_path")
  @@index([publishedToPublicMediaId], map: "idx_published_inbox_files_public_media")
  @@map("published_inbox_files")
}

// ============================================================================
// VIDEO OCR RESULTS
// ============================================================================

model VideoOcrResult {
  id             Int      @id @default(autoincrement())
  videoId        Int      @map("video_id")
  fullText       String   @map("full_text")
  structuredData Json?    @map("structured_data")
  confidence     Float?   @db.Real
  createdAt      DateTime @default(now()) @map("created_at")

  // Relations
  video          Video    @relation(fields: [videoId], references: [id])

  @@index([videoId], map: "idx_video_ocr_results_video")
  @@map("video_ocr_results")
}

// Enhanced video analytics models
model VideoView {
  id               Int       @id @default(autoincrement())
  videoId          Int       @map("video_id")
  userId           String?   @map("user_id")
  ipAddressHash    String?   @map("ip_address_hash") @db.VarChar(64) // SHA-256 hash
  userAgentHash    String?   @map("user_agent_hash") @db.VarChar(64) // SHA-256 hash
  referer          String?   @db.Text
  country          String?   @db.VarChar(2)        // ISO 3166-1 alpha-2
  region           String?   @db.VarChar(100)
  city             String?   @db.VarChar(100)
  latitude         Float?
  longitude        Float?
  watchTimeSeconds Int       @default(0) @map("watch_time_seconds")
  completed        Boolean   @default(false)
  createdAt        DateTime  @default(now()) @map("created_at")
  updatedAt        DateTime  @updatedAt @map("updated_at")

  // Relations
  video            Video     @relation(fields: [videoId], references: [id], onDelete: Cascade)
  user             User?     @relation("VideoViews", fields: [userId], references: [id], onDelete: SetNull)

  @@index([videoId], map: "idx_video_views_video")
  @@index([userId], map: "idx_video_views_user")
  @@index([createdAt], map: "idx_video_views_created")
  @@index([videoId, createdAt], map: "idx_video_views_video_created")
  @@index([country, createdAt], map: "idx_video_views_country_created")
  @@map("video_views")
}

model VideoEvent {
  id         Int      @id @default(autoincrement())
  videoId    Int      @map("video_id")
  viewId     Int?     @map("view_id")
  eventType  String   @map("event_type") @db.VarChar(50) // play, pause, seek, complete
  timestamp  Decimal  @db.Decimal(10, 2) // Video timestamp in seconds
  createdAt  DateTime @default(now()) @map("created_at")

  // Relations
  video      Video    @relation(fields: [videoId], references: [id], onDelete: Cascade)

  @@index([videoId], map: "idx_video_events_video")
  @@index([viewId], map: "idx_video_events_view")
  @@index([eventType], map: "idx_video_events_type")
  @@index([createdAt], map: "idx_video_events_created")
  @@map("video_events")
}

model VideoScheduleHistory {
  id                Int       @id @default(autoincrement())
  videoId           Int       @map("video_id")
  action            String    @db.VarChar(20) // 'publish' or 'unpublish'
  scheduledFor      DateTime  @map("scheduled_for")
  executedAt        DateTime? @map("executed_at")
  status            String    @db.VarChar(20) // 'pending', 'completed', 'failed', 'cancelled'
  error             String?   @db.Text
  scheduledByUserId String    @map("scheduled_by_user_id")
  createdAt         DateTime  @default(now()) @map("created_at")

  // Relations
  video             Video     @relation(fields: [videoId], references: [id], onDelete: Cascade)
  scheduledBy       User      @relation("VideoScheduleHistory", fields: [scheduledByUserId], references: [id])

  @@index([videoId], map: "idx_video_schedule_history_video")
  @@index([scheduledFor], map: "idx_video_schedule_history_scheduled")
  @@index([status], map: "idx_video_schedule_history_status")
  @@index([scheduledByUserId], map: "idx_video_schedule_history_user")
  @@map("video_schedule_history")
}

// ============================================================================
// DOCS ANALYTICS
// ============================================================================

model DocsPageView {
  id            String   @id @default(cuid())
  path          String                          // e.g. "/docs/getting-started/"
  referrer      String?  @db.Text              // document.referrer
  sessionHash   String?                        // anonymous session UUID (sessionStorage)
  userAgent     String?                        // for device type breakdown
  ipAddressHash String?  @map("ip_address_hash") @db.VarChar(64)
  country       String?  @db.VarChar(2)        // ISO 3166-1 alpha-2
  region        String?  @db.VarChar(100)
  city          String?  @db.VarChar(100)
  latitude      Float?
  longitude     Float?
  createdAt     DateTime @default(now())

  @@index([createdAt])
  @@index([path, createdAt])
  @@index([country, createdAt])
  @@map("docs_page_views")
}

// ============================================================================
// PHOTO GALLERY
// ============================================================================

model Photo {
  id                 Int       @id @default(autoincrement())
  path               String    @unique                          // Full path to original file
  filename           String                                     // UUID filename on disk
  originalFilename   String?   @map("original_filename")        // Original upload filename
  title              String?
  description        String?   @db.Text
  producer           String?
  creator            String?
  tags               Json?                                      // String array

  // Image metadata (from sharp)
  width              Int?
  height             Int?
  orientation        String?                                    // H / V / S (horizontal/vertical/square)
  fileSize           BigInt?   @map("file_size")
  format             String?                                    // jpeg, png, webp, avif, gif, tiff, heic
  colorSpace         String?   @map("color_space")              // srgb, display-p3, etc.
  hasAlpha           Boolean?  @default(false) @map("has_alpha")
  dpi                Int?

  // EXIF data
  cameraMake         String?   @map("camera_make")
  cameraModel        String?   @map("camera_model")
  focalLength        String?   @map("focal_length")
  aperture           String?
  shutterSpeed       String?   @map("shutter_speed")
  iso                Int?
  takenAt            DateTime? @map("taken_at")
  gpsLatitude        Float?    @map("gps_latitude")  @db.Real
  gpsLongitude       Float?    @map("gps_longitude") @db.Real

  // Processed variants
  thumbnailPath      String?   @map("thumbnail_path")
  mediumPath         String?   @map("medium_path")
  largePath          String?   @map("large_path")
  webpPath           String?   @map("webp_path")

  // Publishing (mirrors Video)
  isPublished        Boolean   @default(false) @map("is_published")
  publishedAt        DateTime? @map("published_at")
  category           String?
  accessLevel        String    @default("free") @map("access_level")
  position           Int?      @default(0)
  isLocked           Boolean   @default(false) @map("is_locked")
  scheduledPublishAt   DateTime? @map("scheduled_publish_at")
  scheduledUnpublishAt DateTime? @map("scheduled_unpublish_at")

  // Engagement counters
  viewCount          Int       @default(0) @map("view_count")
  upvoteCount        Int       @default(0) @map("upvote_count")
  commentCount       Int       @default(0) @map("comment_count")

  // Album membership
  albumId            Int?      @map("album_id")
  albumPosition      Int?      @default(0) @map("album_position")

  // Tracking
  uploaderId         String?   @map("uploader_id")
  createdAt          DateTime  @default(now()) @map("created_at")

  // Relations
  album              PhotoAlbum? @relation("AlbumPhotos", fields: [albumId], references: [id], onDelete: SetNull)
  uploader           User?       @relation("PhotoUploader", fields: [uploaderId], references: [id])
  upvotes            PhotoUpvote[]
  comments           PhotoComment[]
  views              PhotoView[]
  reactions          PhotoReaction[]
  coverForAlbum      PhotoAlbum? @relation("AlbumCover")

  @@index([orientation], map: "idx_photos_orientation")
  @@index([producer], map: "idx_photos_producer")
  @@index([isPublished, isLocked], map: "idx_photos_published_locked")
  @@index([category, isPublished], map: "idx_photos_category_published")
  @@index([albumId, albumPosition], map: "idx_photos_album_position")
  @@index([createdAt], map: "idx_photos_created_at")
  @@index([uploaderId], map: "idx_photos_uploader")
  @@map("photos")
}

model PhotoAlbum {
  id                   Int       @id @default(autoincrement())
  title                String
  description          String?   @db.Text
  coverPhotoId         Int?      @unique @map("cover_photo_id")

  // Publishing
  isPublished          Boolean   @default(false) @map("is_published")
  publishedAt          DateTime? @map("published_at")
  category             String?
  accessLevel          String    @default("free") @map("access_level")
  position             Int?      @default(0)
  isLocked             Boolean   @default(false) @map("is_locked")

  // Aggregate counters
  viewCount            Int       @default(0) @map("view_count")
  upvoteCount          Int       @default(0) @map("upvote_count")
  photoCount           Int       @default(0) @map("photo_count")

  // Scheduling
  scheduledPublishAt   DateTime? @map("scheduled_publish_at")
  scheduledUnpublishAt DateTime? @map("scheduled_unpublish_at")

  // Tracking
  creatorId            String?   @map("creator_id")
  createdAt            DateTime  @default(now()) @map("created_at")
  updatedAt            DateTime  @updatedAt @map("updated_at")

  // Relations
  photos               Photo[]   @relation("AlbumPhotos")
  coverPhoto           Photo?    @relation("AlbumCover", fields: [coverPhotoId], references: [id], onDelete: SetNull)
  creator              User?     @relation("AlbumCreator", fields: [creatorId], references: [id])

  @@index([isPublished], map: "idx_photo_albums_published")
  @@index([creatorId], map: "idx_photo_albums_creator")
  @@map("photo_albums")
}

model PhotoUpvote {
  id        Int      @id @default(autoincrement())
  photoId   Int      @map("photo_id")
  sessionId String   @map("session_id")
  createdAt DateTime @default(now()) @map("created_at")

  photo     Photo    @relation(fields: [photoId], references: [id], onDelete: Cascade)
  session   Session  @relation("SessionPhotoUpvotes", fields: [sessionId], references: [id])

  @@unique([photoId, sessionId], map: "idx_photo_upvotes_unique")
  @@index([photoId], map: "idx_photo_upvotes_photo")
  @@map("photo_upvotes")
}

model PhotoComment {
  id           Int       @id @default(autoincrement())
  photoId      Int       @map("photo_id")
  sessionId    String    @map("session_id")
  userId       String?   @map("user_id")
  content      String    @db.Text
  createdAt    DateTime  @default(now()) @map("created_at")
  safetyStatus String    @default("approved") @map("safety_status")
  isHidden     Boolean   @default(false) @map("is_hidden")

  photo        Photo     @relation(fields: [photoId], references: [id], onDelete: Cascade)
  session      Session   @relation("SessionPhotoComments", fields: [sessionId], references: [id])
  user         User?     @relation("PhotoCommentUser", fields: [userId], references: [id])

  @@index([photoId, createdAt], map: "idx_photo_comments_photo_date")
  @@index([sessionId], map: "idx_photo_comments_session")
  @@map("photo_comments")
}

model PhotoView {
  id            Int       @id @default(autoincrement())
  photoId       Int       @map("photo_id")
  sessionId     String?   @map("session_id")
  userId        String?   @map("user_id")
  ipAddressHash String?   @map("ip_address_hash")
  country       String?   @db.VarChar(2)        // ISO 3166-1 alpha-2
  region        String?   @db.VarChar(100)
  city          String?   @db.VarChar(100)
  latitude      Float?
  longitude     Float?
  viewedAt      DateTime  @default(now()) @map("viewed_at")

  photo         Photo     @relation(fields: [photoId], references: [id], onDelete: Cascade)

  @@index([photoId, viewedAt], map: "idx_photo_views_photo_date")
  @@index([sessionId], map: "idx_photo_views_session")
  @@index([country, viewedAt], map: "idx_photo_views_country_date")
  @@map("photo_views")
}

model PhotoReaction {
  id           Int       @id @default(autoincrement())
  photoId      Int       @map("photo_id")
  sessionId    String    @map("session_id")
  reactionType String    @map("reaction_type")   // like, love, laugh, wow, sad, angry
  createdAt    DateTime  @default(now()) @map("created_at")

  photo        Photo     @relation(fields: [photoId], references: [id], onDelete: Cascade)
  session      Session   @relation("SessionPhotoReactions", fields: [sessionId], references: [id])

  @@unique([photoId, sessionId, reactionType], map: "idx_photo_reactions_unique")
  @@index([photoId], map: "idx_photo_reactions_photo")
  @@map("photo_reactions")
}

// ============================================================================
// DOCS COMMENTS (Gitea Issues-backed)
// ============================================================================

enum DocsCommentStatus {
  PENDING
  APPROVED
  REJECTED
}

model DocsComment {
  id                String             @id @default(cuid())
  pagePath          String
  giteaIssueNumber  Int
  giteaCommentId    BigInt
  authorName        String
  authorEmail       String?
  status            DocsCommentStatus  @default(PENDING)
  reviewedAt        DateTime?
  reviewedBy        String?
  createdAt         DateTime           @default(now())
  updatedAt         DateTime           @updatedAt

  @@index([pagePath, status])
  @@index([status, createdAt])
  @@index([giteaCommentId])
  @@map("docs_comments")
}

// ============================================================================
// SMS CAMPAIGNS
// ============================================================================

enum SmsContactListStatus {
  ACTIVE
  ARCHIVED
}

enum SmsCampaignStatus {
  DRAFT
  RUNNING
  PAUSED
  COMPLETED
  FAILED
}

enum SmsMessageDirection {
  OUTBOUND
  INBOUND
}

enum SmsMessageStatus {
  PENDING
  SENT
  FAILED
  DELIVERED
}

enum SmsResponseType {
  POSITIVE
  NEGATIVE
  QUESTION
  OPT_OUT
  NEUTRAL
}

enum SmsConversationStatus {
  ACTIVE
  OPTED_OUT
  CLOSED
}

model SmsContactList {
  id               String               @id @default(cuid())
  name             String
  originalFilename String?
  totalContacts    Int                  @default(0)
  status           SmsContactListStatus @default(ACTIVE)
  createdByUserId  String?
  createdByUser    User?                @relation("SmsContactListCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  createdAt        DateTime             @default(now())
  updatedAt        DateTime             @updatedAt

  entries          SmsContactListEntry[]
  campaigns        SmsCampaign[]

  @@map("sms_contact_lists")
}

model SmsContactListEntry {
  id           String         @id @default(cuid())
  listId       String
  list         SmsContactList @relation(fields: [listId], references: [id], onDelete: Cascade)
  phone        String
  name         String?
  email        String?
  customFields Json?          // Arbitrary key-value pairs from CSV columns
  createdAt    DateTime       @default(now())

  @@unique([listId, phone])
  @@index([listId])
  @@index([phone])
  @@map("sms_contact_list_entries")
}

model SmsCampaign {
  id                  String           @id @default(cuid())
  name                String
  messageTemplate     String           @db.Text
  status              SmsCampaignStatus @default(DRAFT)
  totalRecipients     Int              @default(0)
  totalSent           Int              @default(0)
  totalFailed         Int              @default(0)
  totalResponded      Int              @default(0)
  delayBetweenMs      Int              @default(3000)
  startedAt           DateTime?
  completedAt         DateTime?

  // Relations
  contactListId       String
  contactList         SmsContactList   @relation(fields: [contactListId], references: [id])
  advocacyCampaignId  String?
  advocacyCampaign    Campaign?        @relation("SmsCampaigns", fields: [advocacyCampaignId], references: [id], onDelete: SetNull)
  createdByUserId     String?
  createdByUser       User?            @relation("SmsCampaignCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  createdAt           DateTime         @default(now())
  updatedAt           DateTime         @updatedAt

  recipients          SmsCampaignRecipient[]
  messages            SmsMessage[]
  conversations       SmsConversation[]

  @@index([status])
  @@index([contactListId])
  @@index([advocacyCampaignId])
  @@map("sms_campaigns")
}

model SmsCampaignRecipient {
  id           String           @id @default(cuid())
  campaignId   String
  campaign     SmsCampaign      @relation(fields: [campaignId], references: [id], onDelete: Cascade)
  phone        String
  name         String?
  status       SmsMessageStatus @default(PENDING)
  sentAt       DateTime?
  errorMessage String?
  createdAt    DateTime         @default(now())

  @@index([campaignId, status])
  @@index([phone])
  @@map("sms_campaign_recipients")
}

model SmsMessage {
  id             String              @id @default(cuid())
  phone          String
  message        String              @db.Text
  direction      SmsMessageDirection
  status         SmsMessageStatus    @default(PENDING)
  connectionType String?             // e.g. "termux"

  // Campaign context (nullable for ad-hoc messages)
  campaignId     String?
  campaign       SmsCampaign?        @relation(fields: [campaignId], references: [id], onDelete: SetNull)
  conversationId String?
  conversation   SmsConversation?    @relation(fields: [conversationId], references: [id], onDelete: SetNull)

  // Response classification (for inbound messages)
  responseType   SmsResponseType?
  isRead         Boolean             @default(false)
  sentAt         DateTime            @default(now())

  @@index([phone])
  @@index([campaignId])
  @@index([conversationId])
  @@index([direction, sentAt])
  @@map("sms_messages")
}

model SmsConversation {
  id             String                 @id @default(cuid())
  phone          String
  contactName    String?
  campaignId     String?
  campaign       SmsCampaign?           @relation(fields: [campaignId], references: [id], onDelete: SetNull)
  contactId      String?
  contact        Contact?               @relation("ContactSmsConversations", fields: [contactId], references: [id], onDelete: SetNull)
  status         SmsConversationStatus  @default(ACTIVE)
  totalMessages  Int                    @default(0)
  totalResponses Int                    @default(0)
  unreadCount    Int                    @default(0)
  lastMessageAt  DateTime?
  lastResponseAt DateTime?
  notes          String?                @db.Text
  tags           Json?                  // String array
  createdAt      DateTime               @default(now())
  updatedAt      DateTime               @updatedAt

  messages       SmsMessage[]

  @@unique([phone, campaignId])
  @@index([status])
  @@index([lastMessageAt])
  @@index([contactId])
  @@map("sms_conversations")
}

model SmsMessageTemplate {
  id              String   @id @default(cuid())
  name            String
  template        String   @db.Text
  description     String?
  category        String?
  isFavorite      Boolean  @default(false)
  usageCount      Int      @default(0)
  createdByUserId String?
  createdByUser   User?    @relation("SmsTemplateCreator", fields: [createdByUserId], references: [id], onDelete: SetNull)
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt

  @@map("sms_message_templates")
}

model SmsDeviceStatus {
  id             String   @id @default(cuid())
  isConnected    Boolean  @default(false)
  connectionType String?
  batteryLevel   Int?
  batteryStatus  String?
  totalSent      Int      @default(0)
  lastCheckedAt  DateTime @default(now())

  @@map("sms_device_status")
}

// ============================================================================
// PEOPLE CRM
// ============================================================================

enum ContactSource {
  USER
  ADDRESS_OCCUPANT
  CAMPAIGN_SENDER
  SHIFT_SIGNUP
  SMS_CONTACT
  DONATION
  POLL_VOTE
  PETITION_SIGNER
  MANUAL
}

enum ConnectionType {
  HOUSEHOLD
  FAMILY
  COLLEAGUE
  REFERRED_BY
  CUSTOM
}

enum ContactActivityType {
  EMAIL_SENT
  RESPONSE_SUBMITTED
  SHIFT_SIGNUP
  CANVASS_VISIT
  DONATION
  PURCHASE
  SMS_SENT
  SMS_RECEIVED
  VIDEO_VIEW
  NOTE_ADDED
  CONTACT_MERGED
  PROFILE_SELF_EDIT
  PROFILE_PHOTO_UPDATED
  USER_LOGIN
  POLL_VOTED
  PETITION_SIGNED
}

model Contact {
  id              String        @id @default(cuid())
  displayName     String
  firstName       String?
  lastName        String?
  email           String?
  phone           String?
  pronouns        String?

  // CRM data
  tags            Json          @default("[]") // String array
  notes           String?       @db.Text
  supportLevel    SupportLevel?
  signRequested   Boolean       @default(false)

  // Consent
  emailOptOut     Boolean       @default(false)
  smsOptOut       Boolean       @default(false)
  doNotContact    Boolean       @default(false)

  // Self-service profile
  profileToken          String?       @unique  // Random hex token for public access
  profileTokenExpiresAt DateTime?              // null = never expires
  profilePasswordHash   String?                // bcrypt hash; null = no password
  coverPhotoPath        String?                // Path to processed cover photo on disk
  lastSelfEditAt        DateTime?              // Track last self-service edit

  // Source tracking
  primarySource   ContactSource @default(MANUAL)

  // Links to existing models
  userId          String?       @unique
  user            User?         @relation("UserContact", fields: [userId], references: [id], onDelete: SetNull)

  // Merge support
  mergedIntoId    String?
  mergedInto      Contact?      @relation("ContactMerge", fields: [mergedIntoId], references: [id], onDelete: SetNull)
  mergedContacts  Contact[]     @relation("ContactMerge")

  // Audit
  createdByUserId String?
  createdAt       DateTime      @default(now())
  updatedAt       DateTime      @updatedAt

  // Relations
  addresses          ContactAddress[]
  emails             ContactEmail[]
  phones             ContactPhone[]
  connectionsFrom    ContactConnection[] @relation("ConnectionFrom")
  connectionsTo      ContactConnection[] @relation("ConnectionTo")
  activities         ContactActivity[]
  smsConversations   SmsConversation[]   @relation("ContactSmsConversations")
  pollVotes          SchedulingPollVote[] @relation("PollVoteContact")
  strawPollVotes     StrawPollVote[]      @relation("StrawPollVoteContact")
  participantNeeds   ParticipantNeeds?    @relation("ContactParticipantNeeds")
  petitionSignatures PetitionSignature[]  @relation("PetitionSignatureContact")

  @@index([email])
  @@index([phone])
  @@index([displayName])
  @@index([primarySource])
  @@index([mergedIntoId])
  @@map("contacts")
}

model CrmTag {
  id              String    @id @default(cuid())
  name            String    @unique
  slug            String    @unique
  description     String?
  color           String?   // Hex, e.g. "#1890ff"
  listmonkListId  Int?      // Corresponding Listmonk list ID
  contactCount    Int       @default(0) // Denormalized count
  createdByUserId String?
  createdAt       DateTime  @default(now())
  updatedAt       DateTime  @updatedAt

  @@index([name])
  @@map("crm_tags")
}

model ContactAddress {
  id         String   @id @default(cuid())
  contactId  String
  contact    Contact  @relation(fields: [contactId], references: [id], onDelete: Cascade)
  addressId  String
  address    Address  @relation(fields: [addressId], references: [id], onDelete: Cascade)
  isPrimary  Boolean  @default(false)
  createdAt  DateTime @default(now())

  @@unique([contactId, addressId])
  @@index([addressId])
  @@map("contact_addresses")
}

model ContactEmail {
  id         String   @id @default(cuid())
  contactId  String
  contact    Contact  @relation(fields: [contactId], references: [id], onDelete: Cascade)
  email      String
  label      String?  // "Personal", "Work", "Campaign", etc.
  isPrimary  Boolean  @default(false)
  createdAt  DateTime @default(now())

  @@unique([contactId, email])
  @@index([email])
  @@index([contactId])
  @@map("contact_emails")
}

model ContactPhone {
  id         String   @id @default(cuid())
  contactId  String
  contact    Contact  @relation(fields: [contactId], references: [id], onDelete: Cascade)
  phone      String
  label      String?  // "Mobile", "Home", "Work", etc.
  isPrimary  Boolean  @default(false)
  createdAt  DateTime @default(now())

  @@unique([contactId, phone])
  @@index([phone])
  @@index([contactId])
  @@map("contact_phones")
}

model ContactConnection {
  id              String         @id @default(cuid())
  fromContactId   String
  fromContact     Contact        @relation("ConnectionFrom", fields: [fromContactId], references: [id], onDelete: Cascade)
  toContactId     String
  toContact       Contact        @relation("ConnectionTo", fields: [toContactId], references: [id], onDelete: Cascade)
  type            ConnectionType
  label           String?
  notes           String?        @db.Text
  isBidirectional Boolean        @default(true)
  createdAt       DateTime       @default(now())

  @@unique([fromContactId, toContactId, type])
  @@index([toContactId])
  @@map("contact_connections")
}

model ContactActivity {
  id          String              @id @default(cuid())
  contactId   String
  contact     Contact             @relation(fields: [contactId], references: [id], onDelete: Cascade)
  type        ContactActivityType
  title       String
  description String?             @db.Text
  metadata    Json?
  occurredAt  DateTime            @default(now())
  createdAt   DateTime            @default(now())

  @@index([contactId, occurredAt(sort: Desc)])
  @@map("contact_activities")
}

// ============================================================================
// MEETINGS (JITSI)
// ============================================================================

model Meeting {
  id              String    @id @default(cuid())
  slug            String    @unique
  title           String
  description     String?
  jitsiRoom       String    @unique @map("jitsi_room")
  isActive        Boolean   @default(true) @map("is_active")
  createdByUserId String    @map("created_by_user_id")
  createdBy       User      @relation("MeetingCreator", fields: [createdByUserId], references: [id])
  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")
  startTime       DateTime? @map("start_time")
  endTime         DateTime? @map("end_time")

  // Reverse relations (one-to-one)
  shift           Shift?         @relation("ShiftMeeting")
  group           SocialGroup?   @relation("GroupMeeting")
  ticketedEvent   TicketedEvent? @relation("EventMeeting")

  @@map("meetings")
}

// ============================================================================
// SCHEDULING POLLS (Meeting Planner)
// ============================================================================

enum SchedulingPollStatus {
  OPEN
  CLOSED
  FINALIZED
  CANCELLED
}

enum PollVoteValue {
  YES
  IF_NEED_BE
  NO
}

model SchedulingPoll {
  id                  String                @id @default(cuid())
  slug                String                @unique
  title               String
  description         String?               @db.Text
  location            String?
  status              SchedulingPollStatus  @default(OPEN)
  timezone            String                @default("America/Edmonton")
  finalizedOptionId   String?               @unique @map("finalized_option_id")
  finalizedOption     SchedulingPollOption? @relation("FinalizedOption", fields: [finalizedOptionId], references: [id], onDelete: SetNull)
  convertedShiftId    String?               @unique @map("converted_shift_id")
  convertedShift      Shift?                @relation("PollConvertedShift", fields: [convertedShiftId], references: [id], onDelete: SetNull)
  convertedGancioEventId Int?               @map("converted_gancio_event_id")
  convertedCalendarItemId String?           @map("converted_calendar_item_id")
  votingDeadline      DateTime?             @map("voting_deadline")
  autoFinalize        Boolean               @default(false) @map("auto_finalize")
  autoFinalizeThreshold Int?                @map("auto_finalize_threshold")
  autoConvertToCalendar Boolean             @default(false) @map("auto_convert_to_calendar")
  autoConvertToGancio Boolean               @default(false) @map("auto_convert_to_gancio")
  autoConvertToShift  Boolean               @default(false) @map("auto_convert_to_shift")
  tieBreaker          String                @default("earliest") @map("tie_breaker")
  autoEnrollVoters    Boolean               @default(true) @map("auto_enroll_voters")
  autoFinalizeJobId   String?               @map("auto_finalize_job_id")
  allowAnonymous      Boolean               @default(true) @map("allow_anonymous")
  isPrivate           Boolean               @default(false) @map("is_private")
  notifyOnVote        Boolean               @default(true) @map("notify_on_vote")
  createdByUserId     String                @map("created_by_user_id")
  createdBy           User                  @relation("PollCreator", fields: [createdByUserId], references: [id])
  createdAt           DateTime              @default(now()) @map("created_at")
  updatedAt           DateTime              @updatedAt @map("updated_at")

  options             SchedulingPollOption[] @relation("PollOptions")
  votes               SchedulingPollVote[]   @relation("PollVotes")
  comments            SchedulingPollComment[] @relation("PollComments")
  agenda              MeetingAgenda?         @relation("PollAgenda")

  @@index([createdByUserId])
  @@index([status])
  @@map("scheduling_polls")
}

model SchedulingPollOption {
  id        String   @id @default(cuid())
  pollId    String   @map("poll_id")
  poll      SchedulingPoll @relation("PollOptions", fields: [pollId], references: [id], onDelete: Cascade)
  date      DateTime @db.Date
  startTime String   @map("start_time")  // HH:MM
  endTime   String   @map("end_time")    // HH:MM
  sortOrder Int      @default(0) @map("sort_order")

  votes     SchedulingPollVote[] @relation("OptionVotes")

  // Reverse 1:1 for finalized option
  finalizedForPoll SchedulingPoll? @relation("FinalizedOption")

  @@index([pollId])
  @@map("scheduling_poll_options")
}

model SchedulingPollVote {
  id         String        @id @default(cuid())
  pollId     String        @map("poll_id")
  poll       SchedulingPoll @relation("PollVotes", fields: [pollId], references: [id], onDelete: Cascade)
  optionId   String        @map("option_id")
  option     SchedulingPollOption @relation("OptionVotes", fields: [optionId], references: [id], onDelete: Cascade)
  userId     String?       @map("user_id")
  user       User?         @relation("PollVoter", fields: [userId], references: [id], onDelete: SetNull)
  voterName  String        @map("voter_name")
  voterEmail String?       @map("voter_email")
  voterToken String?       @map("voter_token")  // anonymous edit access (cuid)
  contactId  String?       @map("contact_id")
  contact    Contact?      @relation("PollVoteContact", fields: [contactId], references: [id], onDelete: SetNull)
  value      PollVoteValue
  createdAt  DateTime      @default(now()) @map("created_at")
  updatedAt  DateTime      @updatedAt @map("updated_at")

  @@unique([optionId, userId])
  @@unique([optionId, voterToken])
  @@index([pollId])
  @@index([contactId])
  @@map("scheduling_poll_votes")
}

model SchedulingPollComment {
  id         String   @id @default(cuid())
  pollId     String   @map("poll_id")
  poll       SchedulingPoll @relation("PollComments", fields: [pollId], references: [id], onDelete: Cascade)
  userId     String?  @map("user_id")
  user       User?    @relation("PollCommenter", fields: [userId], references: [id], onDelete: SetNull)
  authorName String   @map("author_name")
  content    String   @db.Text
  createdAt  DateTime @default(now()) @map("created_at")

  @@index([pollId])
  @@map("scheduling_poll_comments")
}

// ============================================================================
// SOCIAL: INVITE / REFERRAL SYSTEM
// ============================================================================

model InviteCode {
  id              String    @id @default(cuid())
  code            String    @unique
  createdByUserId String    @map("created_by_user_id")
  maxUses         Int       @default(0) @map("max_uses") // 0 = unlimited
  usedCount       Int       @default(0) @map("used_count")
  expiresAt       DateTime? @map("expires_at")
  isActive        Boolean   @default(true) @map("is_active")
  note            String?
  createdAt       DateTime  @default(now()) @map("created_at")

  // Relations
  createdBy       User      @relation("InviteCodesCreated", fields: [createdByUserId], references: [id])
  referrals       Referral[] @relation("InviteCodeReferrals")

  @@index([code], map: "idx_invite_codes_code")
  @@index([createdByUserId], map: "idx_invite_codes_created_by")
  @@map("invite_codes")
}

model Referral {
  id              Int       @id @default(autoincrement())
  referrerId      String    @map("referrer_id")
  referredUserId  String    @unique @map("referred_user_id")
  inviteCodeId    String?   @map("invite_code_id")
  referralSource  String?   @map("referral_source")
  completedAt     DateTime  @default(now()) @map("completed_at")

  // Relations
  referrer        User      @relation("ReferralsMade", fields: [referrerId], references: [id])
  referredUser    User      @relation("ReferredBy", fields: [referredUserId], references: [id])
  inviteCode      InviteCode? @relation("InviteCodeReferrals", fields: [inviteCodeId], references: [id])

  @@index([referrerId], map: "idx_referrals_referrer")
  @@map("referrals")
}

// ============================================================================
// SOCIAL: IMPACT STORIES / CAMPAIGN VICTORIES
// ============================================================================

enum ImpactStoryType {
  MILESTONE
  VICTORY
  RESPONSE
  CUSTOM
}

enum ImpactStoryStatus {
  DRAFT
  PUBLISHED
  ARCHIVED
}

model ImpactStory {
  id              String           @id @default(cuid())
  campaignId      String           @map("campaign_id")
  type            ImpactStoryType
  status          ImpactStoryStatus @default(DRAFT)
  title           String
  body            String           @db.Text
  coverImageUrl   String?          @map("cover_image_url")
  milestoneValue  Int?             @map("milestone_value")
  milestoneMetric String?          @map("milestone_metric")
  createdByUserId String?          @map("created_by_user_id")
  publishedAt     DateTime?        @map("published_at")
  createdAt       DateTime         @default(now()) @map("created_at")
  updatedAt       DateTime         @updatedAt @map("updated_at")

  // Relations
  campaign        Campaign         @relation("CampaignStories", fields: [campaignId], references: [id])
  createdBy       User?            @relation("ImpactStoryCreator", fields: [createdByUserId], references: [id])

  @@index([campaignId], map: "idx_impact_stories_campaign")
  @@index([status], map: "idx_impact_stories_status")
  @@index([type], map: "idx_impact_stories_type")
  @@map("impact_stories")
}

model CampaignMilestone {
  id             Int      @id @default(autoincrement())
  campaignId     String   @map("campaign_id")
  metric         String   // "emails_sent", "verified_responses"
  threshold      Int
  reachedAt      DateTime @default(now()) @map("reached_at")
  storyGenerated Boolean  @default(false) @map("story_generated")

  // Relations
  campaign       Campaign @relation("CampaignMilestones", fields: [campaignId], references: [id])

  @@unique([campaignId, metric, threshold])
  @@map("campaign_milestones")
}

// ============================================================================
// SOCIAL: VOLUNTEER SPOTLIGHT / WALL OF FAME
// ============================================================================

enum SpotlightStatus {
  NOMINATED
  APPROVED
  FEATURED
  ARCHIVED
}

model VolunteerSpotlight {
  id                String          @id @default(cuid())
  userId            String          @map("user_id")
  status            SpotlightStatus @default(NOMINATED)
  headline          String?
  story             String?         @db.Text
  featuredMonth     String?         @map("featured_month") // "2026-03"
  nominatedByUserId String?         @map("nominated_by_user_id")
  approvedByUserId  String?         @map("approved_by_user_id")
  approvedAt        DateTime?       @map("approved_at")
  createdAt         DateTime        @default(now()) @map("created_at")
  updatedAt         DateTime        @updatedAt @map("updated_at")

  // Relations
  user              User            @relation("SpotlightUser", fields: [userId], references: [id])
  nominatedBy       User?           @relation("SpotlightNominator", fields: [nominatedByUserId], references: [id])
  approvedBy        User?           @relation("SpotlightApprover", fields: [approvedByUserId], references: [id])

  @@index([userId], map: "idx_volunteer_spotlights_user")
  @@index([status], map: "idx_volunteer_spotlights_status")
  @@index([featuredMonth], map: "idx_volunteer_spotlights_month")
  @@map("volunteer_spotlights")
}

// ============================================================================
// SOCIAL: TEAM CHALLENGES
// ============================================================================

enum ChallengeStatus {
  DRAFT
  UPCOMING
  ACTIVE
  COMPLETED
  CANCELLED
}

enum ChallengeMetric {
  DOORS_KNOCKED
  EMAILS_SENT
  SHIFTS_ATTENDED
  RESPONSES_SUBMITTED
  REFERRALS_MADE
}

model Challenge {
  id              String          @id @default(cuid())
  title           String
  description     String?         @db.Text
  metric          ChallengeMetric
  status          ChallengeStatus @default(DRAFT)
  startsAt        DateTime        @map("starts_at")
  endsAt          DateTime        @map("ends_at")
  minTeamSize     Int             @default(2) @map("min_team_size")
  maxTeamSize     Int             @default(10) @map("max_team_size")
  maxTeams        Int?            @map("max_teams") // null = unlimited
  createdByUserId String          @map("created_by_user_id")
  createdAt       DateTime        @default(now()) @map("created_at")
  updatedAt       DateTime        @updatedAt @map("updated_at")

  // Relations
  createdBy       User            @relation("ChallengesCreated", fields: [createdByUserId], references: [id])
  teams           ChallengeTeam[] @relation("ChallengeTeams")

  @@index([status], map: "idx_challenges_status")
  @@index([startsAt], map: "idx_challenges_starts_at")
  @@map("challenges")
}

model ChallengeTeam {
  id              String              @id @default(cuid())
  challengeId     String              @map("challenge_id")
  name            String
  captainUserId   String              @map("captain_user_id")
  score           Int                 @default(0)
  lastScoredAt    DateTime?           @map("last_scored_at")
  createdAt       DateTime            @default(now()) @map("created_at")

  // Relations
  challenge       Challenge           @relation("ChallengeTeams", fields: [challengeId], references: [id])
  captain         User                @relation("ChallengeTeamsCaptained", fields: [captainUserId], references: [id])
  members         ChallengeTeamMember[] @relation("ChallengeTeamMembers")

  @@unique([challengeId, name])
  @@index([challengeId], map: "idx_challenge_teams_challenge")
  @@index([score], map: "idx_challenge_teams_score")
  @@map("challenge_teams")
}

model ChallengeTeamMember {
  id              Int             @id @default(autoincrement())
  teamId          String          @map("team_id")
  userId          String          @map("user_id")
  score           Int             @default(0)
  joinedAt        DateTime        @default(now()) @map("joined_at")

  // Relations
  team            ChallengeTeam   @relation("ChallengeTeamMembers", fields: [teamId], references: [id], onDelete: Cascade)
  user            User            @relation("ChallengeParticipations", fields: [userId], references: [id])

  @@unique([teamId, userId])
  @@index([userId], map: "idx_challenge_team_members_user")
  @@map("challenge_team_members")
}

// ============================================================================
// TICKETED EVENTS
// ============================================================================

enum TicketedEventStatus {
  DRAFT
  PENDING_APPROVAL
  PUBLISHED
  CANCELLED
  COMPLETED
}

enum TicketedEventVisibility {
  PUBLIC
  UNLISTED
  PRIVATE
}

enum TicketTierType {
  PAID
  FREE
  DONATION
}

enum TicketStatus {
  VALID
  CHECKED_IN
  CANCELLED
  REFUNDED
}

enum EventFormat {
  IN_PERSON
  ONLINE
  HYBRID
}

model TicketedEvent {
  id                String                  @id @default(cuid())
  slug              String                  @unique
  title             String
  description       String?                 @db.Text
  richDescription   String?                 @db.Text @map("rich_description")

  // Schedule
  date              DateTime                @db.Date
  startTime         String                  @map("start_time")
  endTime           String                  @map("end_time")
  doorsOpenTime     String?                 @map("doors_open_time")

  // Venue
  venueName         String?                 @map("venue_name")
  venueAddress      String?                 @map("venue_address")
  latitude          Decimal?                @db.Decimal(10, 7)
  longitude         Decimal?                @db.Decimal(10, 7)

  // Status
  status            TicketedEventStatus     @default(DRAFT)
  visibility        TicketedEventVisibility  @default(PUBLIC)
  inviteCode        String?                 @unique @map("invite_code")

  // Media
  coverImageUrl     String?                 @map("cover_image_url")

  // Capacity
  maxAttendees      Int?                    @map("max_attendees")
  currentAttendees  Int                     @default(0) @map("current_attendees")

  // Gancio sync
  gancioEventId     Int?                    @map("gancio_event_id")

  // Format & Meeting
  eventFormat       EventFormat             @default(IN_PERSON) @map("event_format")
  meetingId         String?                 @unique @map("meeting_id")

  // Creator
  createdByUserId   String                  @map("created_by_user_id")
  organizerName     String?                 @map("organizer_name")
  organizerEmail    String?                 @map("organizer_email")

  createdAt         DateTime                @default(now()) @map("created_at")
  updatedAt         DateTime                @updatedAt @map("updated_at")

  // Relations
  createdBy         User                    @relation("EventCreator", fields: [createdByUserId], references: [id])
  meeting           Meeting?                @relation("EventMeeting", fields: [meetingId], references: [id], onDelete: SetNull)
  ticketTiers       TicketTier[]            @relation("EventTiers")
  tickets           Ticket[]                @relation("EventTickets")
  checkIns          CheckIn[]               @relation("EventCheckIns")

  @@index([status], map: "idx_ticketed_events_status")
  @@index([date], map: "idx_ticketed_events_date")
  @@index([visibility], map: "idx_ticketed_events_visibility")
  @@index([createdByUserId], map: "idx_ticketed_events_creator")
  @@map("ticketed_events")
}

model TicketTier {
  id              String          @id @default(cuid())
  eventId         String          @map("event_id")
  name            String
  description     String?
  tierType        TicketTierType  @map("tier_type")
  priceCAD        Int             @default(0) @map("price_cad")   // In cents
  minDonationCAD  Int?            @map("min_donation_cad")        // In cents
  maxQuantity     Int?            @map("max_quantity")
  soldCount       Int             @default(0) @map("sold_count")
  reservedCount   Int             @default(0) @map("reserved_count")  // Pending checkout sessions
  maxPerOrder     Int             @default(10) @map("max_per_order")
  salesStartAt    DateTime?       @map("sales_start_at")
  salesEndAt      DateTime?       @map("sales_end_at")
  sortOrder       Int             @default(0) @map("sort_order")
  isActive        Boolean         @default(true) @map("is_active")

  createdAt       DateTime        @default(now()) @map("created_at")
  updatedAt       DateTime        @updatedAt @map("updated_at")

  // Relations
  event           TicketedEvent   @relation("EventTiers", fields: [eventId], references: [id], onDelete: Cascade)
  tickets         Ticket[]        @relation("TierTickets")

  @@index([eventId], map: "idx_ticket_tiers_event")
  @@map("ticket_tiers")
}

model Ticket {
  id                String        @id @default(cuid())
  ticketCode        String        @unique @map("ticket_code")
  tokenHash         String        @unique @map("token_hash")
  eventId           String        @map("event_id")
  tierId            String        @map("tier_id")
  orderId           String?       @map("order_id")
  holderEmail       String        @map("holder_email")
  holderName        String?       @map("holder_name")
  userId            String?       @map("user_id")
  status            TicketStatus  @default(VALID)
  checkedInAt       DateTime?     @map("checked_in_at")
  checkedInByUserId String?       @map("checked_in_by_user_id")
  issuedAt          DateTime      @default(now()) @map("issued_at")
  updatedAt         DateTime      @updatedAt @map("updated_at")

  // Relations
  event             TicketedEvent @relation("EventTickets", fields: [eventId], references: [id])
  tier              TicketTier    @relation("TierTickets", fields: [tierId], references: [id])
  order             Order?        @relation("TicketOrder", fields: [orderId], references: [id])
  holder            User?         @relation("TicketHolder", fields: [userId], references: [id])
  checkIns          CheckIn[]     @relation("TicketCheckIns")

  @@index([eventId], map: "idx_tickets_event")
  @@index([tierId], map: "idx_tickets_tier")
  @@index([orderId], map: "idx_tickets_order")
  @@index([holderEmail], map: "idx_tickets_holder_email")
  @@index([status], map: "idx_tickets_status")
  @@map("tickets")
}

model CheckIn {
  id                String        @id @default(cuid())
  ticketId          String        @map("ticket_id")
  eventId           String        @map("event_id")
  checkedInByUserId String?       @map("checked_in_by_user_id")
  method            String        // "QR" | "MANUAL" | "CODE"
  checkedInAt       DateTime      @default(now()) @map("checked_in_at")
  notes             String?

  // Relations
  ticket            Ticket        @relation("TicketCheckIns", fields: [ticketId], references: [id])
  event             TicketedEvent @relation("EventCheckIns", fields: [eventId], references: [id])
  checkedInBy       User?         @relation("CheckInUser", fields: [checkedInByUserId], references: [id])

  @@index([eventId], map: "idx_checkins_event")
  @@index([ticketId], map: "idx_checkins_ticket")
  @@map("check_ins")
}

// ============================================================================
// SOCIAL CALENDAR
// ============================================================================

enum CalendarLayerType {
  SYSTEM
  USER
  EXTERNAL
}

enum CalendarSystemType {
  SHIFTS
  TICKETS
  POLLS
  PUBLIC_EVENTS
}

enum CalendarVisibility {
  PRIVATE
  FRIENDS
  PUBLIC
}

enum CalendarItemType {
  EVENT
  TIME_BLOCK
  REMINDER
}

enum CalendarBusyStatus {
  BUSY
  TENTATIVE
  FREE
}

enum CalendarShowDetailsTo {
  NOBODY
  FRIENDS
  EVERYONE
}

enum CalendarItemSource {
  MANUAL
  ICS_FEED
  POLL
  SHIFT
  MEETING
  TICKETED_EVENT
}

enum CalendarRecurrenceFrequency {
  DAILY
  WEEKLY
  BIWEEKLY
  MONTHLY
}

enum CalendarFeedStatus {
  OK
  ERROR
  PENDING
}

enum CalendarFeedInterval {
  FIFTEEN_MIN
  HOURLY
  SIX_HOUR
  DAILY
}

enum SharedViewType {
  MANUAL
  ROLE_BASED
}

enum SharedViewScope {
  MEMBERS
  PUBLIC
}

enum SharedViewMemberStatus {
  INVITED
  ACCEPTED
  DECLINED
}

model CalendarLayer {
  id          String             @id @default(cuid())
  userId      String             @map("user_id")
  name        String
  layerType   CalendarLayerType  @map("layer_type")
  systemType  CalendarSystemType? @map("system_type")
  color       String             @default("#1890ff")
  visibility  CalendarVisibility @default(PRIVATE)
  isEnabled   Boolean            @default(true) @map("is_enabled")
  sortOrder   Int                @default(0) @map("sort_order")
  createdAt   DateTime           @default(now()) @map("created_at")
  updatedAt   DateTime           @updatedAt @map("updated_at")

  // Relations
  user        User               @relation("CalendarLayerOwner", fields: [userId], references: [id], onDelete: Cascade)
  items       CalendarItem[]     @relation("CalendarLayerItems")
  feed        CalendarFeed?      @relation("CalendarFeedLayer")

  @@unique([userId, systemType], map: "idx_calendar_layers_user_system")
  @@index([userId], map: "idx_calendar_layers_user")
  @@map("calendar_layers")
}

model CalendarItem {
  id              String                      @id @default(cuid())
  userId          String                      @map("user_id")
  layerId         String                      @map("layer_id")
  title           String
  description     String?                     @db.Text
  date            DateTime                    @db.Date
  startTime       String                      @map("start_time")  // HH:MM
  endTime         String                      @map("end_time")    // HH:MM
  isAllDay        Boolean                     @default(false) @map("is_all_day")
  itemType        CalendarItemType            @default(EVENT) @map("item_type")
  location        String?
  color           String?
  visibility      CalendarVisibility?         // null = inherit from layer
  busyStatus      CalendarBusyStatus          @default(BUSY) @map("busy_status")
  showDetailsTo   CalendarShowDetailsTo       @default(FRIENDS) @map("show_details_to")

  // Recurrence
  recurrenceRule  Json?                       @map("recurrence_rule")
  recurrenceEnd   DateTime?                   @map("recurrence_end")
  seriesId        String?                     @map("series_id")
  isException     Boolean                     @default(false) @map("is_exception")

  // Source tracking
  sourceType      CalendarItemSource          @default(MANUAL) @map("source_type")
  sourceId        String?                     @map("source_id")

  createdAt       DateTime                    @default(now()) @map("created_at")
  updatedAt       DateTime                    @updatedAt @map("updated_at")

  // Relations
  user            User                        @relation("CalendarItemOwner", fields: [userId], references: [id], onDelete: Cascade)
  layer           CalendarLayer               @relation("CalendarLayerItems", fields: [layerId], references: [id], onDelete: Cascade)

  @@index([userId, date], map: "idx_calendar_items_user_date")
  @@index([layerId, date], map: "idx_calendar_items_layer_date")
  @@index([seriesId], map: "idx_calendar_items_series")
  @@index([sourceType, sourceId], map: "idx_calendar_items_source")
  @@map("calendar_items")
}

model CalendarFeed {
  id              String              @id @default(cuid())
  userId          String              @map("user_id")
  name            String
  url             String
  layerId         String              @unique @map("layer_id")
  refreshInterval CalendarFeedInterval @default(HOURLY) @map("refresh_interval")
  lastFetchedAt   DateTime?           @map("last_fetched_at")
  lastStatus      CalendarFeedStatus  @default(PENDING) @map("last_status")
  lastError       String?             @map("last_error")
  itemCount       Int                 @default(0) @map("item_count")
  createdAt       DateTime            @default(now()) @map("created_at")
  updatedAt       DateTime            @updatedAt @map("updated_at")

  // Relations
  user            User                @relation("CalendarFeedOwner", fields: [userId], references: [id], onDelete: Cascade)
  layer           CalendarLayer       @relation("CalendarFeedLayer", fields: [layerId], references: [id], onDelete: Cascade)

  @@index([userId], map: "idx_calendar_feeds_user")
  @@map("calendar_feeds")
}

model SharedCalendarView {
  id                 String           @id @default(cuid())
  name               String
  description        String?          @db.Text
  ownerId            String           @map("owner_id")
  viewType           SharedViewType   @default(MANUAL) @map("view_type")
  autoIncludeRoles   Json?            @map("auto_include_roles")
  includedLayerTypes Json             @default("[]") @map("included_layer_types")
  shareScope         SharedViewScope  @default(MEMBERS) @map("share_scope")
  shareToken         String?          @unique @map("share_token")
  createdAt          DateTime         @default(now()) @map("created_at")
  updatedAt          DateTime         @updatedAt @map("updated_at")

  // Relations
  owner              User                   @relation("SharedViewOwner", fields: [ownerId], references: [id], onDelete: Cascade)
  members            SharedCalendarMember[] @relation("SharedViewMembers")
  comments           SharedViewComment[]    @relation("SharedViewComments")
  reactions          SharedViewReaction[]   @relation("SharedViewReactions")

  @@index([ownerId], map: "idx_shared_views_owner")
  @@map("shared_calendar_views")
}

model SharedCalendarMember {
  id       String                  @id @default(cuid())
  viewId   String                  @map("view_id")
  userId   String                  @map("user_id")
  status   SharedViewMemberStatus  @default(INVITED)
  color    String                  @default("#1890ff")
  joinedAt DateTime?               @map("joined_at")

  // Relations
  view     SharedCalendarView      @relation("SharedViewMembers", fields: [viewId], references: [id], onDelete: Cascade)
  user     User                    @relation("SharedViewMember", fields: [userId], references: [id], onDelete: Cascade)

  @@unique([viewId, userId], map: "idx_shared_members_view_user")
  @@index([userId], map: "idx_shared_members_user")
  @@map("shared_calendar_members")
}

model SharedViewComment {
  id        String             @id @default(cuid())
  viewId    String             @map("view_id")
  userId    String             @map("user_id")
  itemDate  String             @map("item_date")  // YYYY-MM-DD
  itemId    String?            @map("item_id")     // specific item reference
  content   String             @db.Text
  createdAt DateTime           @default(now()) @map("created_at")

  // Relations
  view      SharedCalendarView @relation("SharedViewComments", fields: [viewId], references: [id], onDelete: Cascade)
  user      User               @relation("SharedViewCommentUser", fields: [userId], references: [id], onDelete: Cascade)

  @@index([viewId, itemDate], map: "idx_shared_comments_view_date")
  @@map("shared_view_comments")
}

model SharedViewReaction {
  id        String             @id @default(cuid())
  viewId    String             @map("view_id")
  userId    String             @map("user_id")
  itemId    String             @map("item_id")
  emoji     String
  createdAt DateTime           @default(now()) @map("created_at")

  // Relations
  view      SharedCalendarView @relation("SharedViewReactions", fields: [viewId], references: [id], onDelete: Cascade)
  user      User               @relation("SharedViewReactionUser", fields: [userId], references: [id], onDelete: Cascade)

  @@unique([viewId, userId, itemId, emoji], map: "idx_shared_reactions_unique")
  @@map("shared_view_reactions")
}

model CalendarExportToken {
  id              String   @id @default(cuid())
  userId          String   @map("user_id")
  token           String   @unique
  includePersonal Boolean  @default(false) @map("include_personal")
  includeLayers   Json?    @map("include_layers")
  createdAt       DateTime @default(now()) @map("created_at")

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

  @@index([userId], map: "idx_calendar_export_tokens_user")
  @@map("calendar_export_tokens")
}

// ============================================================================
// DOCS COLLABORATION
// ============================================================================

model DocCollabState {
  id         String   @id @default(cuid())
  documentId String   @unique @map("document_id")  // file path, e.g. "admin/index.md"
  state      Bytes                                   // Y.Doc binary state
  updatedAt  DateTime @updatedAt @map("updated_at")
  createdAt  DateTime @default(now()) @map("created_at")

  @@map("doc_collab_state")
}

// --- Document Access Policies ---

enum DocShareLinkStatus {
  ACTIVE
  REVOKED
  EXPIRED
}

model DocAccessPolicy {
  id             String   @id @default(cuid())
  documentPath   String   @unique @map("document_path")        // e.g. "admin/index.md" or "guides/" (trailing slash = directory)
  isDirectory    Boolean  @default(false) @map("is_directory")
  allowedEditors Json     @default("[]") @map("allowed_editors") // ["role:CONTENT_ADMIN", "user:clxyz", "all_content_editors"]
  createdById    String   @map("created_by_id")
  createdBy      User     @relation("DocAccessPolicyCreator", fields: [createdById], references: [id], onDelete: Cascade)
  createdAt      DateTime @default(now()) @map("created_at")
  updatedAt      DateTime @updatedAt @map("updated_at")

  @@index([createdById])
  @@map("doc_access_policies")
}

model DocShareLink {
  id            String             @id @default(cuid())
  documentPath  String             @map("document_path")
  shareToken    String             @unique @map("share_token")
  status        DocShareLinkStatus @default(ACTIVE)
  canEdit       Boolean            @default(true) @map("can_edit")
  expiresAt     DateTime?          @map("expires_at")
  maxUses       Int?               @map("max_uses")
  useCount      Int                @default(0) @map("use_count")
  guestName     String?            @map("guest_name")
  createdById   String             @map("created_by_id")
  createdBy     User               @relation("DocShareLinkCreator", fields: [createdById], references: [id], onDelete: Cascade)
  createdAt     DateTime           @default(now()) @map("created_at")
  updatedAt     DateTime           @updatedAt @map("updated_at")

  @@index([documentPath])
  @@index([createdById])
  @@map("doc_share_links")
}

model DocWatch {
  id        String   @id @default(cuid())
  userId    String   @map("user_id")
  filePath  String   @map("file_path")
  user      User     @relation("DocWatcher", fields: [userId], references: [id], onDelete: Cascade)
  createdAt DateTime @default(now()) @map("created_at")

  @@unique([userId, filePath])
  @@index([filePath])
  @@map("doc_watches")
}

// ============================================================================
// PARTICIPANT NEEDS
// ============================================================================

model ParticipantNeeds {
  id                  String   @id @default(cuid())
  userId              String?  @unique @map("user_id")
  user                User?    @relation("UserParticipantNeeds", fields: [userId], references: [id], onDelete: SetNull)
  contactId           String?  @unique @map("contact_id")
  contact             Contact? @relation("ContactParticipantNeeds", fields: [contactId], references: [id], onDelete: SetNull)

  // Accessibility
  needsWheelchair     Boolean  @default(false) @map("needs_wheelchair")
  needsGroundFloor    Boolean  @default(false) @map("needs_ground_floor")
  needsHearingLoop    Boolean  @default(false) @map("needs_hearing_loop")
  needsSignLanguage   Boolean  @default(false) @map("needs_sign_language")
  otherAccessibility  String?  @db.Text @map("other_accessibility")

  // Dietary
  isVegan             Boolean  @default(false) @map("is_vegan")
  isVegetarian        Boolean  @default(false) @map("is_vegetarian")
  isGlutenFree        Boolean  @default(false) @map("is_gluten_free")
  isHalal             Boolean  @default(false) @map("is_halal")
  isKosher            Boolean  @default(false) @map("is_kosher")
  hasNutAllergy       Boolean  @default(false) @map("has_nut_allergy")
  otherDietary        String?  @db.Text @map("other_dietary")

  // Care barriers
  needsChildcare      Boolean  @default(false) @map("needs_childcare")
  childcareDetails    String?  @db.Text @map("childcare_details")
  needsTransportation Boolean  @default(false) @map("needs_transportation")
  transportationNotes String?  @db.Text @map("transportation_notes")

  // Communication
  preferredLanguage   String?  @default("en") @map("preferred_language")
  needsTranslation    Boolean  @default(false) @map("needs_translation")
  translationLanguage String?  @map("translation_language")

  // Consent
  visibilityConsent   String   @default("organizer_only") @map("visibility_consent")

  updatedAt           DateTime @updatedAt @map("updated_at")

  @@map("participant_needs")
}

// ============================================================================
// MEETING AGENDAS & ACTION ITEMS
// ============================================================================

model MeetingAgenda {
  id              String   @id @default(cuid())
  shiftId         String?  @unique @map("shift_id")
  shift           Shift?   @relation("ShiftAgenda", fields: [shiftId], references: [id], onDelete: SetNull)
  pollId          String?  @unique @map("poll_id")
  poll            SchedulingPoll? @relation("PollAgenda", fields: [pollId], references: [id], onDelete: SetNull)

  title           String
  items           Json     @default("[]")  // Array<{ id, title, durationMinutes, presenterUserId, notes, order }>
  status          String   @default("draft")  // "draft" | "active" | "completed"

  minutes         MeetingMinutes?
  actionItems     ActionItem[]

  createdByUserId String   @map("created_by_user_id")
  createdBy       User     @relation("AgendaCreator", fields: [createdByUserId], references: [id])
  createdAt       DateTime @default(now()) @map("created_at")
  updatedAt       DateTime @updatedAt @map("updated_at")

  @@map("meeting_agendas")
}

model MeetingMinutes {
  id               String    @id @default(cuid())
  agendaId         String    @unique @map("agenda_id")
  agenda           MeetingAgenda @relation(fields: [agendaId], references: [id], onDelete: Cascade)

  notes            String    @db.Text
  decisions        Json      @default("[]")  // Array<{ id, text, passed: boolean }>
  attendees        Json      @default("[]")  // Array<{ name, pronouns, userId? }>

  approvedAt       DateTime? @map("approved_at")
  approvedByUserId String?   @map("approved_by_user_id")
  approvedBy       User?     @relation("MinutesApprover", fields: [approvedByUserId], references: [id], onDelete: SetNull)
  createdByUserId  String    @map("created_by_user_id")
  createdBy        User      @relation("MinutesCreator", fields: [createdByUserId], references: [id])
  createdAt        DateTime  @default(now()) @map("created_at")
  updatedAt        DateTime  @updatedAt @map("updated_at")

  @@map("meeting_minutes")
}

model ActionItem {
  id              String    @id @default(cuid())
  agendaId        String?   @map("agenda_id")
  agenda          MeetingAgenda? @relation(fields: [agendaId], references: [id], onDelete: SetNull)

  title           String
  description     String?   @db.Text
  assigneeUserId  String?   @map("assignee_user_id")
  assignee        User?     @relation("ActionItemAssignee", fields: [assigneeUserId], references: [id], onDelete: SetNull)
  dueDate         DateTime? @map("due_date")
  status          String    @default("open")  // "open" | "in_progress" | "done" | "blocked"
  priority        String    @default("normal") // "low" | "normal" | "high" | "urgent"

  completedAt     DateTime? @map("completed_at")
  createdByUserId String    @map("created_by_user_id")
  createdBy       User      @relation("ActionItemCreator", fields: [createdByUserId], references: [id])
  createdAt       DateTime  @default(now()) @map("created_at")
  updatedAt       DateTime  @updatedAt @map("updated_at")

  @@index([assigneeUserId, status])
  @@index([dueDate])
  @@map("action_items")
}

// ============================================================================
// STRAW POLLS
// ============================================================================

enum StrawPollType {
  SINGLE_CHOICE
  YES_NO_ABSTAIN
}

enum StrawPollStatus {
  DRAFT
  ACTIVE
  CLOSED
  ARCHIVED
}

enum StrawPollIdentityMode {
  ANONYMOUS
  TOKEN_GATED
  AUTHENTICATED
  MIXED
}

enum StrawPollResultVisibility {
  LIVE
  AFTER_VOTE
  AFTER_CLOSE
  CREATOR_ONLY
  PUBLIC_ALWAYS
}

model StrawPoll {
  id                String                    @id @default(cuid())
  slug              String                    @unique
  title             String                    @db.VarChar(200)
  description       String?                   @db.Text
  type              StrawPollType
  status            StrawPollStatus           @default(DRAFT)
  identityMode      StrawPollIdentityMode     @default(ANONYMOUS) @map("identity_mode")
  resultVisibility  StrawPollResultVisibility  @default(LIVE) @map("result_visibility")
  allowComments     Boolean                   @default(true) @map("allow_comments")
  closesAt          DateTime?                 @map("closes_at")
  closeThreshold    Int?                      @map("close_threshold")
  autoCloseJobId    String?                   @map("auto_close_job_id")
  isPrivate         Boolean                   @default(false) @map("is_private")

  createdByUserId   String                    @map("created_by_user_id")
  createdBy         User                      @relation("StrawPollCreator", fields: [createdByUserId], references: [id])
  createdAt         DateTime                  @default(now()) @map("created_at")
  updatedAt         DateTime                  @updatedAt @map("updated_at")

  options           StrawPollOption[]
  votes             StrawPollVote[]
  comments          StrawPollComment[]
  challenges        StrawPollChallenge[]

  @@index([createdByUserId])
  @@index([status])
  @@map("straw_polls")
}

model StrawPollOption {
  id        String   @id @default(cuid())
  pollId    String   @map("poll_id")
  poll      StrawPoll @relation(fields: [pollId], references: [id], onDelete: Cascade)
  label     String   @db.VarChar(500)
  sortOrder Int      @default(0) @map("sort_order")

  votes     StrawPollVote[]

  @@index([pollId])
  @@map("straw_poll_options")
}

model StrawPollVote {
  id         String   @id @default(cuid())
  pollId     String   @map("poll_id")
  poll       StrawPoll @relation(fields: [pollId], references: [id], onDelete: Cascade)
  optionId   String   @map("option_id")
  option     StrawPollOption @relation(fields: [optionId], references: [id], onDelete: Cascade)

  userId     String?  @map("user_id")
  user       User?    @relation("StrawPollVoter", fields: [userId], references: [id], onDelete: SetNull)
  voterName  String?  @db.VarChar(100) @map("voter_name")
  voterToken String?  @map("voter_token")
  voterIp    String?  @map("voter_ip")
  contactId  String?  @map("contact_id")
  contact    Contact? @relation("StrawPollVoteContact", fields: [contactId], references: [id], onDelete: SetNull)

  createdAt  DateTime @default(now()) @map("created_at")
  updatedAt  DateTime @updatedAt @map("updated_at")

  @@unique([pollId, userId])
  @@unique([pollId, voterToken])
  @@unique([pollId, voterIp])
  @@index([pollId])
  @@index([optionId])
  @@map("straw_poll_votes")
}

model StrawPollComment {
  id         String   @id @default(cuid())
  pollId     String   @map("poll_id")
  poll       StrawPoll @relation(fields: [pollId], references: [id], onDelete: Cascade)
  userId     String?  @map("user_id")
  user       User?    @relation("StrawPollCommenter", fields: [userId], references: [id], onDelete: SetNull)
  authorName String   @db.VarChar(100) @map("author_name")
  content    String   @db.Text
  createdAt  DateTime @default(now()) @map("created_at")

  @@index([pollId])
  @@map("straw_poll_comments")
}

model StrawPollChallenge {
  id               String    @id @default(cuid())
  pollId           String    @map("poll_id")
  poll             StrawPoll @relation(fields: [pollId], references: [id], onDelete: Cascade)
  challengerUserId String    @map("challenger_user_id")
  challenger       User      @relation("StrawPollChallenger", fields: [challengerUserId], references: [id])
  challengedUserId String    @map("challenged_user_id")
  challenged       User      @relation("StrawPollChallenged", fields: [challengedUserId], references: [id])
  completedAt      DateTime? @map("completed_at")
  createdAt        DateTime  @default(now()) @map("created_at")

  @@unique([pollId, challengerUserId, challengedUserId])
  @@map("straw_poll_challenges")
}
