import { pgTable, text, integer, bigint, serial, boolean, timestamp, jsonb, index, uniqueIndex, real } from 'drizzle-orm/pg-core';

// Directory type enum for efficient filtering (replaces LIKE patterns)
export const DIRECTORY_TYPES = ['studios', 'gifs', 'private', 'inbox', 'curated', 'playback', 'compilations', 'videos', 'highlights'] as const;
export type DirectoryType = typeof DIRECTORY_TYPES[number];

export const videos = pgTable('videos', {
  id: serial('id').primaryKey(),
  path: text('path').notNull().unique(),
  filename: text('filename').notNull(),
  producer: text('producer'),
  creator: text('creator'),
  title: text('title'),
  durationSeconds: integer('duration_seconds'),
  quality: text('quality'),
  orientation: text('orientation'),
  hasAudio: boolean('has_audio').default(true),
  fileSize: bigint('file_size', { mode: 'number' }),
  fileHash: text('file_hash'),
  width: integer('width'),
  height: integer('height'),
  lastValidated: timestamp('last_validated', { withTimezone: true }),
  isValid: boolean('is_valid').default(true),
  thumbnailPath: text('thumbnail_path'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  tags: jsonb('tags').$type<string[]>(),
  // Directory type for efficient filtering (indexed)
  directoryType: text('directory_type').$type<DirectoryType>(),
  // Historical engagement stats (preserved when moved from public media)
  publicViewCount: integer('public_view_count'),
  publicUpvoteCount: integer('public_upvote_count'),
  publicCommentCount: integer('public_comment_count'),
  publicCompletionCount: integer('public_completion_count'),
  publicTotalWatchTime: integer('public_total_watch_time'),
  movedFromPublicAt: timestamp('moved_from_public_at', { withTimezone: true }),
  // Name standardization tracking
  originalFilename: text('original_filename'),
  originalPath: text('original_path'),
  standardizedAt: timestamp('standardized_at', { withTimezone: true }),
}, (table) => ({
  orientationIdx: index('idx_orientation').on(table.orientation),
  producerIdx: index('idx_producer').on(table.producer),
  isValidIdx: index('idx_is_valid').on(table.isValid),
  directoryTypeIdx: index('idx_directory_type').on(table.directoryType),
  fingerprintIdx: index('idx_videos_fingerprint').on(table.durationSeconds, table.fileSize, table.width, table.height),
  // Composite index for common filtering pattern (directory + valid + orientation)
  directoryValidOrientationIdx: index('idx_videos_directory_valid_orientation').on(table.directoryType, table.isValid, table.orientation),
}));

export const compilations = pgTable('compilations', {
  id: serial('id').primaryKey(),
  filename: text('filename').notNull(),
  path: text('path'),
  durationSeconds: integer('duration_seconds'),
  videoIds: jsonb('video_ids').$type<number[]>(),
  settings: jsonb('settings').$type<Record<string, unknown>>(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
});

// Resource categories for job queue management
export type ResourceCategory = 'gpu_ai' | 'gpu_encode' | 'cpu';

// Job status including queued state
export type JobStatus = 'pending' | 'queued' | 'running' | 'completed' | 'failed' | 'cancelled';

export const jobs = pgTable('jobs', {
  id: serial('id').primaryKey(),
  type: text('type').notNull(),
  status: text('status').default('pending').$type<JobStatus>(),
  progress: integer('progress').default(0),
  log: text('log'),
  params: jsonb('params').$type<Record<string, unknown>>(),
  startedAt: timestamp('started_at', { withTimezone: true }),
  completedAt: timestamp('completed_at', { withTimezone: true }),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  // Queue management columns
  resourceCategory: text('resource_category').default('cpu').$type<ResourceCategory>(),
  vramRequired: integer('vram_required').default(0),
  queuePosition: integer('queue_position'),
  waitingReason: text('waiting_reason'),
  priority: integer('priority').default(5),
  // Pipeline integration (optional - for jobs created via pipeline)
  pipelineId: integer('pipeline_id'),
  pipelineStepId: integer('pipeline_step_id'),
}, (table) => ({
  queueIdx: index('idx_jobs_queue').on(table.status, table.priority, table.createdAt),
  resourceIdx: index('idx_jobs_resource').on(table.resourceCategory, table.status),
  pipelineIdx: index('idx_jobs_pipeline').on(table.pipelineId),
}));

export type Video = typeof videos.$inferSelect;
export type NewVideo = typeof videos.$inferInsert;
export type Compilation = typeof compilations.$inferSelect;
export type NewCompilation = typeof compilations.$inferInsert;
export type Job = typeof jobs.$inferSelect;
export type NewJob = typeof jobs.$inferInsert;

// Job type union
export type JobType =
  | 'compilation'
  | 'scan'
  | 'public_scan'
  | 'organize'
  | 'organize_studio'
  | 'reencode_streaming'
  | 'compile_random'
  | 'compile_quad'
  | 'compile_quad_horizontal'
  | 'compile_triple_vertical'
  | 'compile_mega'
  | 'compile_gif'
  | 'generate_gif'
  | 'fetch'
  | 'digest'
  | 'digest_generate'
  | 'clip_generate'
  | 'highlight_generate'
  | 'tag_generation'
  | 'scene_extract'
  | 'clip_extract_only'
  | 'auto_organize_publish';

// Script job parameter interfaces
export interface ReencodeParams {
  dryRun?: boolean;
  forceCpu?: boolean;
  debug?: boolean;
  skipHevc?: boolean;
  forceAll?: boolean;
  targetDir?: string;
  singleFile?: string;
}

export interface ClipCountParams {
  clipCount: 30 | 60 | 100;
  excludedDirs?: string[];
}

export interface ClipSetsParams {
  clipSets: 30 | 60 | 100;
  excludedDirs?: string[];
}

export interface DurationOverlayParams {
  duration: 30 | 45 | 60 | 90 | 120;
  overlaySource: string;
  excludedDirs?: string[];
}

export interface CreatorParams {
  creator: string;
  excludedDirs?: string[];
}

export interface GenerateGifParams {
  videoPath: string;
  segmentDuration?: number;  // Default 59 seconds
}

export interface FetchParams {
  urls: string[];
  mode: 'single' | 'batch';
}

export interface DigestParams {
  digestId: number;
  videoId: number;
}

export interface DigestGenerateParams {
  digestId: number;
  videoId: number;
  adCutSpec?: AdCutSpec;
}

export interface ClipGenerateParams {
  digestId: number;
  options?: {
    clipIds?: number[];
    burnCaptions?: boolean;
    captionStyle?: CaptionStyle;
    editedTranscript?: Array<{ index: number; text: string }>;
    closingAdImagePath?: string;
    closingAdDuration?: number;
  };
}

export interface HighlightGenerateParams {
  reactionId: number;
  mediaId: number;
  userId: number;
  timestamp: number;       // Original reaction timestamp (seconds)
  actualStartTime: number; // Calculated start time after edge case handling
  actualEndTime: number;   // Calculated end time after edge case handling
  sourceMediaPath: string;
  username: string;
}

export interface SceneExtractParams {
  digestId: number;
  videoId: number;
  sceneIds?: number[];  // Optional: specific scene indices to extract
  generateScenes?: boolean;
  generateClips?: boolean;
}

export interface ClipExtractOnlyParams {
  digestId: number;
  videoId: number;
  clipIds?: number[];  // Optional: specific clip IDs to extract
  generateScenes?: boolean;
  generateClips?: boolean;
}

// Digest types (defined here for schema reference, full definitions in services/digest/types.ts)
export interface DigestConfig {
  positions?: string[];
  settings?: string[];
  activities?: string[];
  frameInterval?: number;
  minClipDuration?: number;
  maxAdDuration?: number;
  transcribe?: boolean;
  visionModel?: string;
  textModel?: string;
  // Custom prompts (override defaults if set)
  customPrompts?: {
    descriptionStage?: string;    // Stage 1: Full description
    taggingStage?: string;        // Stage 2: Tag extraction
    classificationStage?: string; // Stage 3: Scene classification
    synthesis?: string;           // Final synthesis
  };
}

export interface FrameAnalysis {
  timestamp: number;
  framePath: string;
  performerCount: number;
  performerBreakdown?: { women: number; men: number };
  genders: string[];
  position: string;
  setting: string;
  cameraAngle: string;
  intensity: 'soft' | 'moderate' | 'intense';
  notableElements: string[];
  visualInterestScore: number;
  description: string;
  // Extended fields from multi-stage analysis
  fullDescription?: string;  // Stage 1: Extended 3-5 sentence description
  visualElements?: string[]; // Stage 1: Key visual elements
  atmosphere?: string;       // Stage 1: Mood/atmosphere
  extractedTags?: Array<{    // Stage 2: Per-frame tags with confidence
    category: string;
    value: string;
    confidence: number;
    source: 'matched' | 'visual' | 'inferred';
  }>;
  isKeyMoment?: boolean;           // Stage 3: Is this a notable frame?
  keyMomentType?: 'intro' | 'narrative' | 'action_peak' | 'transition' | 'climax' | 'outro';
  keyMomentReason?: string;        // Stage 3: Why this is a key moment
  // Cinematic theory fields (Stage 3)
  shotType?: 'wide' | 'close-up' | 'medium' | 'pov' | 'overhead' | 'side';
  hasDialogue?: boolean;           // Is someone visibly speaking?
  eyeContact?: boolean;            // Is performer looking at camera?
  // Unwanted content detection (Stage 3)
  isTextHeavy?: boolean;           // High text density (title cards)
  estimatedTextPercentage?: number; // 0-100% of frame covered by text
  hasWatermark?: boolean;          // Logo/branding detected (KEEP these)
  watermarkType?: 'studio' | 'performer' | 'network' | 'copyright' | 'other';
  frameType?: 'sexual_content' | 'non_sexual' | 'text_overlay' | 'branding' | 'advertisement';
  isStaticSequence?: boolean;      // Part of static/unchanging sequence
  // Face detection data (from InsightFace service)
  faceDetection?: {
    faces: Array<{
      bbox: [number, number, number, number];  // Face position [x1,y1,x2,y2]
      gender: 0 | 1;                           // 0=female, 1=male
      age: number;                             // Estimated age
      detScore: number;                        // Detection confidence 0-1
      trackId?: string;                        // Cross-frame person ID
      embedding?: number[];                    // 512-D ArcFace embedding for recognition
    }>;
    count: number;
    womenCount: number;
    menCount: number;
    avgAge: number;                            // Average age across all faces
    source: 'face_service' | 'vision_model';
    serviceAvailable: boolean;
  };
  // JoyTag visual tagging (from JoyTag service)
  joytagTags?: Array<{
    tag: string;                               // Danbooru-style tag
    confidence: number;                        // 0-1 confidence score
    category?: string;                         // Mapped category (appearance, position, etc.)
    mappedValue?: string;                      // Mapped to internal taxonomy
  }>;
  joytagInferenceMs?: number;                  // JoyTag inference time
}

export interface TranscriptSegment {
  start: number;
  end: number;
  text: string;
  words?: { word: string; start: number; end: number }[];
}

export interface DigestTag {
  category: string;
  value: string;
  confidence: number;
  evidence: string[];
  source?: 'vlm' | 'joytag' | 'merged';
}

export interface ClipSuggestion {
  startTime: number;
  endTime: number;
  reason: string;
  type: 'visual' | 'audio' | 'both';
  interestScore: number;
  tags: string[];
}

export interface AdCutSpec {
  totalDuration: number;
  suggestedTitle: string;
  clips: {
    startTime: number;
    endTime: number;
    order: number;
    transitionIn?: string;
  }[];
}

// Stage results for observability - persisted per-frame stage data

/**
 * Individual prompt call within a stage.
 * Tracks each AI model call with its prompt, response, and timing.
 */
export interface PromptCall {
  id: string;                    // Unique ID (e.g., "performer_count", "description", "face_detection")
  name: string;                  // Display name
  type: 'vision' | 'text' | 'api'; // Model/service type
  model?: string;                // Model name (JoyCaption, InsightFace, etc.)
  status: 'pending' | 'running' | 'complete' | 'failed';
  prompt?: string;               // The prompt sent (for AI calls) - full text, not truncated
  rawResponse?: string;          // Raw model response - full text, not truncated
  parsedResult?: unknown;        // Parsed/structured result
  durationMs?: number;
  error?: string;
  timestamp?: number;            // When this call started
  // Extended fields for debugging
  tokenCount?: number;           // Token usage if available
  retryCount?: number;           // Number of retry attempts
  parameters?: Record<string, unknown>;  // Model parameters (temperature, etc.)
}

export interface StageResult {
  stage: 'description' | 'tagging' | 'classification';
  rawResponse: string;
  parsedResult: unknown;
  durationMs: number;
  model: string;
  promptCalls?: PromptCall[];    // All sub-prompt calls within this stage
}

export interface FrameStageResults {
  description?: StageResult;
  tagging?: StageResult;
  classification?: StageResult;
}

// Map of frameIndex -> stage results for the entire digest
export type DigestStageResults = Record<number, FrameStageResults>;

// ============================================
// Public Gallery Tables
// ============================================

export const publicMedia = pgTable('public_media', {
  id: serial('id').primaryKey(),
  path: text('path').notNull().unique(),
  filename: text('filename').notNull(),
  category: text('category').notNull(),
  durationSeconds: integer('duration_seconds'),
  quality: text('quality'),
  orientation: text('orientation'),
  thumbnailPath: text('thumbnail_path'),
  fileSize: bigint('file_size', { mode: 'number' }),
  // Denormalized counters for performance
  viewCount: integer('view_count').default(0),
  upvoteCount: integer('upvote_count').default(0),
  commentCount: integer('comment_count').default(0),
  finishCount: integer('finish_count').default(0),
  totalWatchTime: integer('total_watch_time').default(0),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  // Locked video system
  isLocked: boolean('is_locked').default(false),
  lockedAt: timestamp('locked_at', { withTimezone: true }),
  lockedBy: integer('locked_by'),
  // Manual ordering for playlist (curated category)
  position: integer('position').default(0),
  // Track uploader for quickies
  uploaderId: integer('uploader_id'),
}, (table) => ({
  categoryIdx: index('idx_public_media_category').on(table.category),
  pathIdx: index('idx_public_media_path').on(table.path),
  isLockedIdx: index('idx_public_media_is_locked').on(table.isLocked),
  positionIdx: index('idx_public_media_position').on(table.position),
  // Quickies performance indexes
  uploaderIdx: index('idx_public_media_uploader').on(table.uploaderId),
  categoryDateIdx: index('idx_public_media_category_date').on(table.category, table.createdAt),
  orientationIdx: index('idx_public_media_orientation').on(table.orientation),
  // Composite index for common filtering (category + locked + date)
  categoryLockedDateIdx: index('idx_public_media_category_locked_date').on(table.category, table.isLocked, table.createdAt),
}));

export const sessions = pgTable('sessions', {
  id: text('id').primaryKey(), // UUID from client
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  lastSeenAt: timestamp('last_seen_at', { withTimezone: true }),
  // Device tracking
  ipAddress: text('ip_address'),
  userAgent: text('user_agent'),
  deviceType: text('device_type'), // 'desktop' | 'mobile' | 'tablet'
  browser: text('browser'),
  os: text('os'),
  // Geography (from GeoIP)
  country: text('country'),           // ISO code: 'US'
  countryName: text('country_name'),  // 'United States'
  region: text('region'),             // 'CA'
  city: text('city'),                 // 'Los Angeles'
  timezone: text('timezone'),         // 'America/Los_Angeles'
  latitude: real('latitude'),
  longitude: real('longitude'),
  // User correlation
  userId: integer('user_id'),         // Links session to authenticated user
  // Analytics
  firstSeenAt: timestamp('first_seen_at', { withTimezone: true }),  // For return visitor detection
  visitCount: integer('visit_count').default(1),  // Increment on return visits
}, (table) => ({
  userIdIdx: index('idx_sessions_user_id').on(table.userId),
  countryIdx: index('idx_sessions_country').on(table.country),
}));

export const upvotes = pgTable('upvotes', {
  id: serial('id').primaryKey(),
  mediaId: integer('media_id').notNull(),
  sessionId: text('session_id').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  uniqueVoteIdx: index('idx_upvotes_unique').on(table.mediaId, table.sessionId),
  mediaIdx: index('idx_upvotes_media').on(table.mediaId),
}));

export const comments = pgTable('comments', {
  id: serial('id').primaryKey(),
  mediaId: integer('media_id').notNull(),
  sessionId: text('session_id').notNull(),
  userId: integer('user_id'),
  content: text('content').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  // Content safety fields for LLM-based moderation
  safetyStatus: text('safety_status').default('pending'),
  safetyCheckedAt: timestamp('safety_checked_at', { withTimezone: true }),
  safetyCategories: jsonb('safety_categories').$type<string[]>(),
  safetyReasoning: text('safety_reasoning'),
  // Hidden comment fields for auto-moderation
  isHidden: boolean('is_hidden').default(false),
  hiddenAt: timestamp('hidden_at', { withTimezone: true }),
  hiddenReason: text('hidden_reason'),
  // Admin moderation notes (internal, not shown to users)
  moderationNotes: text('moderation_notes'),
}, (table) => ({
  mediaIdx: index('idx_comments_media').on(table.mediaId),
  sessionIdx: index('idx_comments_session').on(table.sessionId),
  userIdx: index('idx_comments_user').on(table.userId),
  safetyStatusIdx: index('idx_comments_safety_status').on(table.safetyStatus),
  isHiddenIdx: index('idx_comments_is_hidden').on(table.isHidden),
}));

export const views = pgTable('views', {
  id: serial('id').primaryKey(),
  mediaId: integer('media_id').notNull(),
  sessionId: text('session_id').notNull(),
  watchTimeSeconds: integer('watch_time_seconds').default(0),
  lastUpdated: timestamp('last_updated', { withTimezone: true }).defaultNow(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  uniqueViewIdx: index('idx_views_unique').on(table.mediaId, table.sessionId),
  mediaIdx: index('idx_views_media').on(table.mediaId),
}));

// Public Gallery Types
export type PublicMedia = typeof publicMedia.$inferSelect;
export type NewPublicMedia = typeof publicMedia.$inferInsert;
export type Session = typeof sessions.$inferSelect;
export type NewSession = typeof sessions.$inferInsert;
export type Upvote = typeof upvotes.$inferSelect;
export type NewUpvote = typeof upvotes.$inferInsert;
export type Comment = typeof comments.$inferSelect;
export type NewComment = typeof comments.$inferInsert;
export type View = typeof views.$inferSelect;
export type NewView = typeof views.$inferInsert;

// ============================================
// User Management Tables
// ============================================

// V2 Users table (for authentication only)
// This matches the Prisma User model to allow Drizzle queries for auth
// Table name: 'users' (created by Prisma migrations)
export const v2Users = pgTable('users', {
  id: text('id').primaryKey(),
  email: text('email').notNull().unique(),
  password: text('password').notNull(),
  name: text('name'),
  phone: text('phone'),
  role: text('role').notNull().default('USER'),
  status: text('status').notNull().default('ACTIVE'),
  permissions: jsonb('permissions'),
  createdVia: text('createdVia').notNull().default('STANDARD'),
  expiresAt: timestamp('expiresAt', { withTimezone: true }),
  expireDays: integer('expireDays'),
  lastLoginAt: timestamp('lastLoginAt', { withTimezone: true }),
  emailVerified: boolean('emailVerified').notNull().default(false),
  createdAt: timestamp('createdAt', { withTimezone: true }).notNull().defaultNow(),
  updatedAt: timestamp('updatedAt', { withTimezone: true }).notNull().defaultNow(),
}, (table) => ({
  emailIdx: index('User_email_key').on(table.email),
}));

// Media-specific users table (for future media-api user management)
// NOTE: This is separate from v2Users and not currently used
// If/when media-api needs its own user system, this table can be created
export const mediaUsers = pgTable('media_users', {
  id: serial('id').primaryKey(),
  username: text('username').notNull().unique(),
  email: text('email').notNull().unique(),
  passwordHash: text('password_hash').notNull(),
  role: text('role').notNull().default('user'),
  status: text('status').notNull().default('pending'),
  emailVerified: boolean('email_verified').default(false),
  linkedSessionId: text('linked_session_id'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  approvedAt: timestamp('approved_at', { withTimezone: true }),
  approvedBy: integer('approved_by'),
  lastLoginAt: timestamp('last_login_at', { withTimezone: true }),
  // Trusted user fields
  isTrusted: boolean('is_trusted').default(false),
  maxUploadDurationSeconds: integer('max_upload_duration_seconds').default(60),
  autoAcceptUploads: boolean('auto_accept_uploads').default(false),
  trustedAt: timestamp('trusted_at', { withTimezone: true }),
  trustedBy: integer('trusted_by'),
  defaultPlaylistId: integer('default_playlist_id'),
  // Social links visibility
  socialLinksPublic: boolean('social_links_public').default(false),
  // Subscription fields
  subscriptionStatus: text('subscription_status').default('none'), // none, active, grace_period, delinquent, lifetime
  subscriptionPlanId: integer('subscription_plan_id'),
  subscriptionEndDate: timestamp('subscription_end_date', { withTimezone: true }),
  lifetimeMember: boolean('lifetime_member').default(false),
  totalPaidCAD: integer('total_paid_cad').default(0), // Lifetime total in cents
  // Moderation tracking
  lastModerationAction: timestamp('last_moderation_action', { withTimezone: true }),
}, (table) => ({
  emailIdx: index('idx_media_users_email').on(table.email),
  usernameIdx: index('idx_media_users_username').on(table.username),
  statusIdx: index('idx_media_users_status').on(table.status),
  trustedIdx: index('idx_media_users_trusted').on(table.isTrusted),
  subscriptionStatusIdx: index('idx_media_users_subscription_status').on(table.subscriptionStatus),
}));

export const authTokens = pgTable('auth_tokens', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  token: text('token').notNull().unique(),
  type: text('type').notNull(),
  expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  tokenIdx: index('idx_auth_tokens_token').on(table.token),
  userIdx: index('idx_auth_tokens_user').on(table.userId),
}));

