"The query root for this schema"
type Query {
  # Filters
  findSavedFilter(id: ID!): SavedFilter
  findSavedFilters(mode: FilterMode): [SavedFilter!]!
  findDefaultFilter(mode: FilterMode!): SavedFilter
    @deprecated(reason: "default filter now stored in UI config")

  "Find a file by its id or path"
  findFile(id: ID, path: String): BaseFile!

  "Queries for Files"
  findFiles(
    file_filter: FileFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindFilesResultType!

  "Find a file by its id or path"
  findFolder(id: ID, path: String): Folder!

  "Queries for Files"
  findFolders(
    folder_filter: FolderFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindFoldersResultType!

  "Find a scene by ID or Checksum"
  findScene(id: ID, checksum: String): Scene
  findSceneByHash(input: SceneHashInput!): Scene

  "A function which queries Scene objects"
  findScenes(
    scene_filter: SceneFilterType
    scene_ids: [Int!] @deprecated(reason: "use ids")
    ids: [ID!]
    filter: FindFilterType
  ): FindScenesResultType!

  findScenesByPathRegex(filter: FindFilterType): FindScenesResultType!

  """
  Returns any groups of scenes that are perceptual duplicates within the queried distance
  and the difference between their duration is smaller than durationDiff
  """
  findDuplicateScenes(
    distance: Int
    """
    Max difference in seconds between files in order to be considered for similarity matching.
    Fractional seconds are ok: 0.5 will mean only files that have durations within 0.5 seconds between them will be matched based on PHash distance.
    """
    duration_diff: Float
  ): [[Scene!]!]!

  "Return valid stream paths"
  sceneStreams(id: ID): [SceneStreamEndpoint!]!

  parseSceneFilenames(
    filter: FindFilterType
    config: SceneParserInput!
  ): SceneParserResultType!

  "A function which queries SceneMarker objects"
  findSceneMarkers(
    scene_marker_filter: SceneMarkerFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindSceneMarkersResultType!

  findImage(id: ID, checksum: String): Image

  "A function which queries Scene objects"
  findImages(
    image_filter: ImageFilterType
    image_ids: [Int!] @deprecated(reason: "use ids")
    ids: [ID!]
    filter: FindFilterType
  ): FindImagesResultType!

  "Find a performer by ID"
  findPerformer(id: ID!): Performer
  "A function which queries Performer objects"
  findPerformers(
    performer_filter: PerformerFilterType
    filter: FindFilterType
    performer_ids: [Int!] @deprecated(reason: "use ids")
    ids: [ID!]
  ): FindPerformersResultType!

  "Find a studio by ID"
  findStudio(id: ID!): Studio
  "A function which queries Studio objects"
  findStudios(
    studio_filter: StudioFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindStudiosResultType!

  "Find a movie by ID"
  findMovie(id: ID!): Movie @deprecated(reason: "Use findGroup instead")
  "A function which queries Movie objects"
  findMovies(
    movie_filter: MovieFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindMoviesResultType! @deprecated(reason: "Use findGroups instead")

  "Find a group by ID"
  findGroup(id: ID!): Group
  "A function which queries Group objects"
  findGroups(
    group_filter: GroupFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindGroupsResultType!

  findGallery(id: ID!): Gallery
  findGalleries(
    gallery_filter: GalleryFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindGalleriesResultType!

  findTag(id: ID!): Tag
  findTags(
    tag_filter: TagFilterType
    filter: FindFilterType
    ids: [ID!]
  ): FindTagsResultType!

  "Retrieve random scene markers for the wall"
  markerWall(q: String): [SceneMarker!]!
  "Retrieve random scenes for the wall"
  sceneWall(q: String): [Scene!]!

  "Get marker strings"
  markerStrings(q: String, sort: String): [MarkerStringsResultType]!
  "Get stats"
  stats: StatsResultType!
  "Organize scene markers by tag for a given scene ID"
  sceneMarkerTags(scene_id: ID!): [SceneMarkerTag!]!

  logs: [LogEntry!]!

  # Scrapers

  "List available scrapers"
  listScrapers(types: [ScrapeContentType!]!): [Scraper!]!

  "Scrape for a single scene"
  scrapeSingleScene(
    source: ScraperSourceInput!
    input: ScrapeSingleSceneInput!
  ): [ScrapedScene!]!
  "Scrape for multiple scenes"
  scrapeMultiScenes(
    source: ScraperSourceInput!
    input: ScrapeMultiScenesInput!
  ): [[ScrapedScene!]!]!

  "Scrape for a single studio"
  scrapeSingleStudio(
    source: ScraperSourceInput!
    input: ScrapeSingleStudioInput!
  ): [ScrapedStudio!]!

  "Scrape for a single tag"
  scrapeSingleTag(
    source: ScraperSourceInput!
    input: ScrapeSingleTagInput!
  ): [ScrapedTag!]!

  "Scrape for a single performer"
  scrapeSinglePerformer(
    source: ScraperSourceInput!
    input: ScrapeSinglePerformerInput!
  ): [ScrapedPerformer!]!
  "Scrape for multiple performers"
  scrapeMultiPerformers(
    source: ScraperSourceInput!
    input: ScrapeMultiPerformersInput!
  ): [[ScrapedPerformer!]!]!

  "Scrape for a single gallery"
  scrapeSingleGallery(
    source: ScraperSourceInput!
    input: ScrapeSingleGalleryInput!
  ): [ScrapedGallery!]!

  "Scrape for a single movie"
  scrapeSingleMovie(
    source: ScraperSourceInput!
    input: ScrapeSingleMovieInput!
  ): [ScrapedMovie!]! @deprecated(reason: "Use scrapeSingleGroup instead")

  "Scrape for a single group"
  scrapeSingleGroup(
    source: ScraperSourceInput!
    input: ScrapeSingleGroupInput!
  ): [ScrapedGroup!]!

  "Scrape for a single image"
  scrapeSingleImage(
    source: ScraperSourceInput!
    input: ScrapeSingleImageInput!
  ): [ScrapedImage!]!

  "Scrapes content based on a URL"
  scrapeURL(url: String!, ty: ScrapeContentType!): ScrapedContent

  "Scrapes a complete performer record based on a URL"
  scrapePerformerURL(url: String!): ScrapedPerformer
  "Scrapes a complete scene record based on a URL"
  scrapeSceneURL(url: String!): ScrapedScene
  "Scrapes a complete gallery record based on a URL"
  scrapeGalleryURL(url: String!): ScrapedGallery
  "Scrapes a complete image record based on a URL"
  scrapeImageURL(url: String!): ScrapedImage
  "Scrapes a complete movie record based on a URL"
  scrapeMovieURL(url: String!): ScrapedMovie
    @deprecated(reason: "Use scrapeGroupURL instead")
  "Scrapes a complete group record based on a URL"
  scrapeGroupURL(url: String!): ScrapedGroup

  # Plugins
  "List loaded plugins"
  plugins: [Plugin!]
  "List available plugin operations"
  pluginTasks: [PluginTask!]

  # Packages
  "List installed packages"
  installedPackages(type: PackageType!): [Package!]!
  "List available packages"
  availablePackages(type: PackageType!, source: String!): [Package!]!

  # Config
  "Returns the current, complete configuration"
  configuration: ConfigResult!
  "Returns an array of paths for the given path"
  directory(
    "The directory path to list"
    path: String
    "Desired collation locale. Determines the order of the directory result. eg. 'en-US', 'pt-BR', ..."
    locale: String = "en"
  ): Directory!
  validateStashBoxCredentials(input: StashBoxInput!): StashBoxValidationResult!

  # System status
  systemStatus: SystemStatus!

  # Job status
  jobQueue: [Job!]
  findJob(input: FindJobInput!): Job

  dlnaStatus: DLNAStatus!

  # Get everything

  allScenes: [Scene!]! @deprecated(reason: "Use findScenes instead")
  allSceneMarkers: [SceneMarker!]!
    @deprecated(reason: "Use findSceneMarkers instead")
  allImages: [Image!]! @deprecated(reason: "Use findImages instead")
  allGalleries: [Gallery!]! @deprecated(reason: "Use findGalleries instead")

  allPerformers: [Performer!]!
  allTags: [Tag!]! @deprecated(reason: "Use findTags instead")
  allStudios: [Studio!]! @deprecated(reason: "Use findStudios instead")
  allMovies: [Movie!]! @deprecated(reason: "Use findGroups instead")

  # Get everything with minimal metadata

  # Version
  version: Version!

  # LatestVersion
  latestversion: LatestVersion!
}

type Mutation {
  setup(input: SetupInput!): Boolean!

  "Migrates the schema to the required version. Returns the job ID"
  migrate(input: MigrateInput!): ID!

  "Downloads and installs ffmpeg and ffprobe binaries into the configuration directory. Returns the job ID."
  downloadFFMpeg: ID!

  sceneCreate(input: SceneCreateInput!): Scene
  sceneUpdate(input: SceneUpdateInput!): Scene
  sceneMerge(input: SceneMergeInput!): Scene
  bulkSceneUpdate(input: BulkSceneUpdateInput!): [Scene!]
  sceneDestroy(input: SceneDestroyInput!): Boolean!
  scenesDestroy(input: ScenesDestroyInput!): Boolean!
  scenesUpdate(input: [SceneUpdateInput!]!): [Scene]

  "Increments the o-counter for a scene. Returns the new value"
  sceneIncrementO(id: ID!): Int! @deprecated(reason: "Use sceneAddO instead")
  "Decrements the o-counter for a scene. Returns the new value"
  sceneDecrementO(id: ID!): Int! @deprecated(reason: "Use sceneRemoveO instead")

  "Increments the o-counter for a scene. Uses the current time if none provided."
  sceneAddO(id: ID!, times: [Timestamp!]): HistoryMutationResult!
  "Decrements the o-counter for a scene, removing the last recorded time if specific time not provided. Returns the new value"
  sceneDeleteO(id: ID!, times: [Timestamp!]): HistoryMutationResult!

  "Resets the o-counter for a scene to 0. Returns the new value"
  sceneResetO(id: ID!): Int!

  "Sets the resume time point (if provided) and adds the provided duration to the scene's play duration"
  sceneSaveActivity(id: ID!, resume_time: Float, playDuration: Float): Boolean!

  "Resets the resume time point and play duration"
  sceneResetActivity(
    id: ID!
    reset_resume: Boolean
    reset_duration: Boolean
  ): Boolean!

  "Increments the play count for the scene. Returns the new play count value."
  sceneIncrementPlayCount(id: ID!): Int!
    @deprecated(reason: "Use sceneAddPlay instead")

  "Increments the play count for the scene. Uses the current time if none provided."
  sceneAddPlay(id: ID!, times: [Timestamp!]): HistoryMutationResult!
  "Decrements the play count for the scene, removing the specific times or the last recorded time if not provided."
  sceneDeletePlay(id: ID!, times: [Timestamp!]): HistoryMutationResult!
  "Resets the play count for a scene to 0. Returns the new play count value."
  sceneResetPlayCount(id: ID!): Int!

  "Generates screenshot at specified time in seconds. Leave empty to generate default screenshot"
  sceneGenerateScreenshot(id: ID!, at: Float): String!

  sceneMarkerCreate(input: SceneMarkerCreateInput!): SceneMarker
  sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker
  bulkSceneMarkerUpdate(input: BulkSceneMarkerUpdateInput!): [SceneMarker!]
  sceneMarkerDestroy(id: ID!): Boolean!
  sceneMarkersDestroy(ids: [ID!]!): Boolean!

  sceneAssignFile(input: AssignSceneFileInput!): Boolean!

  imageUpdate(input: ImageUpdateInput!): Image
  bulkImageUpdate(input: BulkImageUpdateInput!): [Image!]
  imageDestroy(input: ImageDestroyInput!): Boolean!
  imagesDestroy(input: ImagesDestroyInput!): Boolean!
  imagesUpdate(input: [ImageUpdateInput!]!): [Image]

  "Increments the o-counter for an image. Returns the new value"
  imageIncrementO(id: ID!): Int!
  "Decrements the o-counter for an image. Returns the new value"
  imageDecrementO(id: ID!): Int!
  "Resets the o-counter for a image to 0. Returns the new value"
  imageResetO(id: ID!): Int!

  galleryCreate(input: GalleryCreateInput!): Gallery
  galleryUpdate(input: GalleryUpdateInput!): Gallery
  bulkGalleryUpdate(input: BulkGalleryUpdateInput!): [Gallery!]
  galleryDestroy(input: GalleryDestroyInput!): Boolean!
  galleriesUpdate(input: [GalleryUpdateInput!]!): [Gallery]

  addGalleryImages(input: GalleryAddInput!): Boolean!
  removeGalleryImages(input: GalleryRemoveInput!): Boolean!
  setGalleryCover(input: GallerySetCoverInput!): Boolean!
  resetGalleryCover(input: GalleryResetCoverInput!): Boolean!

  galleryChapterCreate(input: GalleryChapterCreateInput!): GalleryChapter
  galleryChapterUpdate(input: GalleryChapterUpdateInput!): GalleryChapter
  galleryChapterDestroy(id: ID!): Boolean!

  performerCreate(input: PerformerCreateInput!): Performer
  performerUpdate(input: PerformerUpdateInput!): Performer
  performerDestroy(input: PerformerDestroyInput!): Boolean!
  performersDestroy(ids: [ID!]!): Boolean!
  bulkPerformerUpdate(input: BulkPerformerUpdateInput!): [Performer!]
  performerMerge(input: PerformerMergeInput!): Performer!

  studioCreate(input: StudioCreateInput!): Studio
  studioUpdate(input: StudioUpdateInput!): Studio
  studioDestroy(input: StudioDestroyInput!): Boolean!
  studiosDestroy(ids: [ID!]!): Boolean!
  bulkStudioUpdate(input: BulkStudioUpdateInput!): [Studio!]

  movieCreate(input: MovieCreateInput!): Movie
    @deprecated(reason: "Use groupCreate instead")
  movieUpdate(input: MovieUpdateInput!): Movie
    @deprecated(reason: "Use groupUpdate instead")
  movieDestroy(input: MovieDestroyInput!): Boolean!
    @deprecated(reason: "Use groupDestroy instead")
  moviesDestroy(ids: [ID!]!): Boolean!
    @deprecated(reason: "Use groupsDestroy instead")
  bulkMovieUpdate(input: BulkMovieUpdateInput!): [Movie!]
    @deprecated(reason: "Use bulkGroupUpdate instead")

  groupCreate(input: GroupCreateInput!): Group
  groupUpdate(input: GroupUpdateInput!): Group
  groupDestroy(input: GroupDestroyInput!): Boolean!
  groupsDestroy(ids: [ID!]!): Boolean!
  bulkGroupUpdate(input: BulkGroupUpdateInput!): [Group!]

  addGroupSubGroups(input: GroupSubGroupAddInput!): Boolean!
  removeGroupSubGroups(input: GroupSubGroupRemoveInput!): Boolean!

  "Reorder sub groups within a group. Returns true if successful."
  reorderSubGroups(input: ReorderSubGroupsInput!): Boolean!

  tagCreate(input: TagCreateInput!): Tag
  tagUpdate(input: TagUpdateInput!): Tag
  tagDestroy(input: TagDestroyInput!): Boolean!
  tagsDestroy(ids: [ID!]!): Boolean!
  tagsMerge(input: TagsMergeInput!): Tag
  bulkTagUpdate(input: BulkTagUpdateInput!): [Tag!]

  """
  Moves the given files to the given destination. Returns true if successful.
  Either the destination_folder or destination_folder_id must be provided.
  If both are provided, the destination_folder_id takes precedence.
  Destination folder must be a subfolder of one of the stash library paths.
  If provided, destination_basename must be a valid filename with an extension that
  matches one of the media extensions.
  Creates folder hierarchy if needed.
  """
  moveFiles(input: MoveFilesInput!): Boolean!
  deleteFiles(ids: [ID!]!): Boolean!

  fileSetFingerprints(input: FileSetFingerprintsInput!): Boolean!

  # Saved filters
  saveFilter(input: SaveFilterInput!): SavedFilter!
  destroySavedFilter(input: DestroyFilterInput!): Boolean!
  setDefaultFilter(input: SetDefaultFilterInput!): Boolean!
    @deprecated(reason: "now uses UI config")

  "Change general configuration options"
  configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult!
  configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult!
  configureDLNA(input: ConfigDLNAInput!): ConfigDLNAResult!
  configureScraping(input: ConfigScrapingInput!): ConfigScrapingResult!
  configureDefaults(
    input: ConfigDefaultSettingsInput!
  ): ConfigDefaultSettingsResult!

  "overwrites the entire plugin configuration for the given plugin"
  configurePlugin(plugin_id: ID!, input: Map!): Map!

  """
  overwrites the UI configuration
  if input is provided, then the entire UI configuration is replaced
  if partial is provided, then the partial UI configuration is merged into the existing UI configuration
  """
  configureUI(input: Map, partial: Map): Map!
  """
  sets a single UI key value
  key is a dot separated path to the value
  """
  configureUISetting(key: String!, value: Any): Map!

  "Generate and set (or clear) API key"
  generateAPIKey(input: GenerateAPIKeyInput!): String!

  "Returns a link to download the result"
  exportObjects(input: ExportObjectsInput!): String

  "Performs an incremental import. Returns the job ID"
  importObjects(input: ImportObjectsInput!): ID!

  "Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID"
  metadataImport: ID!
  "Start a full export. Outputs to the metadata directory. Returns the job ID"
  metadataExport: ID!
  "Start a scan. Returns the job ID"
  metadataScan(input: ScanMetadataInput!): ID!
  "Start generating content. Returns the job ID"
  metadataGenerate(input: GenerateMetadataInput!): ID!
  "Start auto-tagging. Returns the job ID"
  metadataAutoTag(input: AutoTagMetadataInput!): ID!
  "Clean metadata. Returns the job ID"
  metadataClean(input: CleanMetadataInput!): ID!
  "Clean generated files. Returns the job ID"
  metadataCleanGenerated(input: CleanGeneratedInput!): ID!
  "Identifies scenes using scrapers. Returns the job ID"
  metadataIdentify(input: IdentifyMetadataInput!): ID!

  "Migrate generated files for the current hash naming"
  migrateHashNaming: ID!
  "Migrates legacy scene screenshot files into the blob storage"
  migrateSceneScreenshots(input: MigrateSceneScreenshotsInput!): ID!
  "Migrates blobs from the old storage system to the current one"
  migrateBlobs(input: MigrateBlobsInput!): ID!

  "Anonymise the database in a separate file. Optionally returns a link to download the database file"
  anonymiseDatabase(input: AnonymiseDatabaseInput!): String

  "Optimises the database. Returns the job ID"
  optimiseDatabase: ID!

  "Reload scrapers"
  reloadScrapers: Boolean!

  """
  Enable/disable plugins - enabledMap is a map of plugin IDs to enabled booleans.
  Plugins not in the map are not affected.
  """
  setPluginsEnabled(enabledMap: BoolMap!): Boolean!

  """
  Run a plugin task.
  If task_name is provided, then the task must exist in the plugin config and the tasks configuration
  will be used to run the plugin.
  If no task_name is provided, then the plugin will be executed with the arguments provided only.
  Returns the job ID
  """
  runPluginTask(
    plugin_id: ID!
    "if provided, then the default args will be applied"
    task_name: String
    "displayed in the task queue"
    description: String
    args: [PluginArgInput!] @deprecated(reason: "Use args_map instead")
    args_map: Map
  ): ID!

  """
  Runs a plugin operation. The operation is run immediately and does not use the job queue.
  Returns a map of the result.
  """
  runPluginOperation(plugin_id: ID!, args: Map): Any

  reloadPlugins: Boolean!

  """
  Installs the given packages.
  If a package is already installed, it will be updated if needed..
  If an error occurs when installing a package, the job will continue to install the remaining packages.
  Returns the job ID
  """
  installPackages(type: PackageType!, packages: [PackageSpecInput!]!): ID!
  """
  Updates the given packages.
  If a package is not installed, it will not be installed.
  If a package does not need to be updated, it will not be updated.
  If no packages are provided, all packages of the given type will be updated.
  If an error occurs when updating a package, the job will continue to update the remaining packages.
  Returns the job ID.
  """
  updatePackages(type: PackageType!, packages: [PackageSpecInput!]): ID!
  """
  Uninstalls the given packages.
  If an error occurs when uninstalling a package, the job will continue to uninstall the remaining packages.
  Returns the job ID
  """
  uninstallPackages(type: PackageType!, packages: [PackageSpecInput!]!): ID!

  stopJob(job_id: ID!): Boolean!
  stopAllJobs: Boolean!

  "Submit fingerprints to stash-box instance"
  submitStashBoxFingerprints(
    input: StashBoxFingerprintSubmissionInput!
  ): Boolean!

  "Submit scene as draft to stash-box instance"
  submitStashBoxSceneDraft(input: StashBoxDraftSubmissionInput!): ID
  "Submit performer as draft to stash-box instance"
  submitStashBoxPerformerDraft(input: StashBoxDraftSubmissionInput!): ID

  "Backup the database. Optionally returns a link to download the database file"
  backupDatabase(input: BackupDatabaseInput!): String

  "DANGEROUS: Execute an arbitrary SQL statement that returns rows."
  querySQL(sql: String!, args: [Any]): SQLQueryResult!

  "DANGEROUS: Execute an arbitrary SQL statement without returning any rows."
  execSQL(sql: String!, args: [Any]): SQLExecResult!

  "Run batch performer tag task. Returns the job ID."
  stashBoxBatchPerformerTag(input: StashBoxBatchTagInput!): String!
  "Run batch studio tag task. Returns the job ID."
  stashBoxBatchStudioTag(input: StashBoxBatchTagInput!): String!

  "Enables DLNA for an optional duration. Has no effect if DLNA is enabled by default"
  enableDLNA(input: EnableDLNAInput!): Boolean!
  "Disables DLNA for an optional duration. Has no effect if DLNA is disabled by default"
  disableDLNA(input: DisableDLNAInput!): Boolean!
  "Enables an IP address for DLNA for an optional duration"
  addTempDLNAIP(input: AddTempDLNAIPInput!): Boolean!
  "Removes an IP address from the temporary DLNA whitelist"
  removeTempDLNAIP(input: RemoveTempDLNAIPInput!): Boolean!
}

type Subscription {
  "Update from the metadata manager"
  jobsSubscribe: JobStatusUpdate!

  loggingSubscribe: [LogEntry!]!

  scanCompleteSubscribe: Boolean!
}

schema {
  query: Query
  mutation: Mutation
  subscription: Subscription
}
