datasource db {
  provider = "sqlite"
  url      = "file:dev.db"
}

generator client {
  provider      = "cargo prisma"
  output        = "../../crates/prisma/src/prisma"
  module_path   = "prisma"
  client_format = "folder"
}

generator sync {
  provider      = "cargo prisma-sync"
  output        = "../../crates/prisma/src/prisma_sync"
  client_format = "folder"
}

/// @local
model CRDTOperation {
  id Int @id @default(autoincrement())

  timestamp BigInt
  model     Int

  record_id Bytes
  // Enum: ??
  kind      String
  data      Bytes

  instance_id Int
  instance    Instance @relation(fields: [instance_id], references: [id])

  @@map("crdt_operation")
}

/// @local
model CloudCRDTOperation {
  id Int @id @default(autoincrement())

  timestamp BigInt
  model     Int

  record_id Bytes
  // Enum: ??
  kind      String
  data      Bytes

  instance_id Int
  instance    Instance @relation(fields: [instance_id], references: [id])

  @@map("cloud_crdt_operation")
}

/// @deprecated: This model has to exist solely for backwards compatibility.
/// @local
model Node {
  id           Int      @id @default(autoincrement())
  pub_id       Bytes    @unique
  name         String
  // Enum: sd_core::node::Platform
  platform     Int
  date_created DateTime
  identity     Bytes? // TODO: Change to required field in future

  @@map("node")
}

// represents a single `.db` file (SQLite DB) that is paired to the current library.
// A `LibraryInstance` is always owned by a single `Node` but it's possible for that node to change (or two to be owned by a single node).
/// @local(id: pub_id)
model Instance {
  id              Int    @id @default(autoincrement()) // This is is NOT globally unique
  pub_id          Bytes  @unique // This UUID is meaningless and exists soley cause the `uhlc::ID` must be 16-bit. Really this should be derived from the `identity` field.
  // Enum: sd_p2p::Identity (or sd_core::p2p::IdentityOrRemoteIdentity in early versions)
  identity        Bytes?
  // Enum: sd_core::node::RemoteIdentity
  remote_identity Bytes

  // Enum: uuid::Uuid
  node_id              Bytes
  // Enum: sd_core::node::RemoteIdentity
  node_remote_identity Bytes? // TODO: This should not be optional
  metadata             Bytes? // TODO: This should not be optional

  last_seen    DateTime // Time core started for owner, last P2P message for P2P node
  date_created DateTime

  // clock timestamp for sync
  timestamp BigInt?

  locations          Location[]
  CRDTOperation      CRDTOperation[]
  CloudCRDTOperation CloudCRDTOperation[]

  @@map("instance")
}

/// @local
model Statistics {
  id                                Int      @id @default(autoincrement())
  date_captured                     DateTime @default(now())
  total_object_count                Int      @default(0)
  library_db_size                   String   @default("0")
  // local calulations
  total_local_bytes_used            String   @default("0")
  total_local_bytes_capacity        String   @default("0")
  total_local_bytes_free            String   @default("0")
  // library calculations
  total_library_bytes               String   @default("0")
  total_library_unique_bytes        String   @default("0")
  total_library_preview_media_bytes String   @default("0")

  @@map("statistics")
}

/// @local
model Volume {
  id                    Int      @id @default(autoincrement())
  name                  String
  mount_point           String
  total_bytes_capacity  String   @default("0")
  total_bytes_available String   @default("0")
  disk_type             String?
  filesystem            String?
  is_system             Boolean  @default(false)
  date_modified         DateTime @default(now())

  @@unique([mount_point, name])
  @@map("volume")
}

/// @shared(id: pub_id, modelId: 1)
model Location {
  id     Int   @id @default(autoincrement())
  pub_id Bytes @unique

  name                   String?
  path                   String?
  total_capacity         Int?
  available_capacity     Int?
  size_in_bytes          Bytes?
  is_archived            Boolean?
  generate_preview_media Boolean?
  sync_preview_media     Boolean?
  hidden                 Boolean?
  date_created           DateTime?

  scan_state Int @default(0) // Enum: sd_core::location::ScanState

  // this should just be a local-only cache but it's too much effort to broadcast online locations rn (@brendan)
  instance_id Int?
  instance    Instance? @relation(fields: [instance_id], references: [id], onDelete: SetNull)

  file_paths    FilePath[]
  indexer_rules IndexerRulesInLocation[]

  @@map("location")
}

