// backend/prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

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

model User {
  id           String  @id @default(uuid())
  email        String  @unique
  mobile       String? @unique
  passwordHash String?
  googleId     String? @unique
  facebookId   String? @unique
  name         String
  language     String  @default("en")
  trialUsedAt  DateTime?

  memberships        Membership[]
  ownedOrganizations Organization[] @relation("OrganizationOwner")
  sessions           Session[] // 👈 ADD THIS - opposite relation for Session
  sentStaffInvites   StaffInvitation[]
  sentOrganizationInvitations OrganizationInvitation[]
  statusChangedLabCaseTasks LabCaseTask[] @relation("LabCaseTaskLastStatusChangedBy")
  assignedLabCaseTasks LabCaseTask[] @relation("LabCaseTaskAssignee")
  labCaseTaskStatusEvents LabCaseTaskStatusEvent[]
  labCaseComments LabCaseComment[]
  labCaseActivities LabCaseActivity[]
  phoneVerificationCodes PhoneVerificationCode[]
  userNotifications UserNotification[]
  actedUserNotifications UserNotification[] @relation("UserNotificationActor")

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

  @@map("users")
}

model PhoneVerificationCode {
  id         String    @id @default(uuid())
  userId     String?
  mobile     String
  codeHash   String
  purpose    String
  expiresAt  DateTime
  verifiedAt DateTime?
  createdAt  DateTime  @default(now())

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

  @@index([mobile, purpose, createdAt])
  @@map("phone_verification_codes")
}

model OrganizationType {
  id   String @id @default(uuid())
  name String @unique // "CLINIC" or "LAB"

  organizations Organization[]
  features      Feature[] // 👈 ADD THIS - opposite relation for Feature

  @@map("organization_types")
}

model Organization {
  id      String  @id @default(uuid())
  name    String
  email   String  @unique
  phone   String?
  address String?

  typeId String
  type   OrganizationType @relation(fields: [typeId], references: [id])

  ownerId String
  owner   User   @relation("OrganizationOwner", fields: [ownerId], references: [id])

  memberships Membership[]
  planId      String?
  plan        Plan?        @relation(fields: [planId], references: [id])

  sharedWithMe     OrganizationLink[] @relation("OrganizationB")
  sharedWithOthers OrganizationLink[] @relation("OrganizationA")
  sentOrganizationInvitations OrganizationInvitation[] @relation("OrganizationInvitationInviter")
  createdPatients  Patient[] @relation("PatientCreatedBy")
  appointments     Appointment[]
  treatments       Treatment[]
  labCaseSends     LabCaseSend[]
  labCaseComments  LabCaseComment[]

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

  @@map("organizations")
}

model Patient {
  id                      String    @id @default(uuid())
  firstName               String
  lastName                String
  mobile                  String    @unique
  email                   String?
  dateOfBirth             DateTime?
  notes                   String?
  isActive                Boolean   @default(true)
  createdByOrganizationId String?
  createdAt               DateTime  @default(now())
  updatedAt               DateTime  @updatedAt

  createdByOrganization Organization? @relation("PatientCreatedBy", fields: [createdByOrganizationId], references: [id], onDelete: SetNull)
  treatments            Treatment[]
  appointments          Appointment[]

  @@index([lastName, firstName])
  @@map("patients")
}

model Appointment {
  id             String   @id @default(uuid())
  organizationId String
  patientId      String
  providerUserId String
  startAt        DateTime
  endAt          DateTime
  purpose        String

  organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  patient      Patient      @relation(fields: [patientId], references: [id], onDelete: Cascade)
  treatment    Treatment?

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

  @@index([organizationId, providerUserId, startAt])
  @@index([organizationId, startAt])
  @@map("appointments")
}

enum LabTaskStatus {
  IN_PROGRESS
  COMPLETED
}

enum LabCaseCommentSide {
  LAB
  CLINIC
}