export const sessionBans = pgTable('session_bans', {
  id: serial('id').primaryKey(),
  sessionId: text('session_id').notNull(),
  reason: text('reason'),
  bannedBy: integer('banned_by'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  expiresAt: timestamp('expires_at', { withTimezone: true }), // null = permanent
}, (table) => ({
  sessionIdx: index('idx_session_bans_session').on(table.sessionId),
}));

export const commentModeration = pgTable('comment_moderation', {
  id: serial('id').primaryKey(),
  commentId: integer('comment_id').notNull(),
  status: text('status').notNull().default('pending'),
  moderatedBy: integer('moderated_by'),
  moderatedAt: timestamp('moderated_at', { withTimezone: true }),
  reason: text('reason'),
}, (table) => ({
  commentIdx: index('idx_comment_moderation_comment').on(table.commentId),
  statusIdx: index('idx_comment_moderation_status').on(table.status),
}));

// Email Verification Tokens
export const emailVerificationTokens = pgTable('email_verification_tokens', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  token: text('token').notNull().unique(),
  expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  tokenIdx: index('idx_email_verification_tokens_token').on(table.token),
  userIdx: index('idx_email_verification_tokens_user').on(table.userId),
}));

// Password Reset Tokens
export const passwordResetTokens = pgTable('password_reset_tokens', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  token: text('token').notNull().unique(),
  expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  usedAt: timestamp('used_at', { withTimezone: true }),
}, (table) => ({
  tokenIdx: index('idx_password_reset_tokens_token').on(table.token),
  userIdx: index('idx_password_reset_tokens_user').on(table.userId),
}));

// Email Change Tokens
export const emailChangeTokens = pgTable('email_change_tokens', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  newEmail: text('new_email').notNull(),
  token: text('token').notNull().unique(),
  expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  tokenIdx: index('idx_email_change_tokens_token').on(table.token),
  userIdx: index('idx_email_change_tokens_user').on(table.userId),
}));

// User Management Types
export type MediaUser = typeof mediaUsers.$inferSelect;
export type NewMediaUser = typeof mediaUsers.$inferInsert;
export type AuthToken = typeof authTokens.$inferSelect;
export type NewAuthToken = typeof authTokens.$inferInsert;
export type SessionBan = typeof sessionBans.$inferSelect;
export type NewSessionBan = typeof sessionBans.$inferInsert;
export type CommentModeration = typeof commentModeration.$inferSelect;
export type NewCommentModeration = typeof commentModeration.$inferInsert;
export type EmailVerificationToken = typeof emailVerificationTokens.$inferSelect;
export type NewEmailVerificationToken = typeof emailVerificationTokens.$inferInsert;
export type PasswordResetToken = typeof passwordResetTokens.$inferSelect;
export type NewPasswordResetToken = typeof passwordResetTokens.$inferInsert;
export type EmailChangeToken = typeof emailChangeTokens.$inferSelect;
export type NewEmailChangeToken = typeof emailChangeTokens.$inferInsert;

// Role and status types
export type UserRole = 'admin' | 'user';
export type UserStatus = 'pending' | 'approved' | 'banned';
export type CommentModerationStatus = 'pending' | 'approved' | 'rejected';
export type CommentSafetyStatus = 'pending' | 'safe' | 'flagged' | 'error';

// ============================================
// Achievement & Dashboard Tables
// ============================================

export const userAchievements = pgTable('user_achievements', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  achievementId: text('achievement_id').notNull(),
  unlockedAt: timestamp('unlocked_at', { withTimezone: true }).notNull(),
  progress: integer('progress').default(0),
  notified: boolean('notified').default(false),
}, (table) => ({
  userIdx: index('idx_user_achievements_user').on(table.userId),
  // Unique constraint for ON CONFLICT support in batch inserts
  userAchievementUnique: uniqueIndex('idx_user_achievements_unique').on(table.userId, table.achievementId),
}));

export const userStats = pgTable('user_stats', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().unique(),
  totalWatchTimeSeconds: integer('total_watch_time_seconds').default(0),
  totalVideosWatched: integer('total_videos_watched').default(0),
  totalUpvotesGiven: integer('total_upvotes_given').default(0),
  totalCommentsMade: integer('total_comments_made').default(0),
  totalFinishes: integer('total_finishes').default(0),
  currentDayStreak: integer('current_day_streak').default(0),
  longestDayStreak: integer('longest_day_streak').default(0),
  lastActiveDate: text('last_active_date'), // YYYY-MM-DD format
  longestSingleSession: integer('longest_single_session').default(0),
  categoriesCompleted: jsonb('categories_completed').$type<string[]>(),
  nightOwlCount: integer('night_owl_count').default(0),
  earlyBirdCount: integer('early_bird_count').default(0),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
});

export const userFinishes = pgTable('user_finishes', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  mediaId: integer('media_id'),
  sessionId: text('session_id'),
  createdAt: timestamp('created_at', { withTimezone: true }).notNull(),
}, (table) => ({
  userIdx: index('idx_user_finishes_user').on(table.userId),
  dateIdx: index('idx_user_finishes_date').on(table.createdAt),
}));