/// @shared(id: pub_id, modelId: 2)
model FilePath {
  id     Int   @id @default(autoincrement())
  pub_id Bytes @unique

  is_dir Boolean?

  // content addressable storage id - blake3 sampled checksum
  cas_id             String?
  // full byte contents digested into blake3 checksum
  integrity_checksum String?

  // location that owns this path
  location_id Int?
  location    Location? @relation(fields: [location_id], references: [id], onDelete: SetNull)

  // the path of the file relative to its location
  materialized_path String?

  // the name and extension, MUST have 'COLLATE NOCASE' in migration
  name      String?
  extension String?
  hidden    Boolean?

  size_in_bytes       String? // deprecated
  size_in_bytes_bytes Bytes?

  inode Bytes? // This is actually an unsigned 64 bit integer, but we don't have this type in SQLite

  // the unique Object for this file path
  object_id Int?
  object    Object? @relation(fields: [object_id], references: [id], onDelete: SetNull)

  key_id Int? // replacement for encryption
  // permissions       String?

  date_created  DateTime?
  date_modified DateTime?
  date_indexed  DateTime?

  // key Key? @relation(fields: [key_id], references: [id])

  @@unique([location_id, materialized_path, name, extension])
  @@unique([location_id, inode])
  @@index([location_id])
  @@index([location_id, materialized_path])
  @@map("file_path")
}

/// @shared(id: pub_id, modelId: 3)
model Object {
  id     Int   @id @default(autoincrement())
  pub_id Bytes @unique
  // Enum: sd_file_ext::kind::ObjectKind
  kind   Int?

  key_id        Int?
  // handy ways to mark an object
  hidden        Boolean?
  favorite      Boolean?
  important     Boolean?
  // if we have generated preview media for this object on at least one Node
  // commented out for now by @brendonovich since they they're irrelevant to the sync system
  // has_thumbnail     Boolean?
  // has_thumbstrip    Boolean?
  // has_video_preview Boolean?
  // TODO: change above to:
  // has_generated_thumbnail     Boolean  @default(false)
  // has_generated_thumbstrip    Boolean  @default(false)
  // has_generated_video_preview Boolean  @default(false)
  // integration with ipfs
  // ipfs_id           String?
  // plain text note
  note          String?
  // the original known creation date of this object
  date_created  DateTime?
  date_accessed DateTime?

  tags        TagOnObject[]
  labels      LabelOnObject[]
  albums      ObjectInAlbum[]
  spaces      ObjectInSpace[]
  file_paths  FilePath[]
  // comments   Comment[]
  exif_data   ExifData?
  ffmpeg_data FfmpegData?

  // key Key? @relation(fields: [key_id], references: [id])

  @@map("object")
}

// // keys allow us to know exactly which files can be decrypted with a given key
// // they can be "mounted" to a client, and then used to decrypt files automatically
// /// @shared(id: uuid)
// model Key {
//     id                Int       @id @default(autoincrement())
//     // uuid to identify the key
//     uuid              String    @unique
//     version           String
//     key_type          String
//     // the name that the user sets
//     name              String?
//     // is this key the default for encryption?
//     // was not tagged as unique as i'm not too sure if PCR will handle it
//     // can always be tagged as unique, the keys API will need updating to use `find_unique()`
//     default           Boolean   @default(false)
//     // nullable if concealed for security
//     date_created      DateTime? @default(now())
//     // encryption algorithm used to encrypt the key
//     algorithm         String
//     // hashing algorithm used for hashing the key with the content salt
//     hashing_algorithm String
//     // salt used for encrypting data with this key
//     content_salt      Bytes
//     // the *encrypted* master key (48 bytes)
//     master_key        Bytes
//     // the nonce used for encrypting the master key
//     master_key_nonce  Bytes
//     // the nonce used for encrypting the key
//     key_nonce         Bytes
//     // the *encrypted* key
//     key               Bytes
//     // the salt used for deriving the KEK (used for encrypting the master key) from the root key
//     salt              Bytes

//     automount Boolean @default(false)

//     objects    Object[]
//     file_paths FilePath[]

//     @@map("key")
// }

/// @shared(id: object, modelId: 4)
model ExifData {
  id Int @id @default(autoincrement())

  resolution     Bytes?
  media_date     Bytes?
  media_location Bytes?
  camera_data    Bytes?
  artist         String?
  description    String?
  copyright      String?
  exif_version   String?

  // purely for sorting/ordering, never sent to the frontend as they'd be useless
  // these are also usually one-way, and not reversible
  // (e.g. we can't get `MediaDate::Utc(2023-09-26T22:04:37+01:00)` from `1695758677` as we don't store the TZ)
  epoch_time BigInt? // time since unix epoch

  object_id Int    @unique
  object    Object @relation(fields: [object_id], references: [id], onDelete: Cascade)

  @@map("exif_data")
}