model Treatment {
  id             String   @id @default(uuid())
  organizationId String
  patientId      String
  appointmentId  String?  @unique
  providerUserId String
  title          String
  treatmentAt    DateTime

  organization Organization    @relation(fields: [organizationId], references: [id], onDelete: Cascade)
  patient      Patient         @relation(fields: [patientId], references: [id], onDelete: Cascade)
  appointment  Appointment?    @relation(fields: [appointmentId], references: [id], onDelete: SetNull)
  details      TreatmentDetail[]
  labCases     LabCase[]

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

  @@index([patientId, treatmentAt])
  @@map("treatments")
}

model TreatmentDetail {
  id            String @id @default(uuid())
  treatmentId   String
  clientKey     String?
  sortOrder     Int
  treatmentType String
  teeth         Json
  /** Selection groups: connected spans + singles. Shape: { groupId, kind, teeth[] }[] */
  toothSelectionGroups Json?
  comment       String?

  treatment   Treatment                   @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
  attachments TreatmentDetailAttachment[]
  labCaseLink LabCaseDetail?
  labCaseTasks LabCaseTask[]
  toothProsthesis LabCaseToothProsthesis[]

  @@index([treatmentId, sortOrder])
  @@map("treatment_details")
}

model TreatmentDetailAttachment {
  id              String  @id @default(uuid())
  detailId        String?
  appointmentId   String?
  detailClientKey String?
  fileName        String
  mimeType        String
  sizeBytes       Int
  storagePath     String

  detail TreatmentDetail? @relation(fields: [detailId], references: [id], onDelete: Cascade)
  labCaseLinks LabCaseAttachment[]

  createdAt DateTime @default(now())

  @@index([appointmentId, detailClientKey])
  @@index([detailId])
  @@map("treatment_detail_attachments")
}

model LabCase {
  id                        String    @id @default(uuid())
  treatmentId               String
  clientKey                 String?
  sortOrder                 Int
  destinationOrganizationId String?
  sentAt                    DateTime?
  dueDate                   DateTime?
  isImportant               Boolean   @default(false)
  /// Optional code from an external lab app (e.g. exocad) for print/matching.
  externalCode              String?
  accessToken               String?   @unique

  treatment       Treatment                @relation(fields: [treatmentId], references: [id], onDelete: Cascade)
  details         LabCaseDetail[]
  sends           LabCaseSend[]
  tasks           LabCaseTask[]
  toothProsthesis LabCaseToothProsthesis[]
  comments        LabCaseComment[]
  attachments     LabCaseAttachment[]
  activities      LabCaseActivity[]
  userReadStates  LabCaseUserReadState[]

  @@index([treatmentId, sortOrder])
  @@map("lab_cases")
}

model LabCaseAttachment {
  labCaseId    String
  attachmentId String

  labCase    LabCase                   @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  attachment TreatmentDetailAttachment @relation(fields: [attachmentId], references: [id], onDelete: Cascade)

  @@id([labCaseId, attachmentId])
  @@index([attachmentId])
  @@map("lab_case_attachments")
}

model LabCaseDetail {
  labCaseId         String @unique
  treatmentDetailId String @unique

  labCase LabCase         @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  detail  TreatmentDetail @relation(fields: [treatmentDetailId], references: [id], onDelete: Cascade)

  @@id([labCaseId, treatmentDetailId])
  @@map("lab_case_details")
}

model LabCaseSend {
  id             String   @id @default(uuid())
  labCaseId      String
  organizationId String
  sentAt         DateTime @default(now())

  labCase      LabCase      @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)

  @@unique([labCaseId, organizationId])
  @@map("lab_case_sends")
}

model TreatmentType {
  id                     String  @id @default(uuid())
  code                   String  @unique
  labDependent           Boolean @default(false)
  sortOrder              Int     @default(0)
  isActive               Boolean @default(true)
  availableInAppointments Boolean @default(true)
  availableInTreatment    Boolean @default(true)

  @@map("treatment_types")
}

enum CatalogEntityKind {
  TREATMENT_TYPE
  PROSTHESIS_TYPE
  LAB_WORKFLOW_STEP
}

model CatalogTranslation {
  id         String            @id @default(uuid())
  entityKind CatalogEntityKind
  entityCode String
  locale     String
  label      String

  @@unique([entityKind, entityCode, locale])
  @@map("catalog_translations")
}

