<script setup lang="ts">
import {
  Activity,
  Bot,
  CircleAlert,
  Clock3,
  Filter,
  Loader2,
  RefreshCw,
  Search,
  ServerCog,
  X,
} from '@lucide/vue'
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import { RouterLink, useRoute, useRouter } from 'vue-router'
import type { EntityRefDto } from '../api/contracts'
import { useActivity } from '../api/activity'
import { useOpenClawOverviewQuery } from '../api/openclawRuntime'
import EntityLink from '../components/mission-control/EntityLink.vue'
import AsyncStatePanel from '../components/mission-control/AsyncStatePanel.vue'

interface UnifiedActivity {
  id: string
  source: 'OpenClaw' | 'Nexus'
  type: string
  action: string
  status: string
  message: string
  severity: string | null
  actor: string | null
  agentId: string | null
  sessionKey: string | null
  runId: string | null
  occurredAt: string | null
  entity: EntityRefDto | null
}

const overviewQuery = useOpenClawOverviewQuery()
const nexusActivity = useActivity()
const route = useRoute()
const router = useRouter()
const query = ref('')
const sourceFilter = ref<'all' | 'OpenClaw' | 'Nexus'>('all')
const severityFilter = ref('all')
const selectedEvent = ref<UnifiedActivity | null>(null)
const requestedEventId = computed(() =>
  typeof route.query.event === 'string' ? route.query.event : '',
)

const events = computed<UnifiedActivity[]>(() => {
  const runtimeEvents = (overviewQuery.data.value?.activity.items ?? []).map(event => ({
    id: `openclaw:${event.id}`,
    source: 'OpenClaw' as const,
    type: event.kind || event.eventType,
    action: event.action,
    status: event.status,
    message: event.message,
    severity: event.severity,
    actor: event.actor,
    agentId: event.agentId,
    sessionKey: event.sessionKey,
    runId: event.runId,
    occurredAt: event.occurredAt,
    entity: null,
  }))
  const nexusEvents = (nexusActivity.data.value?.items ?? []).map(event => ({
    id: `nexus:${event.id}:${event.at}`,
    source: 'Nexus' as const,
    type: event.type,
    action: event.type,
    status: 'recorded',
    message: event.message,
    severity: null,
    actor: null,
    agentId: null,
    sessionKey: null,
    runId: null,
    occurredAt: event.at,
    entity: event.entity,
  }))
  return [...runtimeEvents, ...nexusEvents].sort((left, right) =>
    timestamp(right.occurredAt) - timestamp(left.occurredAt),
  )
})

const severities = computed(() =>
  [...new Set(events.value.map(event => event.severity || event.status).filter(Boolean))]
    .sort((left, right) => left.localeCompare(right)),
)

const filteredEvents = computed(() => {
  const needle = query.value.trim().toLocaleLowerCase()
  return events.value.filter(event => {
    const matchesSource = sourceFilter.value === 'all' || event.source === sourceFilter.value
    const tone = event.severity || event.status
    const matchesSeverity = severityFilter.value === 'all' || tone === severityFilter.value
    const matchesQuery = !needle || [
      event.type,
      event.action,
      event.message,
      event.actor,
      event.agentId,
      event.sessionKey,
      event.runId,
    ].some(value => value?.toLocaleLowerCase().includes(needle))
    return matchesSource && matchesSeverity && matchesQuery
  })
})

const runtimeCollection = computed(() => overviewQuery.data.value?.activity ?? null)
const runtimeCount = computed(() => overviewQuery.data.value?.activity.items.length ?? 0)
const nexusCount = computed(() => nexusActivity.data.value?.totalCount ?? 0)
const failedCount = computed(() =>
  events.value.filter(event => ['error', 'failed', 'critical', 'high'].includes(
    (event.severity || event.status).toLocaleLowerCase(),
  )).length,
)

function timestamp(value: string | null) {
  if (!value) return 0
  const parsed = Date.parse(value)
  return Number.isNaN(parsed) ? 0 : parsed
}

function formatTime(value: string | null) {
  if (!value) return 'No timestamp'
  const parsed = new Date(value)
  if (Number.isNaN(parsed.getTime())) return value
  return parsed.toLocaleString('de-DE', {
    day: '2-digit',
    month: 'short',
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
  })
}

