<template>
  <div class="fc-activity">
    <!-- Queues + workers pane -->
    <v-card class="mb-4">
      <CardHeading icon="mdi-pulse" title="Queues + workers">
        <v-spacer />
        <span class="text-caption fc-muted">
          updated {{ formatRelative(store.queues?.fetched_at) }}
        </span>
      </CardHeading>
      <v-card-text>
        <QueuesTable
          :queues="store.queues"
          :workers="store.workers"
          :recent-runs="store.recentRuns"
        />
      </v-card-text>
    </v-card>

    <!-- The non-Celery halves of the app (2026-07-02): the GPU agent does the
         majority of processing and downloads feed the library — Activity is
         the whole-app pulse, not just the worker queues. -->
    <GpuActivityPanel @open-maintenance="$emit('open-maintenance')" />
    <DownloadsActivityPanel />

    <!-- Recent failures pane -->
    <v-card class="mb-4">
      <CardHeading icon="mdi-alert-circle-outline" title="Recent failures (last 24h)">
        <v-spacer />
        <v-text-field
          v-model="failureSearch"
          placeholder="Search task, queue, target, error…"
          density="compact" hide-details clearable
          prepend-inner-icon="mdi-magnify"
          style="max-width: 280px;"
        />
        <span class="text-caption fc-muted ml-3">
          {{ filteredFailures.length }} / {{ failureCount }}
        </span>
      </CardHeading>
      <v-card-text>
        <div v-if="errorTypes.length" class="mb-3 fc-pills">
          <v-chip
            v-for="t in errorTypes" :key="t.name"
            :color="t.name === filterErrorType ? 'accent' : undefined"
            size="small" variant="tonal" closable
            @click="toggleErrorTypeFilter(t.name)"
            @click:close="toggleErrorTypeFilter(t.name)"
          >
            {{ t.name }} × {{ t.count }}
          </v-chip>
        </div>
        <v-table density="compact">
          <thead>
            <tr>
              <th>Time</th>
              <th>Queue</th>
              <th>Task</th>
              <th>Target</th>
              <th>Error</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="r in filteredFailures" :key="r.id">
              <td class="fc-tabular" :title="r.finished_at">
                {{ formatRelative(r.finished_at) }}
              </td>
              <td>{{ r.queue }}</td>
              <td><code>{{ shortTaskName(r.task_name) }}</code></td>
              <td class="fc-tabular">{{ r.target_id ?? '—' }}</td>
              <td>
                <button
                  type="button" class="fc-err-link"
                  @click="openError(r)"
                  :title="'Click for full error'"
                >{{ r.error_type }}</button>
              </td>
            </tr>
            <tr v-if="!filteredFailures.length">
              <td colspan="5" class="text-center fc-muted py-4">
                No failures in the last 24h.
              </td>
            </tr>
          </tbody>
        </v-table>
      </v-card-text>
    </v-card>

    <!-- All activity pane -->
    <v-card>
      <CardHeading icon="mdi-format-list-bulleted" title="All recent activity">
        <v-spacer />
        <v-text-field
          v-model="filterTask"
          placeholder="Search task…"
          density="compact" hide-details clearable
          prepend-inner-icon="mdi-magnify"
          style="max-width: 220px;"
          @update:model-value="onTaskSearch"
        />
        <v-select
          v-model="filterQueue"
          :items="queueOptions" density="compact" hide-details
          style="max-width: 180px;"
          @update:model-value="onFilterChange"
        />
        <v-select
          v-model="filterStatus"
          :items="statusOptions" density="compact" hide-details
          style="max-width: 180px;"
          @update:model-value="onFilterChange"
        />
        <v-btn
          variant="text" size="small" rounded="pill"
          @click="onRefresh"
        >
          <v-icon start size="small">mdi-refresh</v-icon>
          Refresh
        </v-btn>
      </CardHeading>
      <v-card-text>
        <v-table density="compact">
          <thead>
            <tr>
              <th></th>
              <th>Time</th>
              <th>Queue</th>
              <th>Task</th>
              <th>Target</th>
              <th class="text-right">Duration</th>
              <th>Status</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="r in store.runs" :key="r.id">
              <td><v-icon size="small" :color="statusColor(r.status)">{{ statusIcon(r.status) }}</v-icon></td>
              <td class="fc-tabular" :title="r.started_at">{{ formatRelative(r.started_at) }}</td>
              <td>{{ r.queue }}</td>
              <td><code>{{ shortTaskName(r.task_name) }}</code></td>
              <td class="fc-tabular">{{ r.target_id ?? '—' }}</td>
              <td class="text-right fc-tabular">{{ formatDuration(r.duration_ms) }}</td>
              <td>{{ r.status }}</td>
            </tr>
            <tr v-if="!store.runs.length && !store.loading.runs">
              <td colspan="7" class="text-center fc-muted py-4">
                No activity yet.
              </td>
            </tr>
          </tbody>
        </v-table>
        <div v-if="store.runsHasMore" class="d-flex justify-center py-3">
          <v-btn
            variant="text" size="small"
            :loading="store.loading.runs"
            @click="onLoadMore"
          >Load more</v-btn>
        </div>
      </v-card-text>
    </v-card>

    <ErrorDetailModal
      v-model="showErrorModal"
      :row="errorModalRow"
    />
  </div>
</template>

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

import { useSystemActivityStore } from '../../stores/systemActivity.js'
import { formatRelative as fmtRelative } from '../../utils/date.js'
import ErrorDetailModal from '../common/ErrorDetailModal.vue'
import QueuesTable from './QueuesTable.vue'
import CardHeading from '../common/CardHeading.vue'
import GpuActivityPanel from './GpuActivityPanel.vue'
import DownloadsActivityPanel from './DownloadsActivityPanel.vue'

