<template>
  <v-card class="mb-4">
    <CardHeading icon="mdi-expansion-card" title="GPU agent pipeline">
      <v-spacer />
      <v-btn
        variant="text" size="small" rounded="pill"
        @click="$emit('open-maintenance')"
      >
        Open maintenance
        <v-icon end size="small">mdi-arrow-right</v-icon>
      </v-btn>
    </CardHeading>
    <v-card-text>
      <div class="fc-cells mb-2">
        <div class="fc-cell">
          <div class="fc-cell__n">{{ q.pending }}</div>
          <div class="fc-cell__l">pending</div>
        </div>
        <div class="fc-cell">
          <div class="fc-cell__n">{{ q.leased }}</div>
          <div class="fc-cell__l">in flight</div>
        </div>
        <div class="fc-cell">
          <div class="fc-cell__n fc-good">{{ q.done }}</div>
          <div class="fc-cell__l">done</div>
        </div>
        <div class="fc-cell">
          <div class="fc-cell__n" :class="q.error ? 'fc-weak' : ''">{{ q.error }}</div>
          <div class="fc-cell__l">errored</div>
        </div>
      </div>
      <p v-if="!q.error" class="fc-muted text-body-2 mb-0">
        No failed jobs — the pipeline is clean. Work drains whenever the
        desktop agent is running.
      </p>
      <p v-else class="text-body-2 mb-0">
        Triage: <b>{{ triage.defect }}</b> defective file(s) ·
        {{ triage.file_ok }} file-ok · {{ triage.unclassified }} unprobed
        <span v-if="reasonSummary" class="fc-muted">— {{ reasonSummary }}</span>
        <br>
        <span class="fc-muted text-caption">
          Recover defective files from Maintenance → Failed processing.
        </span>
      </p>
    </v-card-text>
  </v-card>
</template>

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

import CardHeading from '../common/CardHeading.vue'
import { useGpuStore } from '../../stores/gpu.js'

defineEmits(['open-maintenance'])

const store = useGpuStore()
const q = ref({ pending: 0, leased: 0, done: 0, error: 0 })
const triage = ref({ defect: 0, file_ok: 0, unclassified: 0 })
const byClass = ref({})
let pollId = null
let lastErrorCount = -1

const reasonSummary = computed(() =>
  Object.entries(byClass.value)
    .sort((a, b) => b[1] - a[1])
    .slice(0, 3)
    .map(([k, n]) => `${k.replaceAll('_', ' ')} ${n}`)
    .join(' · '))

async function poll() {
  try {
    q.value = await store.status()
    // The triage detail is only worth a second call when the error count
    // actually moved (it's a 500-row join server-side).
    if (q.value.error !== lastErrorCount) {
      lastErrorCount = q.value.error
      if (q.value.error > 0) {
        const body = await store.errors()
        triage.value = body.triage
        byClass.value = body.by_class
      } else {
        triage.value = { defect: 0, file_ok: 0, unclassified: 0 }
        byClass.value = {}
      }
    }
  } catch { /* non-fatal — panel just shows the last snapshot */ }
}

onMounted(() => {
  poll()
  pollId = setInterval(() => { if (!document.hidden) poll() }, 5000)
})
onUnmounted(() => { if (pollId) clearInterval(pollId) })
</script>

<style scoped>
.fc-cells { display: flex; gap: 28px; }
.fc-cell__n {
  font-size: 20px; font-weight: 700; line-height: 1.1;
  font-family: 'JetBrains Mono', monospace;
}
.fc-cell__l {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
  color: rgb(var(--v-theme-on-surface-variant));
}
</style>
