<script setup lang="ts">
import { computed, nextTick, ref } from 'vue'
import { useRoute } from 'vue-router'
import {
  AlertTriangle,
  ArrowLeft,
  Bot,
  Check,
  Clock3,
  FileText,
  LoaderCircle,
  RefreshCw,
  RotateCcw,
  ShieldCheck,
  X,
} from '@lucide/vue'
import {
  canApproveAgentProposal,
  canRejectAgentProposal,
  canRetryAgentProposal,
  getAgentProposalStatusMeta,
  useAgentProposalQuery,
  useApproveAgentProposalMutation,
  useRejectAgentProposalMutation,
  useRetryAgentProposalMutation,
} from '../api/agentProposals'
import { useAuthStore } from '../stores/auth'
import {
  normalizeOperationResult,
  type OperationResultDto,
} from '../api/contracts'
import OperationResultCard from '../components/mission-control/OperationResultCard.vue'
import AsyncStatePanel from '../components/mission-control/AsyncStatePanel.vue'

type ActionMode = 'approve' | 'reject' | 'retry'

const route = useRoute()
const auth = useAuthStore()
const isOwner = computed(() => auth.isOwner)
const proposalId = computed(() => String(route.params.proposalId ?? ''))
const proposalQuery = useAgentProposalQuery(proposalId, isOwner)
const approveMutation = useApproveAgentProposalMutation()
const rejectMutation = useRejectAgentProposalMutation()
const retryMutation = useRetryAgentProposalMutation()

const actionMode = ref<ActionMode | null>(null)
const actionReason = ref('')
const actionRequestId = ref('')
const actionError = ref('')
const operationMessage = ref('')
const operationResult = ref<OperationResultDto | null>(null)
const actionDialog = ref<HTMLElement | null>(null)
const actionDialogTitle = ref<HTMLElement | null>(null)
let actionTrigger: HTMLElement | null = null

const proposal = computed(() => proposalQuery.data.value ?? null)
const statusMeta = computed(() => getAgentProposalStatusMeta(proposal.value?.status ?? ''))
const actionPending = computed(() =>
  approveMutation.isPending.value || rejectMutation.isPending.value || retryMutation.isPending.value,
)
const actionHeading = computed(() => {
  if (actionMode.value === 'approve') return 'Agent-Provisionierung freigeben'
  if (actionMode.value === 'reject') return 'Vorschlag ablehnen'
  return 'Provisionierung erneut versuchen'
})
const actionCopy = computed(() => {
  if (actionMode.value === 'approve') {
    return 'Nexus darf danach genau diesen Proposal-Stand an OpenClaw übergeben.'
  }
  if (actionMode.value === 'reject') {
    return 'Der Vorschlag bleibt als Audit-Eintrag erhalten und wird nicht provisioniert.'
  }
  return 'Nexus startet einen neuen kontrollierten Versuch und prüft das OpenClaw-Inventar erneut.'
})

function formatDate(value: string | null | undefined): string {
  if (!value) return '—'
  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) return value
  return new Intl.DateTimeFormat('de-DE', {
    dateStyle: 'medium',
    timeStyle: 'short',
  }).format(parsed)
}

function openAction(mode: ActionMode) {
  actionTrigger = document.activeElement instanceof HTMLElement ? document.activeElement : null
  actionMode.value = mode
  actionReason.value = ''
  actionError.value = ''
  actionRequestId.value = crypto.randomUUID()
  void nextTick(() => actionDialogTitle.value?.focus())
}

function closeAction(force = false) {
  if (actionPending.value && !force) return
  actionMode.value = null
  actionReason.value = ''
  actionError.value = ''
  actionRequestId.value = ''
  void nextTick(() => {
    if (actionTrigger?.isConnected) actionTrigger.focus()
    actionTrigger = null
  })
}

function onDialogKeydown(event: KeyboardEvent) {
  if (event.key === 'Escape') {
    event.preventDefault()
    closeAction()
    return
  }
  if (event.key !== 'Tab' || !actionDialog.value) return
  const focusable = [...actionDialog.value.querySelectorAll<HTMLElement>(
    'button:not(:disabled), textarea:not(:disabled), input:not(:disabled), select:not(:disabled), a[href]',
  )]
  if (!focusable.length) return
  const first = focusable[0]
  const last = focusable[focusable.length - 1]
  if (event.shiftKey && document.activeElement === first) {
    event.preventDefault()
    last.focus()
  } else if (!event.shiftKey && document.activeElement === last) {
    event.preventDefault()
    first.focus()
  }
}