model FfmpegData {
  id Int @id @default(autoincrement())

  // Internal FFmpeg properties
  formats    String
  bit_rate   Bytes // Actually a i64 in the backend
  duration   Bytes? // Actually a i64 in the backend
  start_time Bytes? // Actually a i64 in the backend

  chapters FfmpegMediaChapter[]
  programs FfmpegMediaProgram[]

  // Metadata for search
  title         String?
  creation_time DateTime?
  date          DateTime?
  album_artist  String?
  disc          String?
  track         String?
  album         String?
  artist        String?
  metadata      Bytes?

  object    Object @relation(fields: [object_id], references: [id], onDelete: Cascade)
  object_id Int    @unique

  @@map("ffmpeg_data")
}

model FfmpegMediaChapter {
  chapter_id Int

  start Bytes // Actually a i64 in the backend
  end   Bytes // Actually a i64 in the backend

  time_base_den Int
  time_base_num Int

  // Metadata for search
  title    String?
  metadata Bytes?

  ffmpeg_data    FfmpegData @relation(fields: [ffmpeg_data_id], references: [id], onDelete: Cascade)
  ffmpeg_data_id Int

  @@id(name: "likeId", [ffmpeg_data_id, chapter_id])
  @@map("ffmpeg_media_chapter")
}

model FfmpegMediaProgram {
  program_id Int

  streams FfmpegMediaStream[]

  // Metadata for search
  name     String?
  metadata Bytes?

  ffmpeg_data    FfmpegData @relation(fields: [ffmpeg_data_id], references: [id], onDelete: Cascade)
  ffmpeg_data_id Int

  @@id(name: "likeId", [ffmpeg_data_id, program_id])
  @@map("ffmpeg_media_program")
}

model FfmpegMediaStream {
  stream_id Int

  name                  String?
  codec                 FfmpegMediaCodec?
  aspect_ratio_num      Int
  aspect_ratio_den      Int
  frames_per_second_num Int
  frames_per_second_den Int
  time_base_real_den    Int
  time_base_real_num    Int
  dispositions          String?

  // Metadata for search
  title    String?
  encoder  String?
  language String?
  duration Bytes? // Actually a i64 in the backend
  metadata Bytes?

  program        FfmpegMediaProgram @relation(fields: [ffmpeg_data_id, program_id], references: [ffmpeg_data_id, program_id], onDelete: Cascade)
  program_id     Int
  ffmpeg_data_id Int

  @@id(name: "likeId", [ffmpeg_data_id, program_id, stream_id])
  @@map("ffmpeg_media_stream")
}

model FfmpegMediaCodec {
  id Int @id @default(autoincrement())

  kind     String?
  sub_kind String?
  tag      String?
  name     String?
  profile  String?
  bit_rate Int

  video_props FfmpegMediaVideoProps?
  audio_props FfmpegMediaAudioProps?

  stream         FfmpegMediaStream @relation(fields: [ffmpeg_data_id, program_id, stream_id], references: [ffmpeg_data_id, program_id, stream_id], onDelete: Cascade)
  stream_id      Int
  program_id     Int
  ffmpeg_data_id Int

  @@unique([ffmpeg_data_id, program_id, stream_id])
  @@map("ffmpeg_media_codec")
}

model FfmpegMediaVideoProps {
  id Int @id @default(autoincrement())

  pixel_format     String?
  color_range      String?
  bits_per_channel Int?
  color_space      String?
  color_primaries  String?
  color_transfer   String?
  field_order      String?
  chroma_location  String?
  width            Int
  height           Int
  aspect_ratio_num Int?
  aspect_ratio_Den Int?
  properties       String?

  codec    FfmpegMediaCodec @relation(fields: [codec_id], references: [id], onDelete: Cascade)
  codec_id Int              @unique

  @@map("ffmpeg_media_video_props")
}

model FfmpegMediaAudioProps {
  id Int @id @default(autoincrement())

  delay          Int
  padding        Int
  sample_rate    Int?
  sample_format  String?
  bit_per_sample Int?
  channel_layout String?

  codec    FfmpegMediaCodec @relation(fields: [codec_id], references: [id], onDelete: Cascade)
  codec_id Int              @unique

  @@map("ffmpeg_media_audio_props")
}

//// Tag ////

/// @shared(id: pub_id, modelId: 5)
model Tag {
  id     Int     @id @default(autoincrement())
  pub_id Bytes   @unique
  name   String?
  color  String?

  is_hidden Boolean? // user hidden entire tag

  date_created  DateTime?
  date_modified DateTime?

  tag_objects TagOnObject[]

  @@map("tag")
}

