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

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

enum Plan {
  FREE
  PREMIUM
}

/// Public-facing subscription tier (kept in sync with `plan`: FREE→free, PREMIUM→pro)
enum SubscriptionStatus {
  free
  pro
}

enum Privacy {
  PUBLIC
  PRIVATE
  UNLISTED
}

enum JobStatus {
  PENDING
  PROCESSING
  COMPLETED
  FAILED
  PARTIAL
}

enum JobItemStatus {
  PENDING
  ENCODING
  UPLOADING
  COMPLETED
  FAILED
}

enum QuotaExtensionRequestStatus {
  PENDING
  APPROVED
  REJECTED
}

enum QuotaExtensionKind {
  VIDEO_QUOTA
  API_RATE_LIMIT
}

model User {
  id                      String                   @id @default(cuid())
  email                   String                   @unique
  name                    String?
  image                   String?
  /// FREE = free tier; PREMIUM = Pro (€5/mo, formerly starter_5eur)
  plan                    Plan                     @default(FREE)
  /// Mirrors plan for product/UI (`free` | `pro`). Prefer `plan` for entitlements.
  subscriptionStatus      SubscriptionStatus       @default(free)
  /// Lifetime count of successfully completed video renders (not monthly quota).
  createdVideoCount       Int                      @default(0)
  /// Credits consumed in the current billing cycle (credits_used)
  videosUsed              Int                      @default(0)
  /// Monthly allocation for the current cycle (10 free / 50 pro)
  monthlyCredits          Int                      @default(10)
  quotaResetAt            DateTime
  cardLast4               String?
  subscribedAt            DateTime?
  bonusQuota              Int                      @default(0)
  apiRateLimitBonus       Int                      @default(0)
  /// Purchased extras that never expire (maps to legacy "videoCredits" column)
  extraCredits            Int                      @default(0) @map("videoCredits")
  /// Free-tier one-time +15 top-up already purchased
  freeTopUpPurchased      Boolean                  @default(false)
  stripeCustomerId        String?
  stripeSubscriptionId    String?
  apiKeyHash              String?                  @unique
  apiKeyPrefix            String?
  youtubeConnection       YouTubeConnection?
  jobs                    Job[]
  quotaExtensionRequests  QuotaExtensionRequest[]
  creditPurchases         CreditPurchase[]
  createdAt               DateTime                 @default(now())
}

enum CreditPurchaseStatus {
  PENDING
  COMPLETED
  FAILED
}

model CreditPurchase {
  id                    String               @id @default(cuid())
  userId                String
  user                  User                 @relation(fields: [userId], references: [id], onDelete: Cascade)
  credits               Int
  amountCents           Int
  status                CreditPurchaseStatus @default(PENDING)
  stripeSessionId       String?              @unique
  stripePaymentIntentId String?
  createdAt             DateTime             @default(now())
  completedAt           DateTime?
}

enum JobItemBilling {
  QUOTA
  CREDIT
}

model QuotaExtensionRequest {
  id          String                      @id @default(cuid())
  userId      String
  user        User                        @relation(fields: [userId], references: [id], onDelete: Cascade)
  kind        QuotaExtensionKind          @default(VIDEO_QUOTA)
  status      QuotaExtensionRequestStatus @default(PENDING)
  message     String                      @default("")
  requestedAt DateTime                    @default(now())
  processedAt DateTime?
  adminNote   String?
}

model YouTubeConnection {
  id           String   @id @default(cuid())
  userId       String   @unique
  user         User     @relation(fields: [userId], references: [id], onDelete: Cascade)
  accessToken  String
  refreshToken String
  expiresAt    DateTime
  channelId    String
  channelTitle String
}

model Job {
  id          String    @id @default(cuid())
  userId      String
  user        User      @relation(fields: [userId], references: [id], onDelete: Cascade)
  status      JobStatus @default(PENDING)
  imagePath   String
  items       JobItem[]
  createdAt   DateTime  @default(now())
  completedAt DateTime?
}

model JobItem {
  id                String        @id @default(cuid())
  jobId             String
  job               Job           @relation(fields: [jobId], references: [id], onDelete: Cascade)
  audioPath         String
  audioFilename     String
  title             String
  description       String        @default("")
  tags              String        @default("")
  privacy           Privacy       @default(PUBLIC)
  categoryId        String        @default("10")
  resolution        String        @default("1280x720")
  notifySubscribers Boolean       @default(true)
  madeForKids       Boolean       @default(false)
  embeddable        Boolean       @default(true)
  creativeCommons   Boolean       @default(false)
  includeWatermark  Boolean       @default(true)
  /// Optional per-item cover (Pro). Falls back to Job.imagePath when null.
  itemImagePath     String?
  /// none | default | text | logo  custom modes require Pro
  watermarkMode     String        @default("default")
  watermarkText     String?
  watermarkLogoPath String?
  /// curated key | custom | system
  watermarkFontKey  String?
  /// uploaded .ttf/.otf when fontKey=custom
  watermarkFontPath String?
  /// top-left | top-right | bottom-left | bottom-right | center
  watermarkPosition String        @default("bottom-right")
  watermarkOffsetX  Int           @default(20)
  watermarkOffsetY  Int           @default(20)
  /// Optional artist line for art-track layouts (Pro, on-video only)
  artist            String?
  /// Song title burned into art-track layouts (Pro, on-video only)
  songTitle         String?
  /// COVER_LEFT_TEXT_RIGHT | COVER_TOP_TEXT_BOTTOM | COVER_RIGHT_TEXT_LEFT | CENTERED_COMPACT | null=classic
  layoutTemplate    String?
  /// 0–100 Gaussian / boxblur intensity (Pro art-track)
  blurAmount        Int           @default(55)
  /// 0–100 visibility of blurred fill vs black
  blurOpacity       Int           @default(100)
  /// Padding around cover + text in art-track layouts
  textPadding       Int           @default(48)
  /// Extra gap between title and artist lines (px)
  titleArtistGap    Int           @default(10)
  /// Fine-tune text block within template (-120..120)
  textOffsetX       Int           @default(0)
  textOffsetY       Int           @default(0)
  playlistId        String?
  billingSource     JobItemBilling @default(QUOTA)
  status            JobItemStatus @default(PENDING)
  outputPath        String?
  youtubeVideoId    String?
  error             String?
}

/// Site footer badge / launch-directory embeds (managed via /admin)
model FooterBadge {
  id        String   @id @default(cuid())
  name      String
  /// Raw paste from Product Hunt / LaunchBuff / etc. (preferred source)
  embedHtml String?  @db.Text
  /// Parsed or manually set image URL (used when embedHtml is empty)
  imageUrl  String?
  /// Parsed or manually set destination URL
  linkUrl   String?
  isActive  Boolean  @default(true)
  sortOrder Int      @default(0)
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  @@index([isActive, sortOrder])
}