// ============================================
// Video Reactions Table
// ============================================

export const REACTION_TYPES = ['like', 'love', 'laugh', 'wow', 'sad', 'angry'] as const;
export type ReactionType = typeof REACTION_TYPES[number];

export const videoReactions = pgTable('video_reactions', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  mediaId: integer('media_id').notNull(),
  reactionType: text('reaction_type').notNull(),
  videoTimestamp: integer('video_timestamp').notNull(), // seconds into the video when reaction was clicked
  createdAt: timestamp('created_at', { withTimezone: true }).notNull(),
}, (table) => ({
  userMediaTypeIdx: index('idx_video_reactions_user_media_type').on(table.userId, table.mediaId, table.reactionType),
  mediaTimestampIdx: index('idx_video_reactions_media_timestamp').on(table.mediaId, table.videoTimestamp),
  mediaIdx: index('idx_video_reactions_media').on(table.mediaId),
  createdAtIdx: index('idx_video_reactions_created').on(table.createdAt),
}));

export type VideoReaction = typeof videoReactions.$inferSelect;
export type NewVideoReaction = typeof videoReactions.$inferInsert;

// ============================================
// Quickie Generation Cooldowns Table
// ============================================

export const highlightCooldowns = pgTable('highlight_cooldowns', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  lastGeneratedAt: timestamp('last_generated_at', { withTimezone: true }).notNull(),
}, (table) => ({
  userIdx: index('idx_highlight_cooldowns_user').on(table.userId),
}));

export type HighlightCooldown = typeof highlightCooldowns.$inferSelect;
export type NewHighlightCooldown = typeof highlightCooldowns.$inferInsert;

export const userDailyActivity = pgTable('user_daily_activity', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  activityDate: text('activity_date').notNull(), // YYYY-MM-DD
  watchTimeSeconds: integer('watch_time_seconds').default(0),
  videosWatched: integer('videos_watched').default(0),
  firstActivityHour: integer('first_activity_hour'),
  createdAt: timestamp('created_at', { withTimezone: true }),
}, (table) => ({
  uniqueIdx: index('idx_user_daily_activity_unique').on(table.userId, table.activityDate),
}));

// Achievement & Dashboard Types
export type UserAchievement = typeof userAchievements.$inferSelect;
export type NewUserAchievement = typeof userAchievements.$inferInsert;
export type UserStatsRecord = typeof userStats.$inferSelect;
export type NewUserStatsRecord = typeof userStats.$inferInsert;
export type UserFinish = typeof userFinishes.$inferSelect;
export type NewUserFinish = typeof userFinishes.$inferInsert;
export type UserDailyActivity = typeof userDailyActivity.$inferSelect;
export type NewUserDailyActivity = typeof userDailyActivity.$inferInsert;

// Achievement rarity type
export type AchievementRarity = 'common' | 'uncommon' | 'rare' | 'epic' | 'legendary';

// ============================================
// Chat Thread Tracking Table
// ============================================

export const chatThreadReadStatus = pgTable('chat_thread_read_status', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  mediaId: integer('media_id').notNull(),
  lastSeenAt: timestamp('last_seen_at', { withTimezone: true }).notNull(),
}, (table) => ({
  uniqueIdx: index('idx_chat_thread_read_unique').on(table.userId, table.mediaId),
  userIdx: index('idx_chat_thread_read_user').on(table.userId),
}));

export type ChatThreadReadStatus = typeof chatThreadReadStatus.$inferSelect;
export type NewChatThreadReadStatus = typeof chatThreadReadStatus.$inferInsert;

// ============================================
// App Settings Table
// ============================================

export const appSettings = pgTable('app_settings', {
  key: text('key').primaryKey(),
  value: text('value').notNull(),
  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
});

export type AppSetting = typeof appSettings.$inferSelect;
export type NewAppSetting = typeof appSettings.$inferInsert;

// ============================================
// Rate Limiting Table (for persistent rate limits)
// ============================================

export const rateLimits = pgTable('rate_limits', {
  id: serial('id').primaryKey(),
  key: text('key').notNull().unique(),
  count: integer('count').notNull().default(1),
  resetAt: timestamp('reset_at', { withTimezone: true }).notNull(),
}, (table) => ({
  keyIdx: uniqueIndex('idx_rate_limits_key').on(table.key),
  resetAtIdx: index('idx_rate_limits_reset_at').on(table.resetAt),
}));

export type RateLimit = typeof rateLimits.$inferSelect;
export type NewRateLimit = typeof rateLimits.$inferInsert;

// ============================================
// Moderation Word Lists Table
// ============================================

export const WORD_FILTER_LEVELS = ['low', 'medium', 'high', 'custom'] as const;
export type WordFilterLevel = typeof WORD_FILTER_LEVELS[number];

export const moderationWordLists = pgTable('moderation_word_lists', {
  id: serial('id').primaryKey(),
  level: text('level').notNull(),
  word: text('word').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  createdBy: integer('created_by'),
}, (table) => ({
  levelIdx: index('idx_moderation_word_lists_level').on(table.level),
  wordIdx: index('idx_moderation_word_lists_word').on(table.word),
}));

export type ModerationWordList = typeof moderationWordLists.$inferSelect;
export type NewModerationWordList = typeof moderationWordLists.$inferInsert;

// Hidden reason type
export type CommentHiddenReason = 'ai_flagged' | 'word_filter' | 'spam' | 'manual' | 'rate_limit' | 'duplicate' | 'link';

// ============================================
// Content Reports Table
// ============================================

export const REPORT_TYPES = ['inappropriate', 'spam', 'copyright', 'illegal', 'false_info', 'other'] as const;
export type ReportType = typeof REPORT_TYPES[number];

export const REPORT_STATUS = ['pending', 'reviewed', 'actioned', 'dismissed'] as const;
export type ReportStatus = typeof REPORT_STATUS[number];

export const contentReports = pgTable('content_reports', {
  id: serial('id').primaryKey(),
  mediaId: integer('media_id').notNull(),
  sessionId: text('session_id'),
  userId: integer('user_id'),
  reportType: text('report_type').notNull(),
  description: text('description'),
  status: text('status').notNull().default('pending'),
  resolvedBy: integer('resolved_by'),
  resolvedAt: timestamp('resolved_at', { withTimezone: true }),
  resolutionNotes: text('resolution_notes'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  mediaIdx: index('idx_content_reports_media').on(table.mediaId),
  statusIdx: index('idx_content_reports_status').on(table.status),
  sessionIdx: index('idx_content_reports_session').on(table.sessionId),
  createdAtIdx: index('idx_content_reports_created').on(table.createdAt),
}));

export type ContentReport = typeof contentReports.$inferSelect;
export type NewContentReport = typeof contentReports.$inferInsert;

// ============================================
// Playlist Tables
// ============================================

export const playlists = pgTable('playlists', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  name: text('name').notNull(),
  description: text('description'),
  isPublic: boolean('is_public').default(false),
  shareToken: text('share_token').unique(),
  thumbnailMediaId: integer('thumbnail_media_id'),
  // Denormalized counters
  videoCount: integer('video_count').default(0),
  totalDurationSeconds: integer('total_duration_seconds').default(0),
  viewCount: integer('view_count').default(0),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  userIdx: index('idx_playlists_user').on(table.userId),
  publicIdx: index('idx_playlists_public').on(table.isPublic),
  shareTokenIdx: index('idx_playlists_share_token').on(table.shareToken),
  // Prevent duplicate playlist names per user (e.g., multiple "My Quickies")
  userNameIdx: uniqueIndex('idx_playlists_user_name').on(table.userId, table.name),
}));

export const playlistVideos = pgTable('playlist_videos', {
  id: serial('id').primaryKey(),
  playlistId: integer('playlist_id').notNull(),
  mediaId: integer('media_id').notNull(),
  position: integer('position').notNull().default(0),
  addedAt: timestamp('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  playlistIdx: index('idx_playlist_videos_playlist').on(table.playlistId),
  mediaIdx: index('idx_playlist_videos_media').on(table.mediaId),
  uniqueIdx: index('idx_playlist_videos_unique').on(table.playlistId, table.mediaId),
}));

export const featuredPlaylists = pgTable('featured_playlists', {
  id: serial('id').primaryKey(),
  playlistId: integer('playlist_id').notNull().unique(),
  position: integer('position').notNull().default(0),
  featuredBy: integer('featured_by'),
  featuredAt: timestamp('featured_at', { withTimezone: true }),
}, (table) => ({
  positionIdx: index('idx_featured_playlists_position').on(table.position),
}));

export const playlistViews = pgTable('playlist_views', {
  id: serial('id').primaryKey(),
  playlistId: integer('playlist_id').notNull(),
  sessionId: text('session_id').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  playlistIdx: index('idx_playlist_views_playlist').on(table.playlistId),
  uniqueIdx: index('idx_playlist_views_unique').on(table.playlistId, table.sessionId),
}));

// Playlist Types
export type Playlist = typeof playlists.$inferSelect;
export type NewPlaylist = typeof playlists.$inferInsert;
export type PlaylistVideo = typeof playlistVideos.$inferSelect;
export type NewPlaylistVideo = typeof playlistVideos.$inferInsert;
export type FeaturedPlaylist = typeof featuredPlaylists.$inferSelect;
export type NewFeaturedPlaylist = typeof featuredPlaylists.$inferInsert;
export type PlaylistView = typeof playlistViews.$inferSelect;
export type NewPlaylistView = typeof playlistViews.$inferInsert;

// ============================================
// Advertisement Tables
// ============================================

export const ads = pgTable('ads', {
  id: serial('id').primaryKey(),
  type: text('type').notNull(),
  // For make_account: variant identifier (e.g., 'playlist_promo', 'chat_promo')
  // For custom: null (uses imagePath instead)
  variant: text('variant'),
  // Custom ad fields
  imagePath: text('image_path'),
  linkUrl: text('link_url'),
  title: text('title'), // For alt text and admin reference
  // Visibility: 'everyone', 'members_only', 'non_members'
  visibility: text('visibility').notNull().default('everyone'),
  // Status
  isActive: boolean('is_active').default(true),
  // Ordering for rotation
  position: integer('position').default(0),
  // Denormalized counters for performance
  impressionCount: integer('impression_count').default(0),
  clickCount: integer('click_count').default(0),
  // Timestamps
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  typeIdx: index('idx_ads_type').on(table.type),
  isActiveIdx: index('idx_ads_is_active').on(table.isActive),
  visibilityIdx: index('idx_ads_visibility').on(table.visibility),
}));

export const adImpressions = pgTable('ad_impressions', {
  id: serial('id').primaryKey(),
  adId: integer('ad_id').notNull().references(() => ads.id, { onDelete: 'cascade' }),
  sessionId: text('session_id'),
  userId: integer('user_id'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  adIdx: index('idx_ad_impressions_ad').on(table.adId),
  sessionIdx: index('idx_ad_impressions_session').on(table.sessionId),
  dateIdx: index('idx_ad_impressions_date').on(table.createdAt),
}));

export const adClicks = pgTable('ad_clicks', {
  id: serial('id').primaryKey(),
  adId: integer('ad_id').notNull().references(() => ads.id, { onDelete: 'cascade' }),
  sessionId: text('session_id'),
  userId: integer('user_id'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  adIdx: index('idx_ad_clicks_ad').on(table.adId),
  sessionIdx: index('idx_ad_clicks_session').on(table.sessionId),
  dateIdx: index('idx_ad_clicks_date').on(table.createdAt),
}));

// Advertisement Types
export type Ad = typeof ads.$inferSelect;
export type NewAd = typeof ads.$inferInsert;
export type AdImpression = typeof adImpressions.$inferSelect;
export type NewAdImpression = typeof adImpressions.$inferInsert;
export type AdClick = typeof adClicks.$inferSelect;
export type NewAdClick = typeof adClicks.$inferInsert;

// Ad type enums
export type AdType = 'make_account' | 'custom';
export type AdVisibility = 'everyone' | 'members_only' | 'non_members';

// ============================================
// Friends & Social Tables
// ============================================

export const FRIENDSHIP_STATUS = ['pending', 'accepted', 'declined'] as const;
export type FriendshipStatus = typeof FRIENDSHIP_STATUS[number];

export const friendships = pgTable('friendships', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  friendId: integer('friend_id').notNull(),
  status: text('status').notNull().default('pending'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  acceptedAt: timestamp('accepted_at', { withTimezone: true }),
}, (table) => ({
  userFriendIdx: index('idx_friendships_user_friend').on(table.userId, table.friendId),
  userIdx: index('idx_friendships_user').on(table.userId),
  friendIdx: index('idx_friendships_friend').on(table.friendId),
  statusIdx: index('idx_friendships_status').on(table.status),
}));

export const userBlocks = pgTable('user_blocks', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  blockedUserId: integer('blocked_user_id').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  uniqueBlockIdx: index('idx_user_blocks_unique').on(table.userId, table.blockedUserId),
  userIdx: index('idx_user_blocks_user').on(table.userId),
}));

export const pokes = pgTable('pokes', {
  id: serial('id').primaryKey(),
  fromUserId: integer('from_user_id').notNull(),
  toUserId: integer('to_user_id').notNull(),
  isRead: boolean('is_read').default(false),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  toUserIdx: index('idx_pokes_to_user').on(table.toUserId),
  fromUserIdx: index('idx_pokes_from_user').on(table.fromUserId),
}));