function toneFor(event: UnifiedActivity) {
  const value = (event.severity || event.status).toLocaleLowerCase()
  if (['error', 'failed', 'critical', 'high', 'denied'].includes(value)) return 'danger'
  if (['warning', 'warn', 'medium', 'pending', 'blocked'].includes(value)) return 'warning'
  if (['success', 'succeeded', 'completed', 'resolved', 'ready'].includes(value)) return 'success'
  return 'info'
}

function entityRefs(event: UnifiedActivity): EntityRefDto[] {
  const refs: EntityRefDto[] = event.entity ? [event.entity] : []
  if (event.agentId) refs.push({ type: 'agent', id: event.agentId, label: event.agentId })
  if (event.runId) refs.push({ type: 'run', id: event.runId, label: event.runId })
  return [...new Map(refs.map(entity => [`${entity.type}:${entity.id}`, entity])).values()]
}

async function refresh() {
  await Promise.allSettled([
    overviewQuery.refetch(),
    nexusActivity.refetch(),
  ])
}

function closeDetails() {
  selectedEvent.value = null
}

watch(
  [requestedEventId, events],
  async ([requested, items]) => {
    if (!requested) return
    const event = items.find(item =>
      item.id === requested || item.id.startsWith(`nexus:${requested}:`),
    )
    if (!event) return
    selectedEvent.value = event
    await nextTick()
    document.getElementById(`activity-${event.id}`)?.focus({ preventScroll: true })
  },
  { immediate: true },
)

function onKeydown(event: KeyboardEvent) {
  if (event.key === 'Escape') closeDetails()
}

onMounted(() => {
  window.addEventListener('keydown', onKeydown)
})

onUnmounted(() => {
  window.removeEventListener('keydown', onKeydown)
})
</script>