model ProsthesisType {
  id                   String  @id @default(uuid())
  code                 String  @unique
  sortOrder            Int     @default(0)
  isActive             Boolean @default(true)
  skipPackingShipping  Boolean @default(false)

  steps ProsthesisTypeStep[]

  @@map("prosthesis_types")
}

model LabWorkflowStep {
  id        String @id @default(uuid())
  code      String @unique
  sortOrder Int    @default(0)

  prosthesisSteps ProsthesisTypeStep[]

  @@map("lab_workflow_steps")
}

model ProsthesisTypeStep {
  id                String @id @default(uuid())
  prosthesisTypeId  String
  labWorkflowStepId String
  stepOrder         Int

  prosthesisType  ProsthesisType  @relation(fields: [prosthesisTypeId], references: [id], onDelete: Cascade)
  labWorkflowStep LabWorkflowStep @relation(fields: [labWorkflowStepId], references: [id], onDelete: Cascade)

  @@unique([prosthesisTypeId, stepOrder])
  @@unique([prosthesisTypeId, labWorkflowStepId])
  @@map("prosthesis_type_steps")
}

model LabCaseToothProsthesis {
  id                 String @id @default(uuid())
  labCaseId          String
  treatmentDetailId  String
  tooth              String
  prosthesisTypeCode String
  /** Links to TreatmentDetail.toothSelectionGroups[].groupId for task grouping. */
  selectionGroupId   String @default("")

  labCase LabCase         @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  detail  TreatmentDetail @relation(fields: [treatmentDetailId], references: [id], onDelete: Cascade)

  @@unique([labCaseId, treatmentDetailId, tooth])
  @@map("lab_case_tooth_prosthesis")
}

model LabCaseTask {
  id                 String        @id @default(uuid())
  labCaseId          String
  treatmentDetailId  String
  teeth              Json
  treatmentType      String
  prosthesisTypeCode String
  /** Empty string for legacy tasks; otherwise matches selection group id. */
  selectionGroupId   String        @default("")
  workflowStepCode   String
  stepOrder          Int
  stepLabel          String
  status             LabTaskStatus @default(IN_PROGRESS)
  assigneeUserId     String?
  assignedAt         DateTime?
  lastStatusChangedByUserId String?
  lastStatusChangedAt       DateTime?

  labCase  LabCase         @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  detail   TreatmentDetail @relation(fields: [treatmentDetailId], references: [id], onDelete: Cascade)
  assignee User?           @relation("LabCaseTaskAssignee", fields: [assigneeUserId], references: [id], onDelete: SetNull)
  lastStatusChangedBy User? @relation("LabCaseTaskLastStatusChangedBy", fields: [lastStatusChangedByUserId], references: [id], onDelete: SetNull)
  statusEvents LabCaseTaskStatusEvent[]

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

  @@unique([labCaseId, treatmentDetailId, prosthesisTypeCode, stepOrder], map: "lab_case_tasks_case_detail_prosthesis_step_key")
  @@index([labCaseId, status])
  @@index([assigneeUserId])
  @@map("lab_case_tasks")
}

model LabCaseTaskStatusEvent {
  id         String        @id @default(uuid())
  taskId     String
  fromStatus LabTaskStatus?
  toStatus   LabTaskStatus
  changedByUserId String?
  changedAt  DateTime      @default(now())

  task LabCaseTask @relation(fields: [taskId], references: [id], onDelete: Cascade)
  changedBy User?  @relation(fields: [changedByUserId], references: [id], onDelete: SetNull)

  @@index([taskId, changedAt])
  @@map("lab_case_task_status_events")
}

model LabCaseComment {
  id                   String             @id @default(uuid())
  labCaseId            String
  authorUserId         String?
  authorOrganizationId String?
  authorSide           LabCaseCommentSide
  body                 String
  visibleToClinic      Boolean            @default(false)

  labCase      LabCase       @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  authorUser   User?         @relation(fields: [authorUserId], references: [id], onDelete: SetNull)
  authorOrganization Organization? @relation(fields: [authorOrganizationId], references: [id], onDelete: SetNull)

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

  @@index([labCaseId, createdAt])
  @@map("lab_case_comments")
}