// Video recommendations - share videos with friends
export const videoRecommendations = pgTable('video_recommendations', {
  id: serial('id').primaryKey(),
  fromUserId: integer('from_user_id').notNull(),
  toUserId: integer('to_user_id').notNull(),
  mediaId: integer('media_id').notNull(),
  message: text('message'),
  isRead: boolean('is_read').default(false),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  toUserIdx: index('idx_video_recommendations_to_user').on(table.toUserId),
  fromUserIdx: index('idx_video_recommendations_from_user').on(table.fromUserId),
  mediaIdx: index('idx_video_recommendations_media').on(table.mediaId),
}));

export const userPresence = pgTable('user_presence', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().unique(),
  isOnline: boolean('is_online').default(false),
  currentMediaId: integer('current_media_id'),
  lastActivityAt: timestamp('last_activity_at', { withTimezone: true }),
  lastVideoChangeAt: timestamp('last_video_change_at', { withTimezone: true }),
}, (table) => ({
  onlineIdx: index('idx_user_presence_online').on(table.isOnline),
  userIdx: index('idx_user_presence_user').on(table.userId),
}));

export const userGalleryImages = pgTable('user_gallery_images', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  filename: text('filename').notNull(),
  originalFilename: text('original_filename'),
  position: integer('position').notNull().default(0),
  uploadedAt: timestamp('uploaded_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  userIdx: index('idx_user_gallery_user').on(table.userId),
  positionIdx: index('idx_user_gallery_position').on(table.userId, table.position),
}));

// Friends & Social Types
export type Friendship = typeof friendships.$inferSelect;
export type NewFriendship = typeof friendships.$inferInsert;
export type UserBlock = typeof userBlocks.$inferSelect;
export type NewUserBlock = typeof userBlocks.$inferInsert;
export type Poke = typeof pokes.$inferSelect;
export type NewPoke = typeof pokes.$inferInsert;
export type VideoRecommendation = typeof videoRecommendations.$inferSelect;
export type NewVideoRecommendation = typeof videoRecommendations.$inferInsert;
export type UserPresence = typeof userPresence.$inferSelect;
export type NewUserPresence = typeof userPresence.$inferInsert;
export type UserGalleryImage = typeof userGalleryImages.$inferSelect;
export type NewUserGalleryImage = typeof userGalleryImages.$inferInsert;

// Social link platforms
export const SOCIAL_PLATFORMS = [
  'twitter', 'instagram', 'onlyfans', 'fansly', 'reddit',
  'discord', 'tiktok', 'youtube', 'twitch', 'snapchat',
  'linktree', 'custom'
] as const;
export type SocialPlatform = typeof SOCIAL_PLATFORMS[number];

export const userSocialLinks = pgTable('user_social_links', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  platform: text('platform').notNull(), // One of SOCIAL_PLATFORMS
  url: text('url').notNull(),
  displayName: text('display_name'), // Optional custom label
  position: integer('position').notNull().default(0),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  userIdx: index('idx_user_social_links_user').on(table.userId),
  positionIdx: index('idx_user_social_links_position').on(table.userId, table.position),
}));

export type UserSocialLink = typeof userSocialLinks.$inferSelect;
export type NewUserSocialLink = typeof userSocialLinks.$inferInsert;

// ============================================
// Privacy Settings Tables
// ============================================

export const privacySettings = pgTable('privacy_settings', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().unique().references(() => mediaUsers.id, { onDelete: 'cascade' }),

  // Online Status
  showOnlineStatus: boolean('show_online_status').default(true),
  showCurrentlyWatching: boolean('show_currently_watching').default(true),

  // Activity Visibility
  showInFriendActivity: boolean('show_in_friend_activity').default(true),
  anonymizePublicComments: boolean('anonymize_public_comments').default(false),
  hidePublicReactions: boolean('hide_public_reactions').default(false),
  hidePublicFinishes: boolean('hide_public_finishes').default(false),

  // Friend Requests
  allowFriendRequests: boolean('allow_friend_requests').default(true),

  // Close Friends restrictions
  closeFriendsOnlyWatching: boolean('close_friends_only_watching').default(false),

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  userIdx: index('idx_privacy_settings_user').on(table.userId),
}));

export const closeFriends = pgTable('close_friends', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  closeFriendId: integer('close_friend_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  addedAt: timestamp('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  uniqueIdx: uniqueIndex('idx_close_friends_unique').on(table.userId, table.closeFriendId),
  userIdx: index('idx_close_friends_user').on(table.userId),
}));

// Privacy Settings Types
export type PrivacySettings = typeof privacySettings.$inferSelect;
export type NewPrivacySettings = typeof privacySettings.$inferInsert;
export type CloseFriend = typeof closeFriends.$inferSelect;
export type NewCloseFriend = typeof closeFriends.$inferInsert;

// ============================================
// User Uploads (Trusted User Feature)
// ============================================

export const USER_UPLOAD_STATUS = ['pending', 'approved', 'rejected'] as const;
export type UserUploadStatus = typeof USER_UPLOAD_STATUS[number];

export const userUploads = pgTable('user_uploads', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  filename: text('filename').notNull(),
  originalFilename: text('original_filename'),
  path: text('path').notNull(),
  durationSeconds: integer('duration_seconds'),
  quality: text('quality'),
  orientation: text('orientation'),
  fileSize: bigint('file_size', { mode: 'number' }),
  thumbnailPath: text('thumbnail_path'),
  status: text('status').notNull().default('pending'),
  reviewedBy: integer('reviewed_by'),
  reviewedAt: timestamp('reviewed_at', { withTimezone: true }),
  reviewNotes: text('review_notes'),
  publicMediaId: integer('public_media_id'),
  uploadInviteId: integer('upload_invite_id'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  userIdx: index('idx_user_uploads_user').on(table.userId),
  statusIdx: index('idx_user_uploads_status').on(table.status),
  createdAtIdx: index('idx_user_uploads_created').on(table.createdAt),
  inviteIdx: index('idx_user_uploads_invite').on(table.uploadInviteId),
}));

export type UserUpload = typeof userUploads.$inferSelect;
export type NewUserUpload = typeof userUploads.$inferInsert;

// ============================================
// Upload Invites (Shareable Upload Links)
// ============================================

export const UPLOAD_INVITE_STATUS = ['active', 'inactive', 'expired'] as const;
export type UploadInviteStatus = typeof UPLOAD_INVITE_STATUS[number];

export const uploadInvites = pgTable('upload_invites', {
  id: serial('id').primaryKey(),
  code: text('code').notNull().unique(),
  label: text('label'),
  createdBy: integer('created_by').notNull(),
  status: text('status').notNull().default('active'),
  maxUploads: integer('max_uploads'),
  uploadCount: integer('upload_count').notNull().default(0),
  expiresAt: timestamp('expires_at', { withTimezone: true }),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  codeIdx: index('idx_upload_invites_code').on(table.code),
  statusIdx: index('idx_upload_invites_status').on(table.status),
  createdByIdx: index('idx_upload_invites_created_by').on(table.createdBy),
}));

export type UploadInvite = typeof uploadInvites.$inferSelect;
export type NewUploadInvite = typeof uploadInvites.$inferInsert;

// ============================================
// Tag System Tables
// ============================================

export const tagCategories = pgTable('tag_categories', {
  id: serial('id').primaryKey(),
  name: text('name').notNull().unique(),
  displayOrder: integer('display_order').notNull().default(0),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  displayOrderIdx: index('idx_tag_categories_display_order').on(table.displayOrder),
}));

export const tags = pgTable('tags', {
  id: serial('id').primaryKey(),
  categoryId: integer('category_id').notNull(),
  name: text('name').notNull(),
  displayOrder: integer('display_order').notNull().default(0),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  categoryIdx: index('idx_tags_category').on(table.categoryId),
  displayOrderIdx: index('idx_tags_display_order').on(table.displayOrder),
  uniqueNamePerCategory: index('idx_tags_unique_name').on(table.categoryId, table.name),
}));

export const publicMediaTags = pgTable('public_media_tags', {
  id: serial('id').primaryKey(),
  mediaId: integer('media_id').notNull(),
  tagId: integer('tag_id').notNull(),
  addedAt: timestamp('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  mediaIdx: index('idx_public_media_tags_media').on(table.mediaId),
  tagIdx: index('idx_public_media_tags_tag').on(table.tagId),
  uniqueIdx: index('idx_public_media_tags_unique').on(table.mediaId, table.tagId),
}));

export const userUploadSuggestedTags = pgTable('user_upload_suggested_tags', {
  id: serial('id').primaryKey(),
  uploadId: integer('upload_id').notNull(),
  tagId: integer('tag_id').notNull(),
  suggestedAt: timestamp('suggested_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  uploadIdx: index('idx_user_upload_suggested_tags_upload').on(table.uploadId),
  tagIdx: index('idx_user_upload_suggested_tags_tag').on(table.tagId),
  uniqueIdx: index('idx_user_upload_suggested_tags_unique').on(table.uploadId, table.tagId),
}));

export const userTagPreferences = pgTable('user_tag_preferences', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull(),
  tagId: integer('tag_id').notNull(),
  savedAt: timestamp('saved_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  userIdx: index('idx_user_tag_preferences_user').on(table.userId),
  tagIdx: index('idx_user_tag_preferences_tag').on(table.tagId),
  uniqueIdx: index('idx_user_tag_preferences_unique').on(table.userId, table.tagId),
}));

// Tag System Types
export type TagCategory = typeof tagCategories.$inferSelect;
export type NewTagCategory = typeof tagCategories.$inferInsert;
export type Tag = typeof tags.$inferSelect;
export type NewTag = typeof tags.$inferInsert;
export type PublicMediaTag = typeof publicMediaTags.$inferSelect;
export type NewPublicMediaTag = typeof publicMediaTags.$inferInsert;
export type UserUploadSuggestedTag = typeof userUploadSuggestedTags.$inferSelect;
export type NewUserUploadSuggestedTag = typeof userUploadSuggestedTags.$inferInsert;
export type UserTagPreference = typeof userTagPreferences.$inferSelect;
export type NewUserTagPreference = typeof userTagPreferences.$inferInsert;

// ============================================
// Public Media Performers Junction
// ============================================

/**
 * Links public media items to performers for "Browse Actors" feature.
 */
export const publicMediaPerformers = pgTable('public_media_performers', {
  id: serial('id').primaryKey(),
  mediaId: integer('media_id').notNull(),
  performerId: integer('performer_id').notNull(),
  addedAt: timestamp('added_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  mediaIdx: index('idx_public_media_performers_media').on(table.mediaId),
  performerIdx: index('idx_public_media_performers_performer').on(table.performerId),
  uniqueIdx: uniqueIndex('idx_public_media_performers_unique').on(table.mediaId, table.performerId),
}));

export type PublicMediaPerformer = typeof publicMediaPerformers.$inferSelect;
export type NewPublicMediaPerformer = typeof publicMediaPerformers.$inferInsert;

// ============================================
// Video Digest Tables
// ============================================

export const DIGEST_STATUS = [
  'pending', 'scene_detection', 'extracting', 'analyzing',
  'face_detection', 'transcribing', 'segmenting', 'synthesizing',
  'completed', 'failed', 'cancelled'
] as const;
export type DigestStatus = typeof DIGEST_STATUS[number];

export const videoDigests = pgTable('video_digests', {
  id: serial('id').primaryKey(),
  videoId: integer('video_id').notNull(),
  jobId: integer('job_id'),
  status: text('status').notNull().default('pending'),
  progress: integer('progress').default(0),
  frameCount: integer('frame_count'),
  config: jsonb('config').$type<DigestConfig>(),
  frameAnalyses: jsonb('frame_analyses').$type<FrameAnalysis[]>(),
  transcript: jsonb('transcript').$type<TranscriptSegment[]>(),
  tags: jsonb('tags').$type<DigestTag[]>(),
  suggestedClips: jsonb('suggested_clips').$type<ClipSuggestion[]>(),
  adCutSpec: jsonb('ad_cut_spec').$type<AdCutSpec>(),
  // Per-frame stage results for observability (persisted during analysis)
  stageResults: jsonb('stage_results').$type<DigestStageResults>(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  startedAt: timestamp('started_at', { withTimezone: true }),
  completedAt: timestamp('completed_at', { withTimezone: true }),
  error: text('error'),
}, (table) => ({
  videoIdx: index('idx_video_digests_video').on(table.videoId),
  statusIdx: index('idx_video_digests_status').on(table.status),
  createdAtIdx: index('idx_video_digests_created').on(table.createdAt),
}));

export const digestVideoTags = pgTable('digest_video_tags', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  category: text('category').notNull(),
  value: text('value').notNull(),
  confidence: integer('confidence'),
  source: text('source').default('digest'),
  evidence: jsonb('evidence').$type<string[]>(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  digestIdx: index('idx_digest_video_tags_digest').on(table.digestId),
  videoIdx: index('idx_digest_video_tags_video').on(table.videoId),
  categoryIdx: index('idx_digest_video_tags_category').on(table.category),
  valueIdx: index('idx_digest_video_tags_value').on(table.value),
  categoryValueIdx: index('idx_digest_video_tags_cat_val').on(table.category, table.value),
}));

// Digest Types
export type VideoDigest = typeof videoDigests.$inferSelect;
export type NewVideoDigest = typeof videoDigests.$inferInsert;
export type DigestVideoTag = typeof digestVideoTags.$inferSelect;
export type NewDigestVideoTag = typeof digestVideoTags.$inferInsert;

// ============================================
// Digest Clip Selection & Generation Tables
// ============================================