/// @relation(item: object, group: tag, modelId: 6)
model TagOnObject {
  object_id Int
  object    Object @relation(fields: [object_id], references: [id], onDelete: Restrict)

  tag_id Int
  tag    Tag @relation(fields: [tag_id], references: [id], onDelete: Restrict)

  date_created DateTime?

  @@id([tag_id, object_id])
  @@map("tag_on_object")
}

//// Label ////

/// @shared(id: name, modelId: 7)
model Label {
  id            Int       @id @default(autoincrement())
  name          String    @unique
  date_created  DateTime?
  date_modified DateTime?

  label_objects LabelOnObject[]

  @@map("label")
}

/// @relation(item: object, group: label, modelId: 8)
model LabelOnObject {
  date_created DateTime @default(now())

  object_id Int
  object    Object @relation(fields: [object_id], references: [id], onDelete: Restrict)

  label_id Int
  label    Label @relation(fields: [label_id], references: [id], onDelete: Restrict)

  @@id([label_id, object_id])
  @@map("label_on_object")
}

//// Space ////

model Space {
  id            Int       @id @default(autoincrement())
  pub_id        Bytes     @unique
  name          String?
  description   String?
  date_created  DateTime?
  date_modified DateTime?

  objects ObjectInSpace[]

  @@map("space")
}

model ObjectInSpace {
  space_id Int
  space    Space @relation(fields: [space_id], references: [id], onDelete: Restrict)

  object_id Int
  object    Object @relation(fields: [object_id], references: [id], onDelete: Restrict)

  @@id([space_id, object_id])
  @@map("object_in_space")
}

//// Job ////

model Job {
  id Bytes @id

  name   String?
  action String? // Will be composed of "{action_description}(-{children_order})*"

  // Enum: sd_core::job::job_manager:JobStatus
  status Int? // 0 = Queued

  // List of errors, separated by "\n\n" in case of failed jobs or completed with errors
  errors_text         String? // Deprecated, use `critical_error` or `non_critical_errors` instead
  critical_error      String? // Serialized error field with info about the failed job after completion
  non_critical_errors Bytes? // Serialized non-critical errors field with info about the completed job with errors after completion

  data     Bytes? // Deprecated
  metadata Bytes? // Serialized metadata field with info about the job after completion

  parent_id Bytes?

  task_count                Int?
  completed_task_count      Int?
  date_estimated_completion DateTime? // Estimated timestamp that the job will be complete at

  date_created   DateTime?
  date_started   DateTime? // Started execution
  date_completed DateTime? // Finished execution

  parent   Job?  @relation("jobs_dependency", fields: [parent_id], references: [id], onDelete: SetNull)
  children Job[] @relation("jobs_dependency")

  @@map("job")
}

//// Album ////

model Album {
  id        Int      @id
  pub_id    Bytes    @unique
  name      String?
  is_hidden Boolean?

  date_created  DateTime?
  date_modified DateTime?

  objects ObjectInAlbum[]

  @@map("album")
}

model ObjectInAlbum {
  date_created DateTime?
  album_id     Int
  album        Album     @relation(fields: [album_id], references: [id], onDelete: NoAction)

  object_id Int
  object    Object @relation(fields: [object_id], references: [id], onDelete: NoAction)

  @@id([album_id, object_id])
  @@map("object_in_album")
}

//// Indexer Rules ////

model IndexerRule {
  id     Int   @id @default(autoincrement())
  pub_id Bytes @unique

  name           String?   @unique
  default        Boolean?
  rules_per_kind Bytes?
  date_created   DateTime?
  date_modified  DateTime?

  locations IndexerRulesInLocation[]

  @@map("indexer_rule")
}

model IndexerRulesInLocation {
  location_id Int
  location    Location @relation(fields: [location_id], references: [id], onDelete: Restrict)

  indexer_rule_id Int
  indexer_rule    IndexerRule @relation(fields: [indexer_rule_id], references: [id], onDelete: Restrict)

  @@id([location_id, indexer_rule_id])
  @@map("indexer_rule_in_location")
}

/// @shared(id: key, modelId: 9)
model Preference {
  key   String @id
  value Bytes?

  @@map("preference")
}

model Notification {
  id         Int       @id @default(autoincrement())
  read       Boolean   @default(false)
  // Enum: crate::api::notifications::NotificationData
  data       Bytes
  expires_at DateTime?

  @@map("notification")
}

/// @shared(id: pub_id, modelId: 10)
model SavedSearch {
  id     Int   @id @default(autoincrement())
  pub_id Bytes @unique

  // enum: crate::api::search::saved::SearchTarget
  target  String?
  search  String?
  filters String?

  name        String?
  icon        String?
  description String?
  // order         Int? // Add this line to include ordering

  date_created  DateTime?
  date_modified DateTime?

  @@map("saved_search")
}
