<template>
  <v-dialog :model-value="modelValue" max-width="900"
            @update:model-value="$emit('update:modelValue', $event)">
    <v-card>
      <v-card-title class="d-flex align-center" style="gap: 12px;">
        <v-icon icon="mdi-alert-circle-outline" color="error" />
        <span>{{ displayTitle }}</span>
        <v-spacer />
        <v-btn icon variant="text" size="small" @click="close">
          <v-icon>mdi-close</v-icon>
        </v-btn>
      </v-card-title>
      <v-card-text>
        <dl v-if="contextRows.length" class="fc-err-context">
          <template v-for="(row, idx) in contextRows" :key="idx">
            <dt>{{ row[0] }}</dt>
            <dd>{{ row[1] }}</dd>
          </template>
        </dl>
        <pre class="fc-err-pre">{{ displayMessage || '(no error message)' }}</pre>
      </v-card-text>
      <v-card-actions>
        <v-btn
          variant="text" rounded="pill" size="small"
          :prepend-icon="copied ? 'mdi-check' : 'mdi-content-copy'"
          @click="onCopy"
        >{{ copied ? 'Copied' : 'Copy' }}</v-btn>
        <v-spacer />
        <v-btn variant="text" rounded="pill" @click="close">Close</v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<script setup>
import { toast } from '../../utils/toast.js'
import { computed, ref, watch } from 'vue'

import { copyText } from '../../utils/clipboard.js'

const props = defineProps({
  modelValue: { type: Boolean, default: false },
  // Legacy mode: pass title + message strings. Used by callers whose row
  // shape lacks structured context (e.g. ImportTaskList where `error` is
  // a plain string on the import_task row, not a TaskRun).
  title: { type: String, default: 'Error details' },
  message: { type: String, default: '' },
  // Row mode: pass the full TaskRun-shaped dict from /api/system_activity.
  // When set, displayTitle/displayMessage derive from the row and a context
  // panel of task_name/queue/target/duration/etc. renders above the error.
  row: { type: Object, default: null },
})

const emit = defineEmits(['update:modelValue'])

const copied = ref(false)
let copiedTimer = null

const displayTitle = computed(() => {
  if (props.row) return props.row.error_type || 'Error details'
  return props.title
})

const displayMessage = computed(() => {
  if (props.row) return props.row.error_message || ''
  return props.message
})

function _shortTaskName (name) {
  if (!name) return ''
  const parts = String(name).split('.')
  return parts[parts.length - 1]
}

function _formatDuration (ms) {
  if (ms == null) return null
  if (ms < 1000) return `${ms} ms`
  if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`
  return `${(ms / 60_000).toFixed(1)} min`
}

const contextRows = computed(() => {
  const r = props.row
  if (!r) return []
  const rows = []
  if (r.task_name) rows.push(['Task', _shortTaskName(r.task_name)])
  if (r.queue) rows.push(['Queue', r.queue])
  if (r.target_id != null) rows.push(['Target', r.target_id])
  const dur = _formatDuration(r.duration_ms)
  if (dur != null) rows.push(['Duration', dur])
  if (r.started_at) rows.push(['Started', r.started_at])
  if (r.finished_at) rows.push(['Finished', r.finished_at])
  if (r.retry_count) rows.push(['Retries', r.retry_count])
  if (r.worker_hostname) rows.push(['Worker', r.worker_hostname])
  if (r.celery_task_id) rows.push(['Celery ID', r.celery_task_id])
  if (r.args_summary) rows.push(['Args', r.args_summary])
  return rows
})

watch(() => props.modelValue, (open) => {
  if (!open) {
    copied.value = false
    if (copiedTimer) { clearTimeout(copiedTimer); copiedTimer = null }
  }
})

function close () {
  emit('update:modelValue', false)
}

async function onCopy () {
  let text = displayMessage.value || ''
  if (contextRows.value.length) {
    const header = contextRows.value.map(([k, v]) => `${k}: ${v}`).join('\n')
    text = `${header}\n\nError: ${displayTitle.value}\n${text}`
  }
  try {
    await copyText(text)
    copied.value = true
    if (copiedTimer) clearTimeout(copiedTimer)
    copiedTimer = setTimeout(() => { copied.value = false }, 1500)
  } catch (e) {
    toast({ text: `Copy failed: ${e.message}`, type: 'error' })
  }
}
</script>

<style scoped>
/* Context panel: muted labels in vellum, crisp values in parchment. The
   2-column key/value grid keeps rows visually scannable when there are
   many fields (Task, Queue, Target, Duration, Started, Finished, Retries,
   Worker, Celery ID, Args). */
.fc-err-context {
  display: grid;
  grid-template-columns: max-content 1fr;
  column-gap: 14px;
  row-gap: 4px;
  margin: 0 0 14px 0;
  font-size: 13px;
}
.fc-err-context dt {
  color: rgb(var(--v-theme-on-surface-variant));
  font-weight: 500;
  white-space: nowrap;
}
.fc-err-context dd {
  color: rgb(var(--v-theme-on-surface));
  margin: 0;
  font-variant-numeric: tabular-nums;
  word-break: break-all;
}

/* Error pre block: high-contrast pairing. The page's `background` token
   (obsidian #14171A) is darker than the modal card's `surface` (iron
   #1E2228), so parchment text reads crisply against it. The prior pairing
   used `surface-variant` which Vuetify auto-derives to a near-parchment
   light value in this theme — pale-on-pale and unreadable. Operator-
   flagged 2026-05-26 ("ui contrast is poor"). */
.fc-err-pre {
  font-family: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 12px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-break: break-word;
  background: rgb(var(--v-theme-background));
  color: rgb(var(--v-theme-on-surface));
  padding: 12px 14px;
  border-radius: 6px;
  max-height: 60vh;
  overflow: auto;
  margin: 0;
}
</style>