export const CLIP_TYPES = ['hook', 'intro', 'action', 'climax', 'highlight'] as const;
export type ClipType = typeof CLIP_TYPES[number];

export const CLIP_STATUS = ['pending', 'processing', 'completed', 'failed'] as const;
export type ClipStatus = typeof CLIP_STATUS[number];

export const CLIP_SOURCES = ['machine', 'manual'] as const;
export type ClipSource = typeof CLIP_SOURCES[number];

// Stage 1: Selected clips from digest analysis (metadata only, no extraction yet)
export const digestSelectedClips = pgTable('digest_selected_clips', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  clipType: text('clip_type').notNull(),
  startTime: integer('start_time').notNull(), // seconds
  endTime: integer('end_time').notNull(),
  duration: integer('duration').notNull(),
  reason: text('reason'),
  interestScore: integer('interest_score'),
  position: text('position'), // For action clips (e.g., 'doggy style')
  transcriptHint: text('transcript_hint'), // For climax clips (keyword that triggered selection)
  tags: jsonb('tags').$type<string[]>(),
  source: text('source').notNull().default('machine'),
  isIncluded: integer('is_included').notNull().default(1), // 1 = included in generation, 0 = excluded
  isHook: integer('is_hook').notNull().default(0), // 1 = this clip is the cinematic hook (teaser at start)
  sequenceOrder: integer('sequence_order').notNull().default(0), // Order in final compilation (0-based)
  hookSourceClipId: integer('hook_source_clip_id'), // If set, this is a hook copy cloned from the referenced clip
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  digestIdx: index('idx_digest_selected_clips_digest').on(table.digestId),
  videoIdx: index('idx_digest_selected_clips_video').on(table.videoId),
  clipTypeIdx: index('idx_digest_selected_clips_type').on(table.clipType),
  sourceIdx: index('idx_digest_selected_clips_source').on(table.source),
  sequenceOrderIdx: index('idx_digest_selected_clips_sequence').on(table.sequenceOrder),
  isHookIdx: index('idx_digest_selected_clips_is_hook').on(table.isHook),
  hookSourceIdx: index('idx_digest_selected_clips_hook_source').on(table.hookSourceClipId),
}));

// Stage 2: Generated clips (after FFmpeg extraction)
export const digestGeneratedClips = pgTable('digest_generated_clips', {
  id: serial('id').primaryKey(),
  selectedClipId: integer('selected_clip_id').notNull(),
  digestId: integer('digest_id').notNull(),
  folderId: integer('folder_id'), // References digestOutputFolders.id
  clipPath: text('clip_path'),
  gifPath: text('gif_path'), // Only set if duration <= 60s
  status: text('status').notNull().default('pending'),
  error: text('error'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  completedAt: timestamp('completed_at', { withTimezone: true }),
  // Publishing tracking
  publishedToPublicMediaId: integer('published_to_public_media_id'),
  publishedAt: timestamp('published_at', { withTimezone: true }),
}, (table) => ({
  selectedClipIdx: index('idx_digest_generated_clips_selected').on(table.selectedClipId),
  digestIdx: index('idx_digest_generated_clips_digest').on(table.digestId),
  statusIdx: index('idx_digest_generated_clips_status').on(table.status),
  folderIdx: index('idx_digest_generated_clips_folder').on(table.folderId),
}));

// Caption style type for digest compilations
export const CAPTION_POSITIONS = ['bottom', 'top'] as const;
export type CaptionPosition = typeof CAPTION_POSITIONS[number];

export const CAPTION_SIZES = ['small', 'medium', 'large'] as const;
export type CaptionSize = typeof CAPTION_SIZES[number];

export interface CaptionStyle {
  position: CaptionPosition;
  fontSize: CaptionSize;
}

// Stage 2: Compilation videos
export const digestCompilations = pgTable('digest_compilations', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  folderId: integer('folder_id'), // References digestOutputFolders.id
  filename: text('filename').notNull(),
  name: text('name'), // Optional user-provided name for the compilation
  path: text('path').notNull(),
  durationSeconds: integer('duration_seconds'),
  orientation: text('orientation'),
  status: text('status').notNull().default('pending'),
  error: text('error'),
  // Caption burning options
  hasCaptions: integer('has_captions').notNull().default(0), // 1 = captions burned in
  captionStyle: jsonb('caption_style').$type<CaptionStyle>(),
  // Closing ad frame options
  closingAdPath: text('closing_ad_path'), // Path to uploaded ad frame image
  closingAdDuration: integer('closing_ad_duration'), // Duration in seconds (3-10)
  // Aggregated tags from constituent segments
  tags: jsonb('tags').$type<string[]>(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  completedAt: timestamp('completed_at', { withTimezone: true }),
}, (table) => ({
  digestIdx: index('idx_digest_compilations_digest').on(table.digestId),
  videoIdx: index('idx_digest_compilations_video').on(table.videoId),
  statusIdx: index('idx_digest_compilations_status').on(table.status),
  folderIdx: index('idx_digest_compilations_folder').on(table.folderId),
}));

// Clip & Compilation Types
export type DigestSelectedClip = typeof digestSelectedClips.$inferSelect;
export type NewDigestSelectedClip = typeof digestSelectedClips.$inferInsert;
export type DigestGeneratedClip = typeof digestGeneratedClips.$inferSelect;
export type NewDigestGeneratedClip = typeof digestGeneratedClips.$inferInsert;
export type DigestCompilation = typeof digestCompilations.$inferSelect;
export type NewDigestCompilation = typeof digestCompilations.$inferInsert;

// ============================================
// Digest Output Folders Table
// ============================================

export const digestOutputFolders = pgTable('digest_output_folders', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  folderPath: text('folder_path').notNull(),
  folderName: text('folder_name').notNull(),
  folderType: text('folder_type').notNull().default('clips'), // 'clips' | 'scenes' | 'compilation'
  clipCount: integer('clip_count').default(0),
  compilationCount: integer('compilation_count').default(0),
  totalSize: integer('total_size'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  digestIdx: index('idx_digest_output_folders_digest').on(table.digestId),
  videoIdx: index('idx_digest_output_folders_video').on(table.videoId),
  folderPathIdx: index('idx_digest_output_folders_path').on(table.folderPath),
}));

export type DigestOutputFolder = typeof digestOutputFolders.$inferSelect;
export type NewDigestOutputFolder = typeof digestOutputFolders.$inferInsert;

// ============================================
// Digest Generated Scenes Table
// ============================================
// Tracks individual scene files extracted from digests with derived tags

export const digestGeneratedScenes = pgTable('digest_generated_scenes', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  folderId: integer('folder_id'), // References digestOutputFolders.id
  sceneNumber: integer('scene_number').notNull(),
  scenePath: text('scene_path'),
  startTime: real('start_time').notNull(),
  endTime: real('end_time').notNull(),
  duration: real('duration').notNull(),
  // Tags derived from videoTagTimeline for this time range
  tags: jsonb('tags').$type<string[]>(),
  dominantPosition: text('dominant_position'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  // Publishing tracking
  publishedToPublicMediaId: integer('published_to_public_media_id'),
  publishedAt: timestamp('published_at', { withTimezone: true }),
}, (table) => ({
  digestIdx: index('idx_digest_generated_scenes_digest').on(table.digestId),
  videoIdx: index('idx_digest_generated_scenes_video').on(table.videoId),
  folderIdx: index('idx_digest_generated_scenes_folder').on(table.folderId),
  scenePathIdx: index('idx_digest_generated_scenes_path').on(table.scenePath),
}));

export type DigestGeneratedScene = typeof digestGeneratedScenes.$inferSelect;
export type NewDigestGeneratedScene = typeof digestGeneratedScenes.$inferInsert;

// ============================================
// Video Scene Cuts Table (for clip boundary snapping)
// ============================================

// Merged cut with source attribution for combined detection
export interface MergedCutDetail {
  timestamp: number;
  sources: ('transnetv2' | 'pyscenedetect' | 'clip')[];
  confidence: number;
  weight: number;
}

// Caches detected scene cuts per video for clean clip transitions
// Extended for combined multi-detector scene detection
export const videoSceneCuts = pgTable('video_scene_cuts', {
  id: serial('id').primaryKey(),
  videoId: integer('video_id').notNull(),
  // Array of cut timestamps in seconds (includes 0 and video end)
  cuts: jsonb('cuts').$type<number[]>().notNull(),
  // Number of scenes detected
  sceneCount: integer('scene_count').notNull(),
  // Video duration at time of detection
  duration: real('duration').notNull(),
  // Detection configuration
  detector: text('detector').notNull().default('content'), // 'content', 'adaptive', 'transnetv2', 'combined'
  threshold: real('threshold').notNull().default(27.0),

  // ============================================
  // Combined Detection Fields (for multi-detector merging)
  // ============================================
  // Individual detector results for debugging/analysis
  transnetCuts: jsonb('transnet_cuts').$type<number[] | null>(),
  pyscenedetectCuts: jsonb('pyscenedetect_cuts').$type<number[] | null>(),
  clipCuts: jsonb('clip_cuts').$type<number[] | null>(),
  // Detailed merged cuts with source attribution
  mergedCutsDetailed: jsonb('merged_cuts_detailed').$type<MergedCutDetail[] | null>(),
  // Merge configuration
  mergeStrategy: text('merge_strategy').default('weighted'), // 'union' | 'intersection' | 'weighted' | 'majority'
  toleranceSeconds: real('tolerance_seconds').default(0.75),
  // Which detectors were used (for cache validation)
  detectorsUsed: jsonb('detectors_used').$type<string[] | null>(),
  // Total processing time for combined detection
  processingTimeMs: integer('processing_time_ms'),

  // Timestamps
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  videoIdx: index('idx_video_scene_cuts_video').on(table.videoId),
}));

export type VideoSceneCuts = typeof videoSceneCuts.$inferSelect;
export type NewVideoSceneCuts = typeof videoSceneCuts.$inferInsert;

// ============================================
// Video Segmentation Tables (Multi-Method Analysis)
// ============================================

// Segment types for multi-method clip detection
export const SEGMENT_TYPES = ['scene', 'tag_change', 'vocal', 'fixed_interval'] as const;
export type SegmentType = typeof SEGMENT_TYPES[number];

// Vocal moment categories for transcript-based segments
export const VOCAL_CATEGORIES = ['dialogue', 'dirty_talk', 'climax', 'interview', 'moan'] as const;
export type VocalCategory = typeof VOCAL_CATEGORIES[number];

// Tag timeline entry for tracking visual changes over time (JoyTag every 3s)
export interface TagTimelineEntry {
  tag: string;
  confidence: number;
  category?: string;
}

// Stores JoyTag results at regular intervals for change detection
export const videoTagTimeline = pgTable('video_tag_timeline', {
  id: serial('id').primaryKey(),
  videoId: integer('video_id').notNull(),
  digestId: integer('digest_id').notNull(),
  timestamp: real('timestamp').notNull(),  // Seconds from video start
  // JoyTag results at this timestamp
  tags: jsonb('tags').$type<TagTimelineEntry[]>().notNull(),
  // Derived/computed fields for efficient querying
  dominantPosition: text('dominant_position'),  // Most confident sexual position tag
  performerCount: integer('performer_count'),
  intensity: integer('intensity'),  // 0-10 score based on tag analysis
  hasDialogue: boolean('has_dialogue').default(false),
  hasEyeContact: boolean('has_eye_contact').default(false),
  // Frame reference
  framePath: text('frame_path'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  videoIdx: index('idx_video_tag_timeline_video').on(table.videoId),
  digestIdx: index('idx_video_tag_timeline_digest').on(table.digestId),
  timestampIdx: index('idx_video_tag_timeline_timestamp').on(table.videoId, table.timestamp),
  positionIdx: index('idx_video_tag_timeline_position').on(table.dominantPosition),
}));

export type VideoTagTimeline = typeof videoTagTimeline.$inferSelect;
export type NewVideoTagTimeline = typeof videoTagTimeline.$inferInsert;

// Stores detected segments by type (scene, tag_change, vocal, fixed_interval)
export const videoSegments = pgTable('video_segments', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  segmentType: text('segment_type').notNull().$type<SegmentType>(),
  startTime: real('start_time').notNull(),  // Seconds
  endTime: real('end_time').notNull(),      // Seconds
  duration: real('duration').notNull(),     // Seconds

  // Segment metadata (varies by type)
  tags: jsonb('tags').$type<string[]>(),
  transcriptSnippet: text('transcript_snippet'),
  vocalCategory: text('vocal_category').$type<VocalCategory>(),
  positionChange: text('position_change'),   // "oral → doggystyle"
  dominantPosition: text('dominant_position'),

  // Scoring for auto-sequence building
  interestScore: real('interest_score').default(0),
  hasEyeContact: boolean('has_eye_contact').default(false),
  hasFullSentence: boolean('has_full_sentence').default(false),
  isClimaxMoment: boolean('is_climax_moment').default(false),

  // Caption/title generation
  captioned: boolean('captioned').default(false),
  captionText: text('caption_text'),
  title: text('title'),

  // Frame reference for thumbnail
  keyframePath: text('keyframe_path'),
  keyframeTimestamp: real('keyframe_timestamp'),

  // Inclusion in sequence (for Abstract)
  isIncluded: boolean('is_included').default(true),
  sequenceOrder: integer('sequence_order'),
  isHook: boolean('is_hook').default(false),

  // Inclusion for clip generation (for Clips tab, independent from Abstract)
  includeInClips: boolean('include_in_clips').default(true),

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  digestIdx: index('idx_video_segments_digest').on(table.digestId),
  videoIdx: index('idx_video_segments_video').on(table.videoId),
  typeIdx: index('idx_video_segments_type').on(table.segmentType),
  timeIdx: index('idx_video_segments_time').on(table.videoId, table.startTime),
  includedIdx: index('idx_video_segments_included').on(table.digestId, table.isIncluded),
  sequenceIdx: index('idx_video_segments_sequence').on(table.digestId, table.sequenceOrder),
  clipsIdx: index('idx_video_segments_clips').on(table.digestId, table.includeInClips),
}));