enum LabCaseActivityType {
  CASE_SENT
  CLINIC_COMMENT
  LAB_COMMENT
  CASE_IMPORTANT
  CASE_AMENDED
  TASK_COMPLETED
  TASK_ASSIGNED
}

enum LabCaseTabReadTarget {
  CASES
  TASKS
  TREATMENT
}

enum UserNotificationType {
  CASE_SENT
  CLINIC_COMMENT
  LAB_COMMENT
  LAB_COMMENT_CLINIC
  CASE_IMPORTANT
  TASK_COMPLETED
  TASK_ASSIGNED
  CONNECTION_REQUEST
  STAFF_INVITE
}

/// Fan-out inbox row per recipient. Independent of LabCaseActivity tab badges.
model UserNotification {
  id             String               @id @default(uuid())
  userId         String
  organizationId String
  type           UserNotificationType
  actorUserId    String?
  payload        Json?
  href           String
  readAt         DateTime?
  createdAt      DateTime             @default(now())

  user      User  @relation(fields: [userId], references: [id], onDelete: Cascade)
  actorUser User? @relation("UserNotificationActor", fields: [actorUserId], references: [id], onDelete: SetNull)

  @@index([userId, organizationId, createdAt])
  @@index([userId, organizationId, readAt])
  @@map("user_notifications")
}

model LabCaseActivity {
  id          String              @id @default(uuid())
  labCaseId   String
  type        LabCaseActivityType
  actorUserId String?
  payload     Json?
  createdAt   DateTime            @default(now())

  labCase   LabCase @relation(fields: [labCaseId], references: [id], onDelete: Cascade)
  actorUser User?   @relation(fields: [actorUserId], references: [id], onDelete: SetNull)

  @@index([labCaseId, createdAt])
  @@index([type, createdAt])
  @@map("lab_case_activities")
}

model LabCaseUserReadState {
  userId         String
  organizationId String
  labCaseId      String
  lastReadAt     DateTime

  labCase LabCase @relation(fields: [labCaseId], references: [id], onDelete: Cascade)

  @@id([userId, organizationId, labCaseId])
  @@index([userId, organizationId])
  @@map("lab_case_user_read_states")
}

model LabCaseUserTabReadState {
  userId         String
  organizationId String
  tab            LabCaseTabReadTarget
  lastReadAt     DateTime

  @@id([userId, organizationId, tab])
  @@map("lab_case_user_tab_read_states")
}

model Plan {
  id       String @id @default(uuid())
  name     String @unique // "Solo", "Small", "Medium", "Large", "Enterprise"
  maxUsers Int // 1, 5, 10, 15, 999999 for unlimited
  price    Float
  features Json // Store feature flags as JSON

  organizations Organization[]

  @@map("plans")
}

model Membership {
  id String @id @default(uuid())

  userId         String
  organizationId String

  isOwner Boolean @default(false)
  isActive Boolean @default(true)

  user         User         @relation(fields: [userId], references: [id])
  organization Organization @relation(fields: [organizationId], references: [id])

  permissions MembershipPermission[]
  invitations StaffInvitation[]
  workingHoursSchedule StaffWorkingHoursSchedule?

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

  @@unique([userId, organizationId])
  @@map("memberships")
}

model StaffWorkingHoursSchedule {
  id               String @id @default(uuid())
  membershipId     String @unique
  autoRepeatWeekly Boolean @default(true)

  membership Membership @relation(fields: [membershipId], references: [id], onDelete: Cascade)
  blocks     StaffWorkingHoursBlock[]

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

  @@map("staff_working_hours_schedules")
}

model StaffWorkingHoursBlock {
  id          String @id @default(uuid())
  scheduleId  String
  dayOfWeek   Int
  startMinute Int
  endMinute   Int
  sortOrder   Int    @default(0)

  schedule StaffWorkingHoursSchedule @relation(fields: [scheduleId], references: [id], onDelete: Cascade)

  @@index([scheduleId, dayOfWeek, sortOrder])
  @@map("staff_working_hours_blocks")
}