<template>
  <div class="activity-page nexus-page">
    <header class="nexus-page-header">
      <div class="nexus-page-header__identity">
        <div class="nexus-page-header__icon"><Activity :size="20" aria-hidden="true" /></div>
        <div>
          <span class="eyebrow">UNIFIED AUDIT FEED</span>
          <h1>Activity</h1>
          <p class="page-subtitle">OpenClaw runtime events and Nexus domain changes in one inspectable timeline.</p>
        </div>
      </div>
      <button type="button" class="nexus-button" :disabled="overviewQuery.isFetching.value || nexusActivity.isFetching.value" @click="refresh">
        <Loader2 v-if="overviewQuery.isFetching.value || nexusActivity.isFetching.value" :size="15" class="spin" aria-hidden="true" />
        <RefreshCw v-else :size="15" aria-hidden="true" />
        Refresh
      </button>
    </header>

    <section class="activity-metrics" aria-label="Activity summary">
      <article class="nexus-card metric-card">
        <ServerCog :size="17" aria-hidden="true" />
        <div><strong>{{ runtimeCount }}</strong><span>OpenClaw events</span></div>
      </article>
      <article class="nexus-card metric-card">
        <Activity :size="17" aria-hidden="true" />
        <div><strong>{{ nexusCount }}</strong><span>Nexus events</span></div>
      </article>
      <article class="nexus-card metric-card">
        <CircleAlert :size="17" aria-hidden="true" />
        <div><strong>{{ failedCount }}</strong><span>Attention needed</span></div>
      </article>
    </section>

    <AsyncStatePanel
      v-if="runtimeCollection && runtimeCollection.state !== 'ready'"
      :state="['disconnected', 'error'].includes(runtimeCollection.state) ? 'offline' : 'partial'"
      :title="`OpenClaw-Auditfeed: ${runtimeCollection.state}`"
      :message="runtimeCollection.recovery || runtimeCollection.message || 'Runtime-Aktivität ist derzeit nicht vollständig verfügbar.'"
      action-label="Diagnose öffnen"
      compact
      @action="router.push('/settings')"
    />

    <section class="activity-toolbar nexus-panel" aria-label="Activity filters">
      <label class="activity-search">
        <span class="nexus-visually-hidden">Search activity</span>
        <Search :size="15" aria-hidden="true" />
        <input v-model="query" type="search" placeholder="Search message, agent, session, or run" />
      </label>
      <label>
        <Filter :size="14" aria-hidden="true" />
        <span class="nexus-visually-hidden">Filter source</span>
        <select v-model="sourceFilter">
          <option value="all">All sources</option>
          <option value="OpenClaw">OpenClaw</option>
          <option value="Nexus">Nexus</option>
        </select>
      </label>
      <label>
        <span class="nexus-visually-hidden">Filter severity</span>
        <select v-model="severityFilter">
          <option value="all">All states</option>
          <option v-for="severity in severities" :key="severity" :value="severity">{{ severity }}</option>
        </select>
      </label>
    </section>

    <AsyncStatePanel
      v-if="overviewQuery.isPending.value && !overviewQuery.data.value && nexusActivity.isLoading.value"
      state="loading"
      title="Aktivitätsquellen werden geladen"
    />
    <section v-else-if="filteredEvents.length" class="timeline" aria-label="Activity timeline">
      <button
        v-for="event in filteredEvents"
        :id="`activity-${event.id}`"
        :key="event.id"
        type="button"
        class="event-row nexus-card nexus-card--interactive"
        @click="selectedEvent = event"
      >
        <span class="event-dot" :class="toneFor(event)" aria-hidden="true"></span>
        <span class="event-main">
          <span class="event-title">
            <strong>{{ event.action }}</strong>
            <span class="event-source">{{ event.source }}</span>
          </span>
          <span class="event-message">{{ event.message }}</span>
          <span v-if="event.agentId || event.actor" class="event-actor">
            <Bot :size="12" aria-hidden="true" />
            {{ event.agentId || event.actor }}
          </span>
        </span>
        <span class="event-meta">
          <span class="nexus-status" :class="`nexus-status--${toneFor(event)}`">
            {{ event.severity || event.status }}
          </span>
          <time :datetime="event.occurredAt || undefined">{{ formatTime(event.occurredAt) }}</time>
        </span>
      </button>
    </section>
    <AsyncStatePanel
      v-else
      state="empty"
      title="Keine passende Aktivität"
      :message="events.length ? 'Passe Suche oder Quellenfilter an.' : 'Nexus und OpenClaw haben noch keine Ereignisse geliefert.'"
    />

    <Teleport to="body">
      <div v-if="selectedEvent" class="activity-dialog-overlay" @click.self="closeDetails">
        <section
          class="activity-dialog"
          role="dialog"
          aria-modal="true"
          aria-labelledby="activity-dialog-title"
        >
          <header>
            <div>
              <span class="eyebrow">{{ selectedEvent.source }} · {{ selectedEvent.type }}</span>
              <h2 id="activity-dialog-title">{{ selectedEvent.action }}</h2>
            </div>
            <button type="button" aria-label="Close activity details" @click="closeDetails">
              <X :size="18" aria-hidden="true" />
            </button>
          </header>
          <p class="dialog-message">{{ selectedEvent.message }}</p>
          <dl>
            <div><dt>Status</dt><dd>{{ selectedEvent.severity || selectedEvent.status }}</dd></div>
            <div><dt>Time</dt><dd>{{ formatTime(selectedEvent.occurredAt) }}</dd></div>
            <div v-if="selectedEvent.actor"><dt>Actor</dt><dd>{{ selectedEvent.actor }}</dd></div>
            <div v-if="selectedEvent.agentId"><dt>Agent</dt><dd>{{ selectedEvent.agentId }}</dd></div>
            <div v-if="selectedEvent.sessionKey"><dt>Session</dt><dd><code>{{ selectedEvent.sessionKey }}</code></dd></div>
            <div v-if="selectedEvent.runId"><dt>Run</dt><dd><code>{{ selectedEvent.runId }}</code></dd></div>
          </dl>
          <div v-if="entityRefs(selectedEvent).length" class="dialog-refs" aria-label="Related results">
            <EntityLink
              v-for="entity in entityRefs(selectedEvent)"
              :key="`${entity.type}:${entity.id}`"
              :entity="entity"
            />
          </div>
          <div class="dialog-actions">
            <RouterLink
              v-if="selectedEvent.source === 'OpenClaw'"
              class="nexus-button"
              to="/runs"
              @click="closeDetails"
            >Open Run Control</RouterLink>
            <button type="button" class="nexus-button nexus-button--primary" @click="closeDetails">Done</button>
          </div>
        </section>
      </div>
    </Teleport>
  </div>
</template>

<style scoped>
.activity-page { padding-bottom: 30px; }
.activity-metrics { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 12px; }
.metric-card { display: flex; align-items: center; gap: 12px; padding: 15px; color: var(--a-mid); }
.metric-card div { display: grid; gap: 2px; }
.metric-card strong { color: var(--tx); font-family: var(--font-display); font-size: 21px; }
.metric-card span { color: var(--tx-3); }

.runtime-state { grid-template-columns: auto minmax(0, 1fr) auto; align-items: center; }
.runtime-state h2 { font-size: 15px; }