export type VideoSegment = typeof videoSegments.$inferSelect;
export type NewVideoSegment = typeof videoSegments.$inferInsert;

// ============================================
// Video Tag Mapping Tables (Digest → Tag System)
// ============================================

// Links videos to existing tags (from digest or manual)
export const videoTags = pgTable('video_tags', {
  id: serial('id').primaryKey(),
  videoId: integer('video_id').notNull(),
  tagId: integer('tag_id').notNull(),
  confidence: integer('confidence'), // 0-100, null for manual
  source: text('source').notNull().default('manual'),
  digestId: integer('digest_id'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  videoIdx: index('idx_video_tags_video').on(table.videoId),
  tagIdx: index('idx_video_tags_tag').on(table.tagId),
  sourceIdx: index('idx_video_tags_source').on(table.source),
  uniqueIdx: index('idx_video_tags_unique').on(table.videoId, table.tagId),
}));

export const SUGGESTED_TAG_STATUS = ['pending', 'approved', 'rejected', 'mapped'] as const;
export type SuggestedTagStatus = typeof SUGGESTED_TAG_STATUS[number];

// Unmatched tags from digest that need admin review
export const digestSuggestedTags = pgTable('digest_suggested_tags', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  suggestedCategory: text('suggested_category').notNull(),
  suggestedValue: text('suggested_value').notNull(),
  confidence: integer('confidence'), // 0-100
  status: text('status').notNull().default('pending'),
  mappedTagId: integer('mapped_tag_id'),
  reviewedBy: integer('reviewed_by'),
  reviewedAt: timestamp('reviewed_at', { withTimezone: true }),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  digestIdx: index('idx_digest_suggested_tags_digest').on(table.digestId),
  videoIdx: index('idx_digest_suggested_tags_video').on(table.videoId),
  statusIdx: index('idx_digest_suggested_tags_status').on(table.status),
  categoryValueIdx: index('idx_digest_suggested_tags_cat_val').on(table.suggestedCategory, table.suggestedValue),
}));

// Clip-level tag assignments (links clips to formal tags)
export const digestClipTags = pgTable('digest_clip_tags', {
  id: serial('id').primaryKey(),
  clipId: integer('clip_id').notNull(),
  tagId: integer('tag_id').notNull(),
  source: text('source').notNull().default('auto'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  clipIdx: index('idx_digest_clip_tags_clip').on(table.clipId),
  tagIdx: index('idx_digest_clip_tags_tag').on(table.tagId),
  uniqueIdx: index('idx_digest_clip_tags_unique').on(table.clipId, table.tagId),
}));

// Video Tag Mapping Types
export type VideoTag = typeof videoTags.$inferSelect;
export type NewVideoTag = typeof videoTags.$inferInsert;
export type DigestSuggestedTag = typeof digestSuggestedTags.$inferSelect;
export type NewDigestSuggestedTag = typeof digestSuggestedTags.$inferInsert;
export type DigestClipTag = typeof digestClipTags.$inferSelect;
export type NewDigestClipTag = typeof digestClipTags.$inferInsert;

// ============================================
// Watch Party Tables
// ============================================

export const WATCH_PARTY_STATUS = ['waiting', 'active', 'paused', 'ended'] as const;
export type WatchPartyStatus = typeof WATCH_PARTY_STATUS[number];

export const LOOP_MODE = ['none', 'video', 'queue'] as const;
export type LoopMode = typeof LOOP_MODE[number];

// Watch Party Summary Stats (generated when party ends)
export interface WatchPartySummaryStats {
  totalWatchTimeSeconds: number;
  peakParticipantCount: number;
  totalParticipants: number;
  chatMessageCount: number;
  reactionCount: number;
  mostActiveChatter?: { userId: number; username: string; messageCount: number };
  mostUsedReaction?: { emoji: string; reactionType: string; count: number };
  participantDetails: Array<{
    userId: number;
    username: string;
    watchTimeSeconds: number;
    chatMessageCount: number;
    reactionCount: number;
  }>;
}

export const watchPartySessions = pgTable('watch_party_sessions', {
  id: serial('id').primaryKey(),
  hostId: integer('host_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  mediaId: integer('media_id').notNull().references(() => publicMedia.id, { onDelete: 'cascade' }),
  inviteCode: text('invite_code').notNull().unique(),
  status: text('status').notNull().default('waiting'),
  currentTime: integer('current_time').default(0), // playback position in seconds
  isPlaying: boolean('is_playing').default(false),
  maxParticipants: integer('max_participants').default(20),
  allowChat: boolean('allow_chat').default(true),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  startedAt: timestamp('started_at', { withTimezone: true }),
  endedAt: timestamp('ended_at', { withTimezone: true }),
  // Post-party summary stats (generated when party ends)
  summaryStats: jsonb('summary_stats').$type<WatchPartySummaryStats>(),
}, (table) => ({
  hostIdx: index('idx_watch_party_sessions_host').on(table.hostId),
  inviteCodeIdx: index('idx_watch_party_sessions_invite').on(table.inviteCode),
  statusIdx: index('idx_watch_party_sessions_status').on(table.status),
  createdAtIdx: index('idx_watch_party_sessions_created').on(table.createdAt),
}));

export const watchPartyParticipants = pgTable('watch_party_participants', {
  id: serial('id').primaryKey(),
  partyId: integer('party_id').notNull().references(() => watchPartySessions.id, { onDelete: 'cascade' }),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  isConnected: boolean('is_connected').default(false),
  isKicked: boolean('is_kicked').default(false),
  joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow(),
  leftAt: timestamp('left_at', { withTimezone: true }),
}, (table) => ({
  partyIdx: index('idx_watch_party_participants_party').on(table.partyId),
  userIdx: index('idx_watch_party_participants_user').on(table.userId),
  uniqueIdx: index('idx_watch_party_participants_unique').on(table.partyId, table.userId),
}));

export const watchPartyChatMessages = pgTable('watch_party_chat_messages', {
  id: serial('id').primaryKey(),
  partyId: integer('party_id').notNull().references(() => watchPartySessions.id, { onDelete: 'cascade' }),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  content: text('content').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  partyIdx: index('idx_watch_party_chat_party').on(table.partyId),
  createdAtIdx: index('idx_watch_party_chat_created').on(table.createdAt),
}));

// Watch Party Reactions (persisted for summary stats)
export const watchPartyReactions = pgTable('watch_party_reactions', {
  id: serial('id').primaryKey(),
  partyId: integer('party_id').notNull().references(() => watchPartySessions.id, { onDelete: 'cascade' }),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  reactionType: text('reaction_type').notNull(),
  videoTimestamp: integer('video_timestamp'), // seconds into video when reaction was sent
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  partyIdx: index('idx_watch_party_reactions_party').on(table.partyId),
  userIdx: index('idx_watch_party_reactions_user').on(table.userId),
}));

// Watch Party Invites
export const INVITE_STATUS = ['pending', 'accepted', 'declined', 'expired'] as const;
export type InviteStatus = typeof INVITE_STATUS[number];