model StaffInvitation {
  id String @id @default(uuid())

  membershipId String
  invitedById  String
  tokenHash    String   @unique
  expiresAt    DateTime
  acceptedAt   DateTime?
  revokedAt    DateTime?

  membership Membership @relation(fields: [membershipId], references: [id], onDelete: Cascade)
  invitedBy  User       @relation(fields: [invitedById], references: [id], onDelete: Cascade)

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

  @@index([membershipId, createdAt])
  @@map("staff_invitations")
}

model Permission {
  id          String  @id @default(uuid())
  name        String  @unique
  description String?

  memberships MembershipPermission[]
  featureId   String?
  feature     Feature?               @relation(fields: [featureId], references: [id])

  @@map("permissions")
}

model MembershipPermission {
  membershipId String
  permissionId String

  membership Membership @relation(fields: [membershipId], references: [id])
  permission Permission @relation(fields: [permissionId], references: [id])

  @@id([membershipId, permissionId])
  @@map("membership_permissions")
}

model Feature {
  id          String  @id @default(uuid())
  name        String  @unique
  description String?

  permissions        Permission[]
  organizationTypeId String?
  organizationType   OrganizationType? @relation(fields: [organizationTypeId], references: [id])

  @@map("features")
}

/// Bidirectional clinic↔lab relationship. One row per unordered pair (A id < B id).
///
/// Two product flows share this table:
/// 1. **Connection request** — inviter found an existing subscribed org in search; row is PENDING
///    until the counterpart accepts. No OrganizationInvitation row.
/// 2. **Invitation link** — inviter could not find the org; inviteOrganization() creates a
///    placeholder org, an OrganizationInvitation (signup token), and a PENDING link here so the
///    inviter does not need a second request after signup. acceptInvite() sets the link to ACTIVE.
///
/// `sharedDataTypes` stores metadata (not shared clinical data yet). While PENDING, entries like
/// `requested_by:{orgId}` record who initiated the request (see OrganizationService).
model OrganizationLink {
  id String @id @default(uuid())

  organizationAId String
  organizationBId String
  status          LinkStatus @default(PENDING)
  sharedDataTypes Json

  organizationA Organization @relation("OrganizationA", fields: [organizationAId], references: [id])
  organizationB Organization @relation("OrganizationB", fields: [organizationBId], references: [id])

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

  @@unique([organizationAId, organizationBId])
  @@map("organization_links")
}

/// Signup invite for a counterpart org that is not on DyoLink yet (or has no active subscription).
/// Complements OrganizationLink: invite flow always creates both records in one transaction.
///
/// Only the token *hash* is stored; the plain token is returned once on create/regenerate and may
/// be cached in the browser (see frontend useOrganizationInviteLinkCopy). Regenerating rotates
/// tokenHash and expiresAt on the same invitation row.
///
/// `invitedOrganizationId` points at a placeholder Organization (pending-* email) until accept;
/// list() joins open invitations to links so the UI can offer "copy invitation link" on the
/// pending connection row (pendingInvitationId on the API response).
model OrganizationInvitation {
  id String @id @default(uuid())

  inviterOrganizationId String
  inviterUserId         String

  invitedOrganizationId   String?
  invitedOrganizationName String
  invitedOwnerEmail       String
  invitedOrganizationType String
  tokenHash               String   @unique
  expiresAt               DateTime
  acceptedAt              DateTime?
  revokedAt               DateTime?

  inviterOrganization Organization @relation("OrganizationInvitationInviter", fields: [inviterOrganizationId], references: [id], onDelete: Cascade)
  inviterUser         User         @relation(fields: [inviterUserId], references: [id], onDelete: Cascade)

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

  @@index([inviterOrganizationId, createdAt])
  @@index([invitedOwnerEmail, createdAt])
  @@map("organization_invitations")
}

model Session {
  id           String   @id @default(uuid())
  userId       String
  token        String   @unique
  refreshToken String?  @unique
  expiresAt    DateTime
  userAgent    String?
  ipAddress    String?

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

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

  @@map("sessions")
}

/// OrganizationLink lifecycle. Invitation rows use overlapping semantics in API mappers
/// (e.g. accepted invitation → ACTIVE in listInvitationHistory).
enum LinkStatus {
  PENDING
  ACTIVE
  REJECTED
  BLOCKED
}
