<template>
  <!-- #3068: attachment reclamation. PostAttachment's FKs are both SET NULL, so
       a deleted post or artist leaves the row behind; and the store is
       sha-addressed, so one blob backs many rows and deleting a row never freed
       its file. Nothing swept either. Preview first, then apply (destructive:
       unlinks files). -->
  <MaintenanceTile
    icon="mdi-paperclip-off"
    title="Reclaim orphaned attachments"
    blurb="Remove attachment records belonging to nothing, and the files nothing references."
    destructive
    :open="applying || previewing"
  >
    <p class="text-body-2 mb-3">
      Attachment records survive the post and artist they belonged to, and the
      files behind them are shared between records — so a deleted record never
      freed its file on its own. This finds records attributed to
      <strong>neither</strong> a post nor an artist, and files in the attachment
      store that <strong>no remaining record</strong> points at.
      <strong>Preview</strong> first; <strong>Apply</strong> deletes those
      records and unlinks those files. Files written in the last few hours are
      always left alone, so an in-progress download is never caught mid-write.
    </p>

    <div class="d-flex align-center flex-wrap" style="gap: 12px;">
      <v-btn
        color="primary" variant="tonal" rounded="pill"
        :loading="previewing" :disabled="applying" @click="preview"
      >
        <v-icon start>mdi-magnify</v-icon> Preview
      </v-btn>
      <v-btn
        color="error" rounded="pill"
        :loading="applying"
        :disabled="previewing || !canApply"
        @click="confirmOpen = true"
      >
        <v-icon start>mdi-paperclip-off</v-icon> Apply
      </v-btn>
    </div>

    <v-alert
      v-if="summary" :type="summaryType" variant="tonal" class="mt-4"
      density="comfortable"
    >
      <span v-if="applied">
        Deleted {{ summary.rows }} orphaned record(s) and unlinked
        {{ summary.files }} file(s), reclaiming {{ humanBytes(summary.bytes) }}.
      </span>
      <span v-else-if="hasWork">
        {{ summary.rows }} orphaned record(s) and {{ summary.files }}
        unreferenced file(s) — {{ humanBytes(summary.bytes) }} reclaimable.
        Click <strong>Apply</strong> to remove them.
      </span>
      <span v-else>Nothing to reclaim — every attachment is accounted for.</span>

      <!-- Both of these change what the numbers MEAN, so they are stated
           whenever they are non-zero rather than hidden in a tooltip. -->
      <div v-if="summary.files_failed" class="mt-1 text-caption">
        {{ summary.files_failed }} file(s) could not be read or removed — see
        the worker log.
      </div>
      <div v-if="summary.partial" class="mt-1 text-caption">
        Stopped early at the time limit; some of the store was not examined.
        Run it again to continue.
      </div>
    </v-alert>

    <QueueStatusBar queue="maintenance_long" queue-label="Maintenance" />

    <v-dialog v-model="confirmOpen" max-width="440">
      <v-card>
        <v-card-title>Reclaim orphaned attachments?</v-card-title>
        <v-card-text class="text-body-2">
          This permanently deletes
          <strong>{{ summary?.rows ?? 0 }}</strong> attachment record(s) and
          unlinks <strong>{{ summary?.files ?? 0 }}</strong> file(s)
          ({{ humanBytes(summary?.bytes) }}). Only files that no remaining
          record points at are removed, so nothing still attached to a post
          is affected.
        </v-card-text>
        <v-card-actions>
          <v-spacer />
          <v-btn variant="text" @click="confirmOpen = false">Cancel</v-btn>
          <v-btn color="error" @click="apply">Reclaim</v-btn>
        </v-card-actions>
      </v-card>
    </v-dialog>
  </MaintenanceTile>
</template>

<script setup>
import { computed, ref } from 'vue'

import { useMaintenanceTask } from '../../composables/useMaintenanceTask.js'
import { humanBytes } from '../../utils/bytes.js'
import MaintenanceTile from '../common/MaintenanceTile.vue'
import QueueStatusBar from './QueueStatusBar.vue'

const confirmOpen = ref(false)

// Walks the whole attachment store, so it can run for minutes on a large
// library — the service caps itself at 900s and reports `partial`. 150 polls
// × 2s ≈ 5m of foreground waiting; past that the composable hands off to the
// task dashboard rather than spinning forever.
const { previewing, applying, summary, applied, preview, apply: applyTask } = useMaintenanceTask({
  endpoint: '/api/admin/maintenance/reclaim-attachments',
  storageKey: 'fc.maint.reclaimAttachments',
  appliedToast: 'Orphaned attachments reclaimed',
  maxPolls: 150,
})

const hasWork = computed(
  () => !!summary.value && (summary.value.rows > 0 || summary.value.files > 0),
)
const canApply = computed(() => hasWork.value && !applied.value)
const summaryType = computed(() => {
  if (applied.value) return 'success'
  return hasWork.value ? 'info' : 'success'
})

// The confirm dialog gates the destructive apply; close it, then run.
function apply () {
  confirmOpen.value = false
  applyTask()
}
</script>