async function confirmAction() {
  if (!proposal.value || !actionMode.value) return
  if (actionMode.value === 'reject' && !actionReason.value.trim()) {
    actionError.value = 'Bitte dokumentiere den Grund für die Ablehnung.'
    return
  }
  actionError.value = ''
  operationMessage.value = ''
  operationResult.value = null
  const input = {
    proposalId: proposal.value.id,
    expectedRevision: proposal.value.revision,
    reason: actionReason.value.trim() || null,
    clientRequestId: actionRequestId.value,
  }

  try {
    const operation = actionMode.value === 'approve'
      ? await approveMutation.mutateAsync(input)
      : actionMode.value === 'reject'
        ? await rejectMutation.mutateAsync(input)
        : await retryMutation.mutateAsync(input)
    if (!operation.ok) {
      actionError.value = operation.recovery || operation.message
      return
    }
    operationMessage.value = operation.message
    operationResult.value = normalizeOperationResult(operation.operation)
    closeAction(true)
    await proposalQuery.refetch()
  } catch (error) {
    actionError.value = error instanceof Error ? error.message : 'Aktion konnte nicht abgeschlossen werden.'
  }
}
</script>

<template>
  <div class="proposal-detail-page nexus-page">
    <header class="nexus-page-header">
      <div class="nexus-page-header__identity">
        <RouterLink class="back-link" to="/agents" aria-label="Zurück zu Agents">
          <ArrowLeft :size="17" aria-hidden="true" />
        </RouterLink>
        <div class="nexus-page-header__icon">
          <ShieldCheck :size="21" aria-hidden="true" />
        </div>
        <div>
          <h1>{{ proposal?.requestedName || 'Agent-Vorschlag' }}</h1>
          <p class="page-subtitle">Proposal {{ proposalId }}</p>
        </div>
      </div>
      <button
        type="button"
        class="nexus-button"
        :disabled="proposalQuery.isFetching.value"
        @click="proposalQuery.refetch()"
      >
        <RefreshCw :class="{ spin: proposalQuery.isFetching.value }" :size="15" aria-hidden="true" />
        Aktualisieren
      </button>
    </header>

    <div v-if="!isOwner" class="nexus-state nexus-state--error" role="alert">
      <strong>Owner-Berechtigung erforderlich</strong>
      <span>Agent-Vorschläge und Provisionierungsaktionen sind ausschließlich für Owner sichtbar.</span>
      <RouterLink class="nexus-button" to="/agents">Zur Agentenübersicht</RouterLink>
    </div>
    <AsyncStatePanel
      v-else-if="proposalQuery.isPending.value"
      state="loading"
      title="Agent-Vorschlag wird geladen"
    />
    <AsyncStatePanel
      v-else-if="proposalQuery.isError.value"
      state="error"
      title="Agent-Vorschlag nicht verfügbar"
      :problem="proposalQuery.error.value"
      @action="proposalQuery.refetch()"
    />

    <template v-else-if="proposal">
      <section class="status-hero nexus-panel" :class="`status-hero--${statusMeta.tone}`">
        <span class="status-icon">
          <Check v-if="proposal.status === 'ready'" :size="22" aria-hidden="true" />
          <Clock3 v-else-if="proposal.status === 'awaiting_approval' || proposal.status === 'provisioning'" :size="22" aria-hidden="true" />
          <AlertTriangle v-else-if="proposal.status === 'failed' || proposal.status === 'partial' || proposal.status === 'in_doubt'" :size="22" aria-hidden="true" />
          <Bot v-else :size="22" aria-hidden="true" />
        </span>
        <div>
          <span class="status-eyebrow">{{ statusMeta.label }}</span>
          <h2>{{ proposal.requestedName }}</h2>
          <p>{{ statusMeta.description }}</p>
        </div>
        <span class="revision nexus-mono">rev {{ proposal.revision }}</span>
      </section>

      <div v-if="operationMessage" class="nexus-state operation-success" role="status">
        <Check :size="17" aria-hidden="true" />
        {{ operationMessage }}
      </div>
      <OperationResultCard
        v-if="operationResult"
        :result="operationResult"
        title="Agent-Proposal"
      />

      <div class="detail-layout">
        <main class="detail-main nexus-stack">
          <section class="nexus-panel detail-section">
            <div class="section-heading">
              <div>
                <span class="section-eyebrow">Konfiguration</span>
                <h2>Vorgeschlagener Agent</h2>
              </div>
              <RouterLink
                v-if="proposal.openClawAgentId"
                class="nexus-button"
                :to="`/agents/${encodeURIComponent(proposal.openClawAgentId)}`"
              >
                Agent öffnen
              </RouterLink>
            </div>
            <dl class="fact-grid">
              <div><dt>Name</dt><dd>{{ proposal.requestedName }}</dd></div>
              <div><dt>Agent-ID</dt><dd class="nexus-mono">{{ proposal.requestedAgentId }}</dd></div>
              <div><dt>Rolle</dt><dd>{{ proposal.role || 'Nicht angegeben' }}</dd></div>
              <div><dt>Modell</dt><dd class="nexus-mono">{{ proposal.model || 'OpenClaw-Standard' }}</dd></div>
              <div class="fact-wide"><dt>Zweck</dt><dd>{{ proposal.description || 'Kein zusätzlicher Zweck angegeben.' }}</dd></div>
              <div class="fact-wide"><dt>Verwalteter Workspace</dt><dd class="nexus-mono">{{ proposal.workspace }}</dd></div>
            </dl>
          </section>

          <section class="nexus-panel detail-section">
            <div class="section-heading">
              <div>
                <span class="section-eyebrow">OpenClaw-Dateien</span>
                <h2>{{ proposal.files.length }} Initialdateien</h2>
              </div>
              <FileText :size="19" aria-hidden="true" />
            </div>
            <div v-if="proposal.files.length" class="file-list">
              <details v-for="file in proposal.files" :key="file.name" class="file-item">
                <summary>
                  <span>{{ file.name }}</span>
                  <span class="nexus-mono">{{ file.size }} B · {{ file.contentHash.slice(0, 12) }}</span>
                </summary>
                <pre v-if="file.content">{{ file.content }}</pre>
                <p v-else>Der Dateiinhalt ist in dieser Ansicht nicht freigegeben.</p>
              </details>
            </div>
            <div v-else class="inline-empty">
              OpenClaw verwendet seine Standarddateien; der Proposal enthält keine eigenen Inhalte.
            </div>
          </section>

          <section v-if="proposal.error" class="nexus-state nexus-state--error" role="alert">
            <strong>{{ proposal.error.code }}</strong>
            <span>{{ proposal.error.message }}</span>
            <span v-if="proposal.error.recovery">{{ proposal.error.recovery }}</span>
          </section>

          <section v-if="proposal.rejectionReason" class="nexus-panel rejection-panel">
            <span class="section-eyebrow">Ablehnungsgrund</span>
            <p>{{ proposal.rejectionReason }}</p>
          </section>
        </main>

        <aside class="detail-sidebar nexus-stack">
          <section class="nexus-panel">
            <span class="section-eyebrow">Audit</span>
            <dl class="audit-list">
              <div><dt>Quelle</dt><dd>{{ proposal.source }}</dd></div>
              <div><dt>Angefragt von</dt><dd>{{ proposal.requestedBy }}</dd></div>
              <div><dt>Erstellt</dt><dd>{{ formatDate(proposal.createdAt) }}</dd></div>
              <div><dt>Aktualisiert</dt><dd>{{ formatDate(proposal.updatedAt) }}</dd></div>
              <div v-if="proposal.approvedBy"><dt>Freigegeben von</dt><dd>{{ proposal.approvedBy }}</dd></div>
              <div v-if="proposal.rejectedBy"><dt>Abgelehnt von</dt><dd>{{ proposal.rejectedBy }}</dd></div>
              <div v-if="proposal.completedAt"><dt>Abgeschlossen</dt><dd>{{ formatDate(proposal.completedAt) }}</dd></div>
            </dl>
          </section>

          <section
            v-if="canApproveAgentProposal(proposal) || canRejectAgentProposal(proposal) || canRetryAgentProposal(proposal)"
            class="nexus-panel action-panel"
          >
            <span class="section-eyebrow">Nächste Aktion</span>
            <p>Jede Mutation wird mit Proposal-Revision, Idempotenz- und Korrelationsdaten gesendet.</p>
            <button
              v-if="canApproveAgentProposal(proposal)"
              type="button"
              class="nexus-button nexus-button--primary"
              @click="openAction('approve')"
            >
              <Check :size="16" aria-hidden="true" />
              Freigeben
            </button>
            <button
              v-if="canRejectAgentProposal(proposal)"
              type="button"
              class="nexus-button nexus-button--danger"
              @click="openAction('reject')"
            >
              <X :size="16" aria-hidden="true" />
              Ablehnen
            </button>
            <button
              v-if="canRetryAgentProposal(proposal)"
              type="button"
              class="nexus-button"
              @click="openAction('retry')"
            >
              <RotateCcw :size="16" aria-hidden="true" />
              {{ proposal.status === 'in_doubt' ? 'Read-only abgleichen' : 'Erneut versuchen' }}
            </button>
          </section>
        </aside>
      </div>

      <div
        v-if="actionMode"
        class="action-overlay"
        role="presentation"
        @mousedown.self="closeAction()"
        @keydown="onDialogKeydown"
      >
        <section
          ref="actionDialog"
          class="action-dialog nexus-panel"
          role="dialog"
          aria-modal="true"
          aria-labelledby="proposal-action-title"
        >
          <div class="section-heading">
            <div>
              <span class="section-eyebrow">Bestätigung</span>
              <h2 id="proposal-action-title" ref="actionDialogTitle" tabindex="-1">{{ actionHeading }}</h2>
            </div>
            <button
              type="button"
              class="dialog-close"
              aria-label="Dialog schließen"
              :disabled="actionPending"
              @click="() => closeAction()"
            >
              <X :size="18" aria-hidden="true" />
            </button>
          </div>
          <p>{{ actionCopy }}</p>
          <label class="action-reason">
            <span>{{ actionMode === 'reject' ? 'Begründung (Pflichtfeld)' : 'Notiz (optional)' }}</span>
            <textarea
              v-model="actionReason"
              class="nexus-field"
              rows="4"
              maxlength="1000"
              :required="actionMode === 'reject'"
            ></textarea>
          </label>
          <div v-if="actionError" class="nexus-state nexus-state--error" role="alert">{{ actionError }}</div>
          <div class="dialog-actions">
            <button type="button" class="nexus-button" :disabled="actionPending" @click="() => closeAction()">
              Abbrechen
            </button>
            <button
              type="button"
              class="nexus-button"
              :class="actionMode === 'reject' ? 'nexus-button--danger' : 'nexus-button--primary'"
              :disabled="actionPending"
              @click="confirmAction"
            >
              <LoaderCircle v-if="actionPending" class="spin" :size="16" aria-hidden="true" />
              <Check v-else-if="actionMode === 'approve'" :size="16" aria-hidden="true" />
              <X v-else-if="actionMode === 'reject'" :size="16" aria-hidden="true" />
              <RotateCcw v-else :size="16" aria-hidden="true" />
              Bestätigen
            </button>
          </div>
        </section>
      </div>
    </template>
  </div>