defineEmits(['open-maintenance'])

// Click-to-open modal for full error text. Replaces the unusable
// :title="..." tooltip (operator-flagged 2026-05-26: SQLAlchemy
// rollback + traceback content rendered as a cramped browser tooltip
// you couldn't copy from or scroll within).
const showErrorModal = ref(false)
const errorModalRow = ref(null)

function openError(row) {
  errorModalRow.value = row
  showErrorModal.value = true
}

const store = useSystemActivityStore()

const filterQueue = ref(null)
const filterStatus = ref(null)
const filterErrorType = ref(null)
const filterTask = ref(null)        // server-side task-name search (All activity)
const failureSearch = ref('')       // client-side search over loaded failures

const queueOptions = [
  { title: 'All queues', value: null },
  { title: 'import', value: 'import' },
  { title: 'thumbnail', value: 'thumbnail' },
  { title: 'ml', value: 'ml' },
  { title: 'download', value: 'download' },
  { title: 'scan', value: 'scan' },
  { title: 'maintenance', value: 'maintenance' },
  { title: 'default', value: 'default' },
]
const statusOptions = [
  { title: 'All statuses', value: null },
  { title: 'Running', value: 'running' },
  { title: 'OK', value: 'ok' },
  { title: 'Error', value: 'error' },
  { title: 'Timeout', value: 'timeout' },
  { title: 'Retry', value: 'retry' },
]

let queuesPollId = null
let failuresPollId = null

function pollQueues() {
  if (document.hidden) return
  store.loadQueues()
  store.loadWorkers()
  store.loadRecentRuns()
}
function pollFailures() {
  if (document.hidden) return
  store.loadFailures()
}

onMounted(() => {
  pollQueues()
  pollFailures()
  store.setFilter({ queue: null, status: null })
  store.loadRuns({ reset: true })
  queuesPollId = setInterval(pollQueues, 3000)
  failuresPollId = setInterval(pollFailures, 15000)
})
onUnmounted(() => {
  if (queuesPollId) clearInterval(queuesPollId)
  if (failuresPollId) clearInterval(failuresPollId)
})

const errorTypes = computed(() => {
  const counts = store.failures?.count_by_type || {}
  return Object.entries(counts).map(([name, count]) => ({ name, count }))
})

const failureCount = computed(() => (store.failures?.recent || []).length)

const filteredFailures = computed(() => {
  let all = store.failures?.recent || []
  if (filterErrorType.value) {
    all = all.filter(r => r.error_type === filterErrorType.value)
  }
  const q = (failureSearch.value || '').trim().toLowerCase()
  if (q) {
    all = all.filter(r =>
      [r.task_name, r.queue, r.target_id, r.error_type]
        .some(v => String(v ?? '').toLowerCase().includes(q)),
    )
  }
  return all
})

function toggleErrorTypeFilter(name) {
  filterErrorType.value = filterErrorType.value === name ? null : name
  if (filterErrorType.value) {
    // Mirror into the All-activity table too.
    filterStatus.value = 'error'
    onFilterChange()
  }
}

function onFilterChange() {
  store.setFilter({
    queue: filterQueue.value,
    status: filterStatus.value,
    task: (filterTask.value || '').trim() || null,
  })
  store.loadRuns({ reset: true })
}

// Server-side search hits the full task_run history, not just the loaded
// page — debounce so we don't fire a query per keystroke (matches the
// 300ms idiom in TagsView/AllowlistTable).
let taskSearchDebounce = null
function onTaskSearch() {
  if (taskSearchDebounce) clearTimeout(taskSearchDebounce)
  taskSearchDebounce = setTimeout(onFilterChange, 300)
}

function onLoadMore() { store.loadRuns({ reset: false }) }
function onRefresh() { store.loadRuns({ reset: true }) }

function statusIcon(status) {
  return {
    ok: 'mdi-check-circle',
    error: 'mdi-close-circle',
    timeout: 'mdi-clock-alert',
    retry: 'mdi-refresh',
    running: 'mdi-timer-sand',
  }[status] || 'mdi-help-circle'
}
function statusColor(status) {
  return {
    ok: 'success', error: 'error', timeout: 'warning',
    retry: 'info', running: 'accent',
  }[status] || 'on-surface-variant'
}
function shortTaskName(name) {
  if (!name) return ''
  const parts = name.split('.')
  return parts.slice(-1)[0]
}
function formatDuration(ms) {
  if (ms == null) return '—'
  if (ms < 1000) return `${ms} ms`
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`
  return `${(ms / 60_000).toFixed(1)} min`
}
function formatRelative(iso) {
  return fmtRelative(iso, { nullText: '—' })
}
</script>

<style scoped>
.fc-activity { padding: 0; }
.fc-pills {
  display: flex; gap: 6px; flex-wrap: wrap;
}
.fc-tabular {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum';
}
.fc-err { color: rgb(var(--v-theme-error, 220 80 80)); }
.fc-err-link {
  /* Styled as a text-only button so the error_type cell stays
     visually identical to the prior tooltip-bearing row, but is
     now a real clickable target with hover affordance. */
  color: rgb(var(--v-theme-error, 220 80 80));
  background: transparent;
  border: 0;
  padding: 0;
  font: inherit;
  text-decoration: underline dotted;
  cursor: pointer;
}
.fc-err-link:hover { text-decoration: underline; }
</style>