export const watchPartyInvites = pgTable('watch_party_invites', {
  id: serial('id').primaryKey(),
  partyId: integer('party_id').notNull().references(() => watchPartySessions.id, { onDelete: 'cascade' }),
  fromUserId: integer('from_user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  toUserId: integer('to_user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  status: text('status').notNull().default('pending').$type<InviteStatus>(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  respondedAt: timestamp('responded_at', { withTimezone: true }),
}, (table) => ({
  partyIdx: index('idx_watch_party_invites_party').on(table.partyId),
  toUserIdx: index('idx_watch_party_invites_to_user').on(table.toUserId),
  fromUserIdx: index('idx_watch_party_invites_from_user').on(table.fromUserId),
  statusIdx: index('idx_watch_party_invites_status').on(table.status),
}));

// Watch Party Types
export type WatchPartySession = typeof watchPartySessions.$inferSelect;
export type NewWatchPartySession = typeof watchPartySessions.$inferInsert;
export type WatchPartyParticipant = typeof watchPartyParticipants.$inferSelect;
export type NewWatchPartyParticipant = typeof watchPartyParticipants.$inferInsert;
export type WatchPartyChatMessage = typeof watchPartyChatMessages.$inferSelect;
export type NewWatchPartyChatMessage = typeof watchPartyChatMessages.$inferInsert;
export type WatchPartyReaction = typeof watchPartyReactions.$inferSelect;
export type NewWatchPartyReaction = typeof watchPartyReactions.$inferInsert;
export type WatchPartyInvite = typeof watchPartyInvites.$inferSelect;
export type NewWatchPartyInvite = typeof watchPartyInvites.$inferInsert;

// ==========================================
// Tag Generation Jobs
// ==========================================

export type TagGenerationContentType = 'public' | 'inbox' | 'upload';
export type TagGenerationStatus = 'pending' | 'extracting' | 'analyzing' | 'completed' | 'failed' | 'cancelled';

export interface TagGenerationOptions {
  frameCount?: number;
  frameInterval?: number;
  autoApplyThreshold?: number;
  reuseDigestTags?: boolean;
}

export interface TagSuggestion {
  tagId?: number;
  categoryName: string;
  tagName: string;
  confidence: number;
  frameIndices: number[];
  isMatched: boolean;
}

export interface ContentTagResult {
  contentId: number;
  contentType: TagGenerationContentType;
  filename: string;
  thumbnailPath?: string;
  existingTags: number[];
  suggestedTags: TagSuggestion[];
  framePaths?: string[];
  digestId?: number;
  processedAt: string;
}

export const tagGenerationJobs = pgTable('tag_generation_jobs', {
  id: serial('id').primaryKey(),
  status: text('status').$type<TagGenerationStatus>().default('pending').notNull(),
  contentType: text('content_type').$type<TagGenerationContentType>().notNull(),
  contentIds: jsonb('content_ids').$type<number[]>().notNull(),
  options: jsonb('options').$type<TagGenerationOptions>(),
  results: jsonb('results').$type<ContentTagResult[]>(),
  progress: integer('progress').default(0),
  currentItem: integer('current_item').default(0),
  totalItems: integer('total_items').default(0),
  error: text('error'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  completedAt: timestamp('completed_at', { withTimezone: true }),
}, (table) => ({
  statusIdx: index('idx_tag_gen_status').on(table.status),
  contentTypeIdx: index('idx_tag_gen_content_type').on(table.contentType),
  createdAtIdx: index('idx_tag_gen_created_at').on(table.createdAt),
}));

export type TagGenerationJob = typeof tagGenerationJobs.$inferSelect;
export type NewTagGenerationJob = typeof tagGenerationJobs.$inferInsert;

// ============================================================================
// Performer Face Recognition
// ============================================================================

/**
 * Named performers that can be associated with multiple detected faces.
 * Integrates with tag system for public gallery filtering.
 */
export const creators = pgTable('creators', {
  id: serial('id').primaryKey(),
  name: text('name').notNull().unique(),
  slug: text('slug').notNull().unique(), // URL-friendly name for public browsing
  gender: integer('gender'), // 0=female, 1=male, null=unknown
  primaryFaceId: integer('primary_face_id'), // Best face image for display thumbnail
  faceCount: integer('face_count').default(0), // Denormalized count of linked faces
  videoCount: integer('video_count').default(0), // Videos featuring this performer
  tagId: integer('tag_id'), // Link to performers tag category for filtering
  isPublic: boolean('is_public').default(false), // Visible in public Browse Actors
  // Centroid embedding for efficient matching (512-D averaged, quality-filtered)
  centroidEmbedding: jsonb('centroid_embedding').$type<number[]>(),
  centroidUpdatedAt: timestamp('centroid_updated_at', { withTimezone: true }),
  centroidFaceCount: integer('centroid_face_count').default(0), // Faces used in centroid computation
  // Quality metrics for adaptive matching
  embeddingVariance: real('embedding_variance'), // How spread out the embeddings are
  avgIntraClusterSimilarity: real('avg_intra_cluster_similarity'), // Average similarity within performer's faces
  avgAge: real('avg_age'), // Average detected age of performer
  ageStdDev: real('age_std_dev'), // Standard deviation of detected ages
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  nameIdx: index('idx_creators_name').on(table.name),
  slugIdx: index('idx_creators_slug').on(table.slug),
  genderIdx: index('idx_creators_gender').on(table.gender),
  tagIdx: index('idx_creators_tag').on(table.tagId),
  publicIdx: index('idx_creators_public').on(table.isPublic),
}));

export type Creator = typeof creators.$inferSelect;
export type NewCreator = typeof creators.$inferInsert;

// Face status for tracking review state
export const FACE_STATUS = ['pending', 'assigned', 'ignored', 'skipped'] as const;
export type FaceStatus = typeof FACE_STATUS[number];

/**
 * Stores face embeddings for performer recognition.
 * Each row represents a single detected face that can be linked to a performer.
 */
export const performerFaces = pgTable('performer_faces', {
  id: serial('id').primaryKey(),
  // Performer identification - FK to performers table (null if unidentified)
  performerId: integer('performer_id'), // Links to creators.id
  performerName: text('performer_name'), // Legacy field, kept for migration
  // 512-dimensional face embedding vector from ArcFace
  // Stored as JSON array for compatibility (consider pgvector for production)
  faceEmbedding: jsonb('face_embedding').$type<number[]>().notNull(),
  // Face attributes from InsightFace
  gender: integer('gender'), // 0=female, 1=male
  age: integer('age'),
  detectionScore: real('detection_score'), // Confidence of face detection
  // Bounding box in source frame [x1, y1, x2, y2]
  bbox: jsonb('bbox').$type<number[]>(),
  // Face crop image path
  faceCropPath: text('face_crop_path'), // Path to cropped face thumbnail
  // Source information
  sourceFrame: text('source_frame'), // Path to frame this was extracted from
  sourceVideo: text('source_video'), // Path to source video
  sourceDigestId: integer('source_digest_id'), // Link to video digest
  frameTimestamp: real('frame_timestamp'), // Timestamp in video
  frameIndex: integer('frame_index'), // Index of frame in digest
  // Face grouping metadata
  facesInFrame: integer('faces_in_frame').default(1), // How many faces were in this frame
  status: text('status').$type<FaceStatus>().default('pending'), // Review status
  // Matching metadata
  matchConfidence: real('match_confidence'), // Confidence of performer match (if matched)
  isVerified: boolean('is_verified').default(false), // Human-verified match
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  performerIdIdx: index('idx_performer_faces_performer').on(table.performerId),
  performerNameIdx: index('idx_performer_faces_name').on(table.performerName),
  sourceVideoIdx: index('idx_performer_faces_video').on(table.sourceVideo),
  genderIdx: index('idx_performer_faces_gender').on(table.gender),
  digestIdx: index('idx_performer_faces_digest').on(table.sourceDigestId),
  cropPathIdx: index('idx_performer_faces_crop').on(table.faceCropPath),
  statusIdx: index('idx_performer_faces_status').on(table.status),
  facesInFrameIdx: index('idx_performer_faces_faces_in_frame').on(table.facesInFrame),
  detectionScoreIdx: index('idx_performer_faces_det_score').on(table.detectionScore),
}));

export type CreatorFace = typeof performerFaces.$inferSelect;
export type NewCreatorFace = typeof performerFaces.$inferInsert;

// Discrepancy types for filename vs face detection mismatches
export const DISCREPANCY_TYPES = [
  'no_face_match',      // Parsed name has no matching face
  'low_confidence',     // Face match confidence below threshold
  'name_mismatch',      // Filename says X but face matches Y
  'gender_mismatch',    // Gender of face doesn't match expected
  'unknown_performer',  // Parsed name not in performers database
  'extra_face',         // Detected face not in parsed performers
] as const;
export type DiscrepancyType = typeof DISCREPANCY_TYPES[number];

// Resolution status for discrepancies
export const DISCREPANCY_STATUS = ['pending', 'confirmed', 'rejected', 'merged', 'ignored'] as const;
export type DiscrepancyStatus = typeof DISCREPANCY_STATUS[number];

// Resolution types
export const DISCREPANCY_RESOLUTIONS = ['use_filename', 'use_face', 'create_alias', 'ignore', 'assign_face'] as const;
export type DiscrepancyResolution = typeof DISCREPANCY_RESOLUTIONS[number];

/**
 * Tracks mismatches between filename-parsed performers and face detection results.
 * Used for admin review and resolution of performer identification issues.
 */
export const performerDiscrepancies = pgTable('performer_discrepancies', {
  id: serial('id').primaryKey(),

  // Source info
  videoId: integer('video_id').references(() => videos.id, { onDelete: 'cascade' }),
  digestId: integer('digest_id').references(() => videoDigests.id, { onDelete: 'cascade' }),

  // What filename parsing said
  parsedPerformerName: text('parsed_performer_name').notNull(),
  parsedMethod: text('parsed_method'),  // 'llm', 'regex', 'manual'
  parsedConfidence: real('parsed_confidence'),

  // What face detection found
  detectedPerformerId: integer('detected_performer_id').references(() => creators.id, { onDelete: 'set null' }),
  detectedPerformerName: text('detected_performer_name'),
  faceMatchConfidence: real('face_match_confidence'),
  faceSimilarity: real('face_similarity'),

  // Discrepancy details
  discrepancyType: text('discrepancy_type').$type<DiscrepancyType>().notNull(),

  // Resolution
  status: text('status').$type<DiscrepancyStatus>().default('pending').notNull(),
  resolvedBy: integer('resolved_by').references(() => mediaUsers.id, { onDelete: 'set null' }),
  resolvedAt: timestamp('resolved_at', { withTimezone: true }),
  resolution: text('resolution').$type<DiscrepancyResolution>(),
  adminNotes: text('admin_notes'),

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
}, (table) => ({
  videoIdx: index('idx_discrepancies_video').on(table.videoId),
  digestIdx: index('idx_discrepancies_digest').on(table.digestId),
  statusIdx: index('idx_discrepancies_status').on(table.status),
  typeIdx: index('idx_discrepancies_type').on(table.discrepancyType),
  detectedPerformerIdx: index('idx_discrepancies_detected_performer').on(table.detectedPerformerId),
}));

export type CreatorDiscrepancy = typeof performerDiscrepancies.$inferSelect;
export type NewCreatorDiscrepancy = typeof performerDiscrepancies.$inferInsert;

// ============================================
// Pipeline Management System
// ============================================

// Pipeline status lifecycle
export const PIPELINE_STATUS = ['pending', 'queued', 'running', 'paused', 'completed', 'failed', 'cancelled'] as const;
export type PipelineStatus = typeof PIPELINE_STATUS[number];

// Pipeline step status
export const PIPELINE_STEP_STATUS = ['pending', 'waiting_dependency', 'waiting_resource', 'running', 'completed', 'failed', 'skipped'] as const;
export type PipelineStepStatus = typeof PIPELINE_STEP_STATUS[number];

// Step types available in the pipeline system
export type PipelineStepType =
  | 'frame_extraction'
  | 'scene_detection'
  | 'vision_analysis'
  | 'transcription'
  | 'synthesis'
  | 'tag_generation'
  | 'clip_generation'
  | 'anime_generation'
  | 'custom';

// Pipeline definition - a multi-step workflow
export const pipelines = pgTable('pipelines', {
  id: serial('id').primaryKey(),
  name: text('name').notNull(),
  description: text('description'),
  status: text('status').notNull().default('pending').$type<PipelineStatus>(),
  priority: integer('priority').default(5),

  // Context for the entire pipeline (e.g., videoId, digestId)
  context: jsonb('context').$type<Record<string, unknown>>(),

  // Resource requirements aggregated from steps
  totalVramRequired: integer('total_vram_required').default(0),
  estimatedDurationMs: integer('estimated_duration_ms'),

  // Progress tracking
  progress: integer('progress').default(0),
  currentStepId: integer('current_step_id'),
  completedSteps: integer('completed_steps').default(0),
  totalSteps: integer('total_steps').default(0),

  // Error handling
  error: text('error'),
  retryCount: integer('retry_count').default(0),
  maxRetries: integer('max_retries').default(3),

  // Timestamps
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  queuedAt: timestamp('queued_at', { withTimezone: true }),
  startedAt: timestamp('started_at', { withTimezone: true }),
  completedAt: timestamp('completed_at', { withTimezone: true }),

  // Crash recovery
  lastHeartbeat: timestamp('last_heartbeat', { withTimezone: true }),
  runningOnNode: text('running_on_node'), // For future multi-node support
}, (table) => ({
  statusIdx: index('idx_pipelines_status').on(table.status),
  priorityIdx: index('idx_pipelines_priority').on(table.priority, table.createdAt),
  heartbeatIdx: index('idx_pipelines_heartbeat').on(table.lastHeartbeat),
}));

// Individual steps within a pipeline
export const pipelineSteps = pgTable('pipeline_steps', {
  id: serial('id').primaryKey(),
  pipelineId: integer('pipeline_id').notNull().references(() => pipelines.id, { onDelete: 'cascade' }),

  // Step definition
  name: text('name').notNull(),
  stepType: text('step_type').notNull().$type<PipelineStepType>(),
  sequenceOrder: integer('sequence_order').notNull(),

  // Dependencies (array of step IDs that must complete first)
  dependsOn: jsonb('depends_on').$type<number[]>().default([]),

  // Resource requirements
  resourceCategory: text('resource_category').default('cpu').$type<ResourceCategory>(),
  vramRequired: integer('vram_required').default(0),
  requiredContainers: jsonb('required_containers').$type<string[]>().default([]),

  // Step configuration
  config: jsonb('config').$type<Record<string, unknown>>(),

  // Execution state
  status: text('status').notNull().default('pending').$type<PipelineStepStatus>(),
  progress: integer('progress').default(0),
  result: jsonb('result').$type<unknown>(),
  error: text('error'),

  // Timing
  estimatedDurationMs: integer('estimated_duration_ms'),
  startedAt: timestamp('started_at', { withTimezone: true }),
  completedAt: timestamp('completed_at', { withTimezone: true }),
  durationMs: integer('duration_ms'),
}, (table) => ({
  pipelineIdx: index('idx_pipeline_steps_pipeline').on(table.pipelineId),
  sequenceIdx: index('idx_pipeline_steps_sequence').on(table.pipelineId, table.sequenceOrder),
  statusIdx: index('idx_pipeline_steps_status').on(table.status),
}));

// Step-level progress events for detailed observability
export const pipelineStepEvents = pgTable('pipeline_step_events', {
  id: serial('id').primaryKey(),
  stepId: integer('step_id').notNull().references(() => pipelineSteps.id, { onDelete: 'cascade' }),

  eventType: text('event_type').notNull(), // 'stage_start', 'progress', 'prompt_call', 'stage_complete', 'log', 'error'
  eventData: jsonb('event_data').$type<Record<string, unknown>>(),

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  stepIdx: index('idx_pipeline_step_events_step').on(table.stepId),
  createdAtIdx: index('idx_pipeline_step_events_created').on(table.createdAt),
}));

// Real-time GPU/resource tracking snapshots
export const resourceSnapshots = pgTable('resource_snapshots', {
  id: serial('id').primaryKey(),

  // GPU metrics (from nvidia-smi)
  gpuIndex: integer('gpu_index').default(0),
  gpuName: text('gpu_name'),
  vramUsedMb: integer('vram_used_mb'),
  vramTotalMb: integer('vram_total_mb'),
  gpuUtilization: integer('gpu_utilization'), // 0-100%
  gpuTemperature: integer('gpu_temperature'), // Celsius

  // Active processes using GPU
  activeProcesses: jsonb('active_processes').$type<Array<{
    pid: number;
    name: string;
    vramMb: number;
  }>>(),

  // Linked to pipeline/step if applicable
  pipelineId: integer('pipeline_id'),
  stepId: integer('step_id'),

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  createdAtIdx: index('idx_resource_snapshots_created').on(table.createdAt),
  pipelineIdx: index('idx_resource_snapshots_pipeline').on(table.pipelineId),
}));

// Pipeline templates for common workflows
export const pipelineTemplates = pgTable('pipeline_templates', {
  id: serial('id').primaryKey(),
  name: text('name').notNull().unique(),
  displayName: text('display_name').notNull(),
  description: text('description'),

  // Template definition - array of step configurations
  steps: jsonb('steps').$type<Array<{
    name: string;
    stepType: PipelineStepType;
    dependsOn: string[]; // Names of previous steps (resolved to IDs at creation)
    config?: Record<string, unknown>;
    resourceCategory: ResourceCategory;
    vramRequired: number;
    requiredContainers: string[];
  }>>().notNull(),

  // Default context schema (for validation)
  contextSchema: jsonb('context_schema').$type<Record<string, unknown>>(),

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  nameIdx: index('idx_pipeline_templates_name').on(table.name),
}));

// Pipeline types
export type Pipeline = typeof pipelines.$inferSelect;
export type NewPipeline = typeof pipelines.$inferInsert;
export type PipelineStep = typeof pipelineSteps.$inferSelect;
export type NewPipelineStep = typeof pipelineSteps.$inferInsert;
export type PipelineStepEvent = typeof pipelineStepEvents.$inferSelect;
export type NewPipelineStepEvent = typeof pipelineStepEvents.$inferInsert;
export type ResourceSnapshot = typeof resourceSnapshots.$inferSelect;
export type NewResourceSnapshot = typeof resourceSnapshots.$inferInsert;
export type PipelineTemplate = typeof pipelineTemplates.$inferSelect;
export type NewPipelineTemplate = typeof pipelineTemplates.$inferInsert;

// ============================================
// PAYMENT & SUBSCRIPTION TABLES
// ============================================

// Subscription status enum
export type SubscriptionStatus = 'none' | 'active' | 'grace_period' | 'delinquent' | 'lifetime';

// Payment status enum
export type PaymentStatus = 'unmatched' | 'matched' | 'applied' | 'disputed' | 'refunded';

// Invoice status enum
export type InvoiceStatus = 'pending' | 'paid' | 'overdue' | 'cancelled' | 'refunded';

// Subscription Plans - Available subscription tiers
export const subscriptionPlans = pgTable('subscription_plans', {
  id: serial('id').primaryKey(),
  name: text('name').notNull().unique(), // 'monthly', 'quarterly', 'yearly', 'lifetime'
  displayName: text('display_name').notNull(), // 'Monthly Subscription'
  description: text('description'),
  priceCAD: integer('price_cad').notNull(), // Price in cents (e.g., 1000 = $10.00)
  durationDays: integer('duration_days'), // null for lifetime
  isActive: boolean('is_active').default(true),
  sortOrder: integer('sort_order').default(0),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
});

