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"
}

//// Sync ////

model SharedOperation {
    id        Bytes  @id
    timestamp BigInt
    model     String

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

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

    // attestation Bytes

    @@map("shared_operation")
}

model RelationOperation {
    id        Bytes  @id
    timestamp BigInt
    relation  String

    item_id  Bytes
    group_id Bytes

    kind String
    data Bytes

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

    @@map("relation_operation")
}

/// @deprecated: This model has to exist solely for backwards compatibility.
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
    node_peer_id String? // TODO: Remove as part of - https://linear.app/spacedriveapp/issue/ENG-757/p2p-library-portability

    @@map("node")
}

/// @local(id: pub_id)
// 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).
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_core::p2p::IdentityOrRemoteIdentity
    identity Bytes

    node_id       Bytes
    node_name     String
    // Enum: sd_core::node::Platform
    node_platform Int

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

    // clock timestamp for sync
    timestamp BigInt?

    // attestation Bytes

    SharedOperation   SharedOperation[]
    RelationOperation RelationOperation[]
    locations         Location[]

    @@map("instance")
}

model Statistics {
    id                   Int      @id @default(autoincrement())
    date_captured        DateTime @default(now())
    total_object_count   Int      @default(0)
    library_db_size      String   @default("0")
    total_bytes_used     String   @default("0")
    total_bytes_capacity String   @default("0")
    total_unique_bytes   String   @default("0")
    total_bytes_free     String   @default("0")
    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)
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?

    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)
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)
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[]
    media_data MediaData?

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

    @@map("object")
}

// if there is a conflicting cas_id, the conficting file should be updated to have a larger cas_id as
//the field is unique, however this record is kept to tell the indexer (upon discovering this CAS) that
//there is alternate versions of the file and to check by a full integrity hash to define for which to associate with.
// @brendan: nah this probably won't fly
// model FileConflict {
//     original_object_id   Int @unique
//     detactched_object_id Int @unique

//     @@map("file_conflict")
// }

// 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")
// }

model MediaData {
    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

    // video-specific
    // duration Int?
    // fps      Int?
    // streams  Int?
    // video_codec   String? // eg: "h264, h265, av1"
    // audio_codec String? // eg: "opus"

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

    @@map("media_data")
}

//// Tag ////

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

    // Enum: ??
    redundancy_goal Int?

    date_created  DateTime?
    date_modified DateTime?

    tag_objects TagOnObject[]

    @@map("tag")
}

/// @relation(item: tag, group: object)
model TagOnObject {
    tag_id Int
    tag    Tag @relation(fields: [tag_id], references: [id], onDelete: Restrict)

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

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

//// Label ////

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

    label_objects LabelOnObject[]

    @@map("label")
}

model LabelOnObject {
    date_created DateTime @default(now())

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

    object_id Int
    object    Object @relation(fields: [object_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?

    data     Bytes? // Serialized data to be used on pause/resume
    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")
}

//// Comment ////

// model Comment {
//     id            Int      @id @default(autoincrement())
//     pub_id        Bytes    @unique
//     content       String
//     date_created  DateTime @default(now())
//     date_modified DateTime @default(now())
//     object_id     Int?
//     object        Object?  @relation(fields: [object_id], references: [id])

//     @@map("comment")
// }

//// Indexer Rules ////

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

    name           String?
    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)
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")
}