.activity-toolbar { display: flex; align-items: center; gap: 10px; padding: 12px; }
.activity-toolbar > label { display: flex; align-items: center; gap: 7px; color: var(--tx-3); }
.activity-search { flex: 1; min-width: 230px; padding-left: 11px; border: 1px solid var(--line); border-radius: var(--r-sm); background: var(--field-surface); }
.activity-search:focus-within { border-color: var(--a-blue); box-shadow: var(--focus-ring); }
.activity-search input { flex: 1; min-height: 38px; padding: 0 10px 0 0; border: 0; background: transparent; box-shadow: none; }
.activity-toolbar select { min-width: 135px; }

.timeline { display: grid; gap: 8px; }
.event-row { display: grid; grid-template-columns: 8px minmax(0, 1fr) auto; align-items: center; gap: 12px; width: 100%; padding: 13px 15px; color: inherit; text-align: left; cursor: pointer; }
.event-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--a-blue); box-shadow: 0 0 12px color-mix(in srgb, var(--a-blue) 45%, transparent); }
.event-dot.success { background: var(--st-work); }
.event-dot.warning { background: var(--st-queue); }
.event-dot.danger { background: var(--st-block); }
.event-main { display: grid; gap: 5px; min-width: 0; }
.event-title { display: flex; align-items: center; gap: 8px; min-width: 0; }
.event-title strong { overflow: hidden; color: var(--tx); text-overflow: ellipsis; white-space: nowrap; }
.event-source { flex: 0 0 auto; padding: 2px 6px; border: 1px solid var(--line); border-radius: 999px; color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; }
.event-message { overflow: hidden; color: var(--tx-2); text-overflow: ellipsis; white-space: nowrap; }
.event-actor { display: inline-flex; align-items: center; gap: 5px; color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; }
.event-meta { display: grid; justify-items: end; gap: 7px; }
.event-meta time { color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; }
.event-meta .nexus-status { font-size: 11px; text-transform: capitalize; }

.activity-dialog-overlay { position: fixed; inset: 0; z-index: 300; display: grid; place-items: center; padding: 20px; background: color-mix(in srgb, var(--space-0) 78%, transparent); backdrop-filter: blur(12px); }
.activity-dialog { width: min(100%, 620px); max-height: calc(100dvh - 40px); overflow: auto; border: 1px solid var(--line-3); border-radius: var(--r-lg); background: color-mix(in srgb, var(--space-2) 94%, transparent); box-shadow: var(--panel-shadow); }
.activity-dialog > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding: 18px; border-bottom: 1px solid var(--line); }
.activity-dialog h2 { margin: 4px 0 0; font-family: var(--font-display); font-size: 20px; }
.activity-dialog header button { width: 36px; height: 36px; 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; }
.dialog-message { margin: 16px 18px 6px; color: var(--tx-2); line-height: 1.6; }
.activity-dialog dl { display: grid; margin: 0; padding: 8px 18px; }
.activity-dialog dl div { display: grid; grid-template-columns: 100px minmax(0, 1fr); gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--line); }
.activity-dialog dl div:last-child { border-bottom: 0; }
.activity-dialog dt { color: var(--tx-3); font-family: var(--font-mono-v2); font-size: 11px; text-transform: uppercase; }
.activity-dialog dd { margin: 0; color: var(--tx-2); overflow-wrap: anywhere; }
.activity-dialog code { font-family: var(--font-mono-v2); font-size: 11px; }
.dialog-actions { display: flex; justify-content: flex-end; gap: 10px; padding: 15px 18px 18px; }
.dialog-refs { display: flex; flex-wrap: wrap; gap: 8px; padding: 10px 18px 0; }
.spin { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

@media (max-width: 760px) {
  .activity-toolbar { align-items: stretch; flex-direction: column; }
  .activity-toolbar > label { width: 100%; }
  .activity-toolbar select { flex: 1; }
  .runtime-state { grid-template-columns: auto minmax(0, 1fr); }
  .runtime-state .nexus-button { grid-column: 1 / -1; justify-self: start; }
}

@media (max-width: 580px) {
  .activity-metrics { grid-template-columns: 1fr; }
  .event-row { grid-template-columns: 8px minmax(0, 1fr); }
  .event-meta { grid-column: 2; grid-row: 2; justify-items: start; }
  .event-message { white-space: normal; }
  .activity-dialog dl div { grid-template-columns: 1fr; gap: 4px; }
}
</style>