// User Subscriptions - Tracks individual user subscription status
export const userSubscriptions = pgTable('user_subscriptions', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  planId: integer('plan_id').references(() => subscriptionPlans.id),
  status: text('status').notNull().default('inactive').$type<SubscriptionStatus>(),
  startDate: timestamp('start_date', { withTimezone: true }),
  endDate: timestamp('end_date', { withTimezone: true }), // null for lifetime
  gracePeriodEndDate: timestamp('grace_period_end_date', { withTimezone: true }),
  lastPaymentId: integer('last_payment_id'),
  autoRenew: boolean('auto_renew').default(true),
  notes: text('notes'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  userIdx: uniqueIndex('idx_user_subscriptions_user').on(table.userId),
  statusIdx: index('idx_user_subscriptions_status').on(table.status),
  endDateIdx: index('idx_user_subscriptions_end_date').on(table.endDate),
}));

// Invoices - Generated invoices for users
export const invoices = pgTable('invoices', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  planId: integer('plan_id').references(() => subscriptionPlans.id),
  invoiceNumber: text('invoice_number').notNull().unique(), // INV-2026-00001
  amountCAD: integer('amount_cad').notNull(), // Amount in cents
  status: text('status').notNull().default('pending').$type<InvoiceStatus>(),
  dueDate: timestamp('due_date', { withTimezone: true }).notNull(),
  paidAt: timestamp('paid_at', { withTimezone: true }),
  paymentId: integer('payment_id'),
  description: text('description'),
  notes: text('notes'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  userIdx: index('idx_invoices_user').on(table.userId),
  statusIdx: index('idx_invoices_status').on(table.status),
  dueDateIdx: index('idx_invoices_due_date').on(table.dueDate),
  invoiceNumberIdx: index('idx_invoices_number').on(table.invoiceNumber),
}));

// Raw email payload structure for storage
export interface PaymentEmailPayload {
  from: string;
  fromName?: string;
  to: string;
  subject: string;
  textBody?: string;
  htmlBody?: string;
  date: string;
  headers?: Record<string, string>;
  messageId?: string;
}

// Payments - Records of actual payments received (e-transfers)
export const payments = pgTable('payments', {
  id: serial('id').primaryKey(),
  invoiceId: integer('invoice_id').references(() => invoices.id),
  userId: integer('user_id').references(() => mediaUsers.id, { onDelete: 'set null' }),

  // E-transfer details
  senderEmail: text('sender_email').notNull(),
  senderName: text('sender_name'),
  amountCAD: integer('amount_cad').notNull(), // Amount in cents
  referenceNumber: text('reference_number').unique(), // Interac reference
  customMessage: text('custom_message'), // Contains username hint

  // Matching/processing status
  status: text('status').notNull().default('unmatched').$type<PaymentStatus>(),
  matchConfidence: real('match_confidence'), // 0.0-1.0 confidence score
  matchMethod: text('match_method'), // 'email_exact', 'username_message', 'manual', 'llm'

  // Raw email data for audit trail
  rawEmailJson: jsonb('raw_email_json').$type<PaymentEmailPayload>(),
  receivedAt: timestamp('received_at', { withTimezone: true }).notNull(),
  processedAt: timestamp('processed_at', { withTimezone: true }),
  processedBy: integer('processed_by'), // Admin who manually matched

  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  updatedAt: timestamp('updated_at', { withTimezone: true }),
}, (table) => ({
  userIdx: index('idx_payments_user').on(table.userId),
  invoiceIdx: index('idx_payments_invoice').on(table.invoiceId),
  statusIdx: index('idx_payments_status').on(table.status),
  senderEmailIdx: index('idx_payments_sender_email').on(table.senderEmail),
  referenceIdx: index('idx_payments_reference').on(table.referenceNumber),
  receivedAtIdx: index('idx_payments_received_at').on(table.receivedAt),
}));

// Payment Audit Log - Audit trail for all payment operations
export const paymentAuditLog = pgTable('payment_audit_log', {
  id: serial('id').primaryKey(),
  paymentId: integer('payment_id').references(() => payments.id),
  invoiceId: integer('invoice_id').references(() => invoices.id),
  userId: integer('user_id').references(() => mediaUsers.id),
  action: text('action').notNull(), // 'created', 'matched', 'applied', 'refunded', 'manual_override', etc.
  previousState: jsonb('previous_state').$type<Record<string, unknown>>(),
  newState: jsonb('new_state').$type<Record<string, unknown>>(),
  performedBy: integer('performed_by'), // Admin user ID
  notes: text('notes'),
  ipAddress: text('ip_address'),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  paymentIdx: index('idx_payment_audit_payment').on(table.paymentId),
  invoiceIdx: index('idx_payment_audit_invoice').on(table.invoiceId),
  userIdx: index('idx_payment_audit_user').on(table.userId),
  actionIdx: index('idx_payment_audit_action').on(table.action),
  createdAtIdx: index('idx_payment_audit_created').on(table.createdAt),
}));

// Payment & Subscription types
export type SubscriptionPlan = typeof subscriptionPlans.$inferSelect;
export type NewSubscriptionPlan = typeof subscriptionPlans.$inferInsert;
export type UserSubscription = typeof userSubscriptions.$inferSelect;
export type NewUserSubscription = typeof userSubscriptions.$inferInsert;
export type Invoice = typeof invoices.$inferSelect;
export type NewInvoice = typeof invoices.$inferInsert;
export type Payment = typeof payments.$inferSelect;
export type NewPayment = typeof payments.$inferInsert;
export type PaymentAuditLogEntry = typeof paymentAuditLog.$inferSelect;
export type NewPaymentAuditLogEntry = typeof paymentAuditLog.$inferInsert;

// ============================================
// Notification System Tables
// ============================================

// Notification types enum
export const NOTIFICATION_TYPES = [
  'friend_request',
  'friend_request_accepted',
  'poke',
  'video_recommendation',
  'watch_party_invite',
  'achievement_unlocked',
  'comment_reply',
  'digest_completed',
  'digest_failed',
  'upload_approved',
  'upload_rejected',
  'system',
] as const;
export type NotificationType = typeof NOTIFICATION_TYPES[number];

// Notification metadata interface for JSONB column
export interface NotificationMetadata {
  actorUsername?: string;
  mediaId?: number;
  mediaTitle?: string;
  mediaThumbnail?: string;
  achievementName?: string;
  achievementIcon?: string;
  achievementRarity?: AchievementRarity;
  partyId?: number;
  digestId?: number;
  uploadId?: number;
  message?: string;
  [key: string]: unknown;
}

// Main notifications table
export const notifications = pgTable('notifications', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().references(() => mediaUsers.id, { onDelete: 'cascade' }),
  type: text('type').notNull().$type<NotificationType>(),
  title: text('title').notNull(),
  message: text('message'),
  referenceType: text('reference_type'), // 'user', 'media', 'watch_party', 'digest', 'upload', etc.
  referenceId: integer('reference_id'),
  actorId: integer('actor_id').references(() => mediaUsers.id, { onDelete: 'set null' }),
  metadata: jsonb('metadata').$type<NotificationMetadata>(),
  isRead: boolean('is_read').default(false),
  actionUrl: text('action_url'),
  groupKey: text('group_key'), // For aggregation (e.g., "poke_123" to group multiple pokes from same user)
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  readAt: timestamp('read_at', { withTimezone: true }),
}, (table) => ({
  userUnreadIdx: index('idx_notifications_user_unread').on(table.userId, table.isRead),
  userCreatedIdx: index('idx_notifications_user_created').on(table.userId, table.createdAt),
  typeIdx: index('idx_notifications_type').on(table.type),
  createdAtIdx: index('idx_notifications_created').on(table.createdAt),
  actorIdx: index('idx_notifications_actor').on(table.actorId),
}));

// User notification preferences table
export const notificationPreferences = pgTable('notification_preferences', {
  id: serial('id').primaryKey(),
  userId: integer('user_id').notNull().unique().references(() => mediaUsers.id, { onDelete: 'cascade' }),

  // Email toggles per notification type
  emailFriendRequest: boolean('email_friend_request').default(true),
  emailFriendRequestAccepted: boolean('email_friend_request_accepted').default(true),
  emailPoke: boolean('email_poke').default(true),
  emailVideoRecommendation: boolean('email_video_recommendation').default(true),
  emailWatchPartyInvite: boolean('email_watch_party_invite').default(true),
  emailAchievement: boolean('email_achievement').default(false),
  emailDigestComplete: boolean('email_digest_complete').default(true),
  emailDigestFailed: boolean('email_digest_failed').default(true),
  emailUploadApproved: boolean('email_upload_approved').default(true),
  emailUploadRejected: boolean('email_upload_rejected').default(true),

  // In-app toggles per notification type (default all on)
  inAppFriendRequest: boolean('inapp_friend_request').default(true),
  inAppFriendRequestAccepted: boolean('inapp_friend_request_accepted').default(true),
  inAppPoke: boolean('inapp_poke').default(true),
  inAppVideoRecommendation: boolean('inapp_video_recommendation').default(true),
  inAppWatchPartyInvite: boolean('inapp_watch_party_invite').default(true),
  inAppAchievement: boolean('inapp_achievement').default(true),
  inAppDigestComplete: boolean('inapp_digest_complete').default(true),
  inAppDigestFailed: boolean('inapp_digest_failed').default(true),
  inAppUploadApproved: boolean('inapp_upload_approved').default(true),
  inAppUploadRejected: boolean('inapp_upload_rejected').default(true),
  inAppCommentReply: boolean('inapp_comment_reply').default(true),
  inAppSystem: boolean('inapp_system').default(true),

  // Quiet hours settings
  quietHoursEnabled: boolean('quiet_hours_enabled').default(false),
  quietHoursStart: text('quiet_hours_start'), // "22:00" (24-hour format)
  quietHoursEnd: text('quiet_hours_end'),     // "08:00"

  updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  userIdx: index('idx_notification_preferences_user').on(table.userId),
}));

// Notification System Types
export type Notification = typeof notifications.$inferSelect;
export type NewNotification = typeof notifications.$inferInsert;
export type NotificationPreferences = typeof notificationPreferences.$inferSelect;
export type NewNotificationPreferences = typeof notificationPreferences.$inferInsert;

// ============================================
// Geo-Blocking Tables
// ============================================

export const GEO_BLOCKING_MODES = ['disabled', 'blocklist', 'allowlist'] as const;
export type GeoBlockingMode = typeof GEO_BLOCKING_MODES[number];

export const geoBlockingRules = pgTable('geo_blocking_rules', {
  id: serial('id').primaryKey(),
  country: text('country').notNull(),        // ISO Alpha-2 code (required)
  countryName: text('country_name'),          // Display name
  region: text('region'),                     // Optional - state/province ISO code
  city: text('city'),                         // Optional - city name
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
  createdBy: integer('created_by').references(() => mediaUsers.id),
  note: text('note'),                         // Admin note explaining why blocked
}, (table) => ({
  countryIdx: index('idx_geo_blocking_country').on(table.country),
  uniqueRule: uniqueIndex('idx_geo_blocking_unique').on(table.country, table.region, table.city),
}));

export type GeoBlockingRule = typeof geoBlockingRules.$inferSelect;
export type NewGeoBlockingRule = typeof geoBlockingRules.$inferInsert;

// ============================================
// Inbox Publishing Tracking
// ============================================

export const INBOX_FILE_TYPES = ['clip', 'scene', 'video'] as const;
export type InboxFileType = typeof INBOX_FILE_TYPES[number];

export const publishedInboxFiles = pgTable('published_inbox_files', {
  id: serial('id').primaryKey(),
  sourceInboxPath: text('source_inbox_path').notNull(), // Original path in inbox
  filename: text('filename').notNull(),
  publishedAt: timestamp('published_at', { withTimezone: true }).defaultNow(),
  publishedToPath: text('published_to_path'), // Destination path in public directory
  publicMediaId: integer('public_media_id').references(() => publicMedia.id, { onDelete: 'set null' }),
  fileType: text('file_type').$type<InboxFileType>().notNull(),
}, (table) => ({
  sourcePathIdx: index('idx_published_inbox_source').on(table.sourceInboxPath),
  fileTypeIdx: index('idx_published_inbox_file_type').on(table.fileType),
}));

export type PublishedInboxFile = typeof publishedInboxFiles.$inferSelect;
export type NewPublishedInboxFile = typeof publishedInboxFiles.$inferInsert;

// ============================================
// Video OCR Results Table
// ============================================

export const OCR_TEXT_TYPES = ['credits', 'title_card', 'overlay', 'unknown'] as const;
export type OcrTextType = typeof OCR_TEXT_TYPES[number];

export const videoOcrResults = pgTable('video_ocr_results', {
  id: serial('id').primaryKey(),
  digestId: integer('digest_id').notNull(),
  videoId: integer('video_id').notNull(),
  frameIndex: integer('frame_index').notNull(),
  timestamp: real('timestamp').notNull(),
  extractedText: text('extracted_text').notNull(),
  confidence: real('confidence'),
  textType: text('text_type').$type<OcrTextType>(),
  sourceFrame: text('source_frame').notNull(),
  createdAt: timestamp('created_at', { withTimezone: true }).defaultNow(),
}, (table) => ({
  digestIdx: index('idx_video_ocr_results_digest').on(table.digestId),
  videoIdx: index('idx_video_ocr_results_video').on(table.videoId),
}));

export type VideoOcrResult = typeof videoOcrResults.$inferSelect;
export type NewVideoOcrResult = typeof videoOcrResults.$inferInsert;