</template>

<style scoped>
.proposal-detail-page { padding-bottom: 40px; }
.back-link { width: 42px; height: 42px; padding: 0; flex: 0 0 42px; text-decoration: none; }
.status-hero { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; gap: 16px; }
.status-hero--success { border-color: var(--status-work-line); }
.status-hero--info { border-color: var(--status-think-line); }
.status-hero--warning { border-color: var(--status-queue-line); }
.status-hero--danger { border-color: var(--status-block-line); }
.status-icon { width: 48px; height: 48px; display: grid; place-items: center; border: 1px solid var(--line-2); border-radius: var(--r-sm); background: var(--grad-soft); color: var(--a-mid); }
.status-hero--success .status-icon { color: var(--st-work); background: var(--status-work-bg); border-color: var(--status-work-line); }
.status-hero--warning .status-icon { color: var(--st-queue); background: var(--status-queue-bg); border-color: var(--status-queue-line); }
.status-hero--danger .status-icon { color: var(--st-block); background: var(--status-block-bg); border-color: var(--status-block-line); }
.status-eyebrow, .section-eyebrow { display: block; margin-bottom: 5px; color: var(--a-mid); font-family: var(--font-mono-v2); font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; }
.status-hero h2, .section-heading h2 { margin: 0; font-size: 17px; }
.status-hero p { margin: 4px 0 0; color: var(--tx-2); line-height: 1.5; }
.revision { color: var(--tx-3); }
.operation-success { grid-template-columns: auto 1fr; align-items: center; border-color: var(--status-work-line); background: var(--status-work-bg); color: var(--st-work); }
.detail-layout { display: grid; grid-template-columns: minmax(0, 1fr) 310px; gap: 16px; align-items: start; }
.detail-section { display: grid; gap: 16px; }
.section-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-bottom: 14px; border-bottom: 1px solid var(--line); }
.section-heading a { text-decoration: none; }
.fact-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 18px; margin: 0; }
.fact-grid > div, .audit-list > div { display: grid; gap: 5px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.fact-grid > div:nth-last-child(-n + 2) { border-bottom: 0; }
.fact-grid .fact-wide { grid-column: 1 / -1; }
.fact-grid dt, .audit-list dt { color: var(--tx-3); font-size: 11px; }
.fact-grid dd, .audit-list dd { margin: 0; color: var(--tx); line-height: 1.55; overflow-wrap: anywhere; }
.file-list { display: grid; gap: 9px; }
.file-item { border: 1px solid var(--line); border-radius: var(--r-sm); background: color-mix(in srgb, var(--space-2) 60%, transparent); overflow: hidden; }
.file-item summary { display: flex; justify-content: space-between; gap: 12px; padding: 12px 14px; color: var(--tx); cursor: pointer; }
.file-item summary span:last-child { color: var(--tx-3); }
.file-item pre { max-height: 360px; margin: 0; padding: 14px; overflow: auto; border-top: 1px solid var(--line); color: var(--tx-2); font: 12px/1.6 var(--font-mono-v2); white-space: pre-wrap; overflow-wrap: anywhere; }
.file-item p, .inline-empty { margin: 0; padding: 14px; color: var(--tx-2); line-height: 1.5; }
.rejection-panel p { margin: 0; color: var(--tx-2); line-height: 1.6; }
.audit-list { margin: 0; }
.audit-list > div:last-child { border-bottom: 0; }
.action-panel { display: grid; gap: 10px; }
.action-panel p { margin: 0 0 4px; color: var(--tx-2); line-height: 1.55; }
.action-panel .nexus-button { width: 100%; }
.action-overlay { position: fixed; inset: 0; z-index: 120; display: grid; place-items: center; padding: 20px; background: color-mix(in srgb, var(--space-0) 78%, transparent); backdrop-filter: blur(12px); }
.action-dialog { width: min(100%, 540px); display: grid; gap: 16px; box-shadow: 0 28px 90px rgba(3, 2, 16, .52); }
.action-dialog > p { margin: 0; color: var(--tx-2); line-height: 1.6; }
.dialog-close { width: 38px; height: 38px; display: grid; place-items: center; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--accent-wash); color: var(--tx-2); cursor: pointer; }
.action-reason { display: grid; gap: 7px; color: var(--tx-2); font-weight: 650; }
.action-reason textarea { width: 100%; padding: 10px 12px; resize: vertical; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 10px; }
.spin { animation: spin .8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (max-width: 900px) {
  .detail-layout { grid-template-columns: minmax(0, 1fr); }
}
@media (max-width: 620px) {
  .status-hero { grid-template-columns: auto minmax(0, 1fr); }
  .revision { grid-column: 2; }
  .fact-grid { grid-template-columns: minmax(0, 1fr); }
  .fact-grid .fact-wide { grid-column: auto; }
  .file-item summary { display: grid; }
}
</style>
