<script setup lang="ts">
interface Guest {
  id: string
  event_id: string
  name: string
  email?: string | null
  phone?: string | null
  plus_ones: number
  created_at: string
  rsvp_response?: 'attending' | 'declined' | 'maybe' | null
  rsvp_plus_ones?: number | null
  rsvp_risk_score?: number | null
  rsvp_submitted_at?: string | null
  has_token?: boolean
}

interface GuestStats {
  total: number
  attending: number
  declined: number
  maybe: number
  pending: number
}

interface EventDetail {
  id: string
  host_id: string
  name: string
  slug: string
  event_date: string
  venue: string
  max_capacity: number
  status: string
  created_at: string
  updated_at: string
}

interface IssuedToken {
  token: string
  token_id: string
}

interface WSMessage {
  type: string
  event_id: string
  payload: any
  timestamp: string
}

const route = useRoute()
const eventId = route.params.id as string

const event = ref<EventDetail | null>(null)
const guests = ref<Guest[]>([])
const stats = ref<GuestStats>({ total: 0, attending: 0, declined: 0, maybe: 0, pending: 0 })
const filter = ref<'all' | 'attending' | 'declined' | 'maybe' | 'pending'>('all')
const loading = ref(true)

async function refresh() {
  const [evt, list] = await Promise.all([
    useApi<EventDetail>(`/events/${eventId}`),
    useApi<{ guests: Guest[]; stats: GuestStats }>(`/events/${eventId}/guests`),
  ])
  event.value = evt
  guests.value = list.guests || []
  if (list.stats) stats.value = list.stats
}

const filteredGuests = computed(() => {
  if (filter.value === 'all') return guests.value
  if (filter.value === 'pending') return guests.value.filter((g) => !g.rsvp_response)
  return guests.value.filter((g) => g.rsvp_response === filter.value)
})

interface ActivityItem {
  type: 'rsvp' | 'access_check'
  ts: string
  guest_id: string
  guest_name: string
  // RSVP
  response?: string
  plus_ones?: number
  // Access check
  score?: number
  band?: string
  blocked?: boolean
}

onMounted(async () => {
  try {
    await refresh()
    // Pull history from the activity endpoint. The WebSocket hub only
    // broadcasts future events, so without this catch-up the monitor
    // is blank until a guest does something while the dashboard is open.
    backfillFeed()
  } finally {
    loading.value = false
  }
})

async function backfillFeed() {
  if (feed.value.length > 0) return // don't clobber WS items that arrived first
  try {
    const res = await useApi<{ activity: ActivityItem[] }>(
      `/events/${eventId}/activity?limit=50`,
    )
    feed.value = (res.activity || []).map(activityToFeedItem)
  } catch (e) {
    console.error('activity backfill failed', e)
  }
}

function activityToFeedItem(a: ActivityItem): FeedItem {
  if (a.type === 'rsvp') {
    return {
      type: 'rsvp.confirmed',
      ts: a.ts,
      text: `${a.guest_name} → ${a.response} (+${a.plus_ones || 0})`,
    }
  }
  // access_check — friendly text per band, matching the live-WS handler.
  const band = a.band || 'low'
  const who = a.guest_name || 'A guest'
  const friendlyText: Record<string, string> = {
    low: `${who} opened their invitation — looks normal.`,
    medium: `${who}'s access looks a bit unusual — worth a glance.`,
    high: `${who}'s access looks suspicious — review when you can.`,
    block: `${who}'s access was blocked.`,
  }
  return {
    type: 'fraud.scored',
    ts: a.ts,
    text: friendlyText[band] || `${who}'s invitation was checked.`,
    band,
  }
}

// Add-guest form
const newGuest = reactive({ name: '', email: '', phone: '', plus_ones: 0 })
const addingGuest = ref(false)

async function addGuest() {
  addingGuest.value = true
  try {
    await useApi(`/events/${eventId}/guests`, {
      method: 'POST',
      body: {
        name: newGuest.name,
        email: newGuest.email || null,
        phone: newGuest.phone || null,
        plus_ones: Number(newGuest.plus_ones) || 0,
      },
    })
    Object.assign(newGuest, { name: '', email: '', phone: '', plus_ones: 0 })
    await refresh()
  } finally {
    addingGuest.value = false
  }
}

// Issue token
const issued = ref<Record<string, IssuedToken>>({})
const issuing = ref<string | null>(null)
const copiedFor = ref<string | null>(null)
let copyResetTimer: ReturnType<typeof setTimeout> | null = null

async function issueToken(guestId: string) {
  issuing.value = guestId
  try {
    const res = await useApi<IssuedToken>(`/events/${eventId}/guests/${guestId}/tokens`, {
      method: 'POST',
    })
    issued.value[guestId] = res
  } catch (e) {
    console.error(e)
  } finally {
    issuing.value = null
  }
}

function rsvpUrl(token: string): string {
  if (import.meta.server) return ''
  return `${window.location.origin}/rsvp/${token}`
}

async function copyLink(guestId: string, token: string) {
  if (!import.meta.client) return
  try {
    await navigator.clipboard.writeText(rsvpUrl(token))
    copiedFor.value = guestId
    if (copyResetTimer) clearTimeout(copyResetTimer)
    copyResetTimer = setTimeout(() => {
      if (copiedFor.value === guestId) copiedFor.value = null
    }, 1500)
  } catch (e) {
    console.error('clipboard copy failed', e)
  }
}

// Live monitor — RSVP + fraud feed via WS
interface FeedItem {
  type: string
  ts: string
  text: string
  band?: string
}
const feed = ref<FeedItem[]>([])
const wsConnected = ref(false)
const guestById = computed(() => Object.fromEntries(guests.value.map((g) => [g.id, g])))

function pushFeed(item: FeedItem) {
  feed.value = [item, ...feed.value].slice(0, 50)
}

let stopWS: (() => void) | null = null
onMounted(() => {
  stopWS = useEventWS(eventId, (msg: WSMessage) => {
    wsConnected.value = true
    if (msg.type === 'rsvp.confirmed') {
      const g = guestById.value[msg.payload.guest_id]
      pushFeed({
        type: msg.type,
        ts: msg.timestamp,
        text: `${g?.name || 'Guest'} → ${msg.payload.response} (+${msg.payload.plus_ones || 0})`,
      })
      // Refresh stats and per-guest status so the counts reflect the new RSVP.
      refresh().catch(() => {})
    } else if (msg.type === 'fraud.scored') {
      const g = guestById.value[msg.payload.guest_id]
      const who = g?.name || 'A guest'
      const band = (msg.payload.risk as string) || 'low'
      // Plain-English text per band — no raw scores, no jargon.
      const friendlyText: Record<string, string> = {
        low: `${who} opened their invitation — looks normal.`,
        medium: `${who}'s access looks a bit unusual — worth a glance.`,
        high: `${who}'s access looks suspicious — review when you can.`,
        block: `${who}'s access was blocked.`,
      }
      pushFeed({
        type: msg.type,
        ts: msg.timestamp,
        text: friendlyText[band] || `${who}'s invitation was checked.`,
        band,
      })
    }
  })
})

onUnmounted(() => stopWS?.())

function fmtTime(iso: string): string {
  try { return new Date(iso).toLocaleTimeString() } catch { return iso }
}
function fmtDate(iso: string): string {
  try { return new Date(iso).toLocaleString() } catch { return iso }
}

// Friendly pill label for an access-check item (replaces "fraud · {band}").
function checkLabel(band?: string): string {
  switch (band) {
    case 'low':    return 'Verified'
    case 'medium': return 'Review'
    case 'high':   return 'Suspicious'
    case 'block':  return 'Blocked'
    default:       return 'Checked'
  }
}
</script>

<template>
  <section v-if="loading" class="text-sm text-zinc-500">Loading…</section>
  <section v-else-if="!event" class="card">Event not found.</section>
  <section v-else class="space-y-8">
    <div>
      <NuxtLink to="/dashboard" class="mb-2 inline-block text-sm text-zinc-400 hover:text-zinc-200">
        ← Back to dashboard
      </NuxtLink>
      <div class="flex items-center justify-between">
        <h1 class="text-2xl font-semibold">{{ event.name }}</h1>
        <span class="badge bg-zinc-800 text-zinc-300">{{ event.status }}</span>
      </div>
      <p class="text-sm text-zinc-400">{{ event.venue }} · {{ fmtDate(event.event_date) }}</p>
    </div>

    <div class="grid gap-8 lg:grid-cols-[2fr_1fr]">
      <!-- Guests + add form -->
      <div class="space-y-6">
        <div class="card">
          <h2 class="mb-3 text-lg font-semibold">Add a guest</h2>
          <form class="grid grid-cols-1 gap-3 md:grid-cols-2" @submit.prevent="addGuest">
            <input v-model="newGuest.name" placeholder="Name" class="input md:col-span-2" required />
            <input v-model="newGuest.email" type="email" placeholder="Email" class="input" />
            <input v-model="newGuest.phone" placeholder="Phone" class="input" />
            <!-- Plus-ones stepper -->
            <div class="md:col-span-2">
              <label class="label mb-1">Plus-ones allowed</label>
              <div class="flex items-center overflow-hidden rounded-lg border border-zinc-700 bg-zinc-900">
                <button
                  type="button"
                  class="flex h-10 w-11 shrink-0 items-center justify-center text-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-100 disabled:opacity-30"
                  :disabled="newGuest.plus_ones <= 0"
                  @click="newGuest.plus_ones = Math.max(0, newGuest.plus_ones - 1)"
                >−</button>
                <span class="flex-1 text-center font-semibold tabular-nums text-zinc-100">{{ newGuest.plus_ones }}</span>
                <button
                  type="button"
                  class="flex h-10 w-11 shrink-0 items-center justify-center text-lg text-zinc-400 transition hover:bg-zinc-800 hover:text-zinc-100"
                  @click="newGuest.plus_ones++"
                >+</button>
              </div>
            </div>
            <button class="btn-primary md:col-span-2" :disabled="addingGuest">
              {{ addingGuest ? 'Adding…' : 'Add guest' }}
            </button>
          </form>
        </div>

        <div class="card">
          <div class="mb-4 flex items-center justify-between">
            <h2 class="text-lg font-semibold">Guests</h2>
            <button class="text-xs text-zinc-400 hover:text-zinc-200" @click="refresh">Refresh</button>
          </div>

          <div class="mb-4 grid grid-cols-2 gap-2 sm:grid-cols-5">
            <button
              v-for="bucket in (['all','attending','declined','maybe','pending'] as const)"
              :key="bucket"
              class="rounded-md border px-3 py-2 text-left text-xs transition"
              :class="filter === bucket
                ? 'border-brand-500 bg-brand-950/40 text-brand-200'
                : 'border-zinc-800 bg-zinc-950 text-zinc-400 hover:border-zinc-700'"
              @click="filter = bucket"
            >
              <div class="text-base font-semibold text-zinc-100">
                {{ bucket === 'all' ? stats.total : stats[bucket] }}
              </div>
              <div class="capitalize">{{ bucket }}</div>
            </button>
          </div>

          <div v-if="guests.length === 0" class="text-sm text-zinc-500">No guests yet.</div>
          <div v-else-if="filteredGuests.length === 0" class="text-sm text-zinc-500">
            No guests match this filter.
          </div>
          <ul v-else class="divide-y divide-zinc-800">
            <li v-for="g in filteredGuests" :key="g.id" class="py-3 first:pt-0 last:pb-0">
              <div class="flex items-center justify-between gap-4">
                <div class="min-w-0 flex-1">
                  <div class="flex items-center gap-2">
                    <span class="truncate font-medium text-zinc-100">{{ g.name }}</span>
                    <span v-if="g.rsvp_response === 'attending'" class="badge-low">attending</span>
                    <span v-else-if="g.rsvp_response === 'declined'" class="badge bg-zinc-800 text-zinc-300">declined</span>
                    <span v-else-if="g.rsvp_response === 'maybe'" class="badge-medium">maybe</span>
                    <span v-else class="badge bg-zinc-800/60 text-zinc-500">no response</span>

                    <span
                      v-if="g.rsvp_risk_score != null && g.rsvp_risk_score >= 60"
                      class="badge-high"
                      :title="`Risk score ${g.rsvp_risk_score}`"
                    >flagged</span>
                  </div>
                  <div class="mt-0.5 truncate text-xs text-zinc-500">
                    {{ g.email || '—' }}
                    <span v-if="g.rsvp_response">
                      · bringing
                      {{ g.rsvp_plus_ones ?? 0 }} of {{ g.plus_ones }} plus-ones
                    </span>
                    <span v-else>
                      · invited +{{ g.plus_ones }}
                    </span>
                  </div>
                  <div v-if="issued[g.id]" class="mt-2 break-all rounded border border-zinc-800 bg-zinc-950 px-2 py-1 font-mono text-xs text-brand-300">
                    {{ rsvpUrl(issued[g.id].token) }}
                  </div>
                </div>
                <div class="flex shrink-0 gap-2">
                  <button
                    v-if="!issued[g.id] && !g.rsvp_response"
                    class="btn-ghost"
                    :disabled="issuing === g.id || g.has_token"
                    :title="g.has_token ? 'A token has already been issued for this guest' : ''"
                    @click="issueToken(g.id)"
                  >
                    {{ issuing === g.id ? '…' : g.has_token ? 'Link issued' : 'Generate link' }}
                  </button>
                  <button
                    v-else-if="issued[g.id]"
                    class="btn-ghost"
                    :class="copiedFor === g.id ? 'text-brand-300' : ''"
                    @click="copyLink(g.id, issued[g.id].token)"
                  >
                    {{ copiedFor === g.id ? 'Copied ✓' : 'Copy link' }}
                  </button>
                </div>
              </div>
            </li>
          </ul>
        </div>
      </div>

      <!-- Live monitor -->
      <aside class="card flex max-h-[80vh] flex-col">
        <div class="mb-3 flex items-center justify-between">
          <h2 class="text-lg font-semibold">Live monitor</h2>
          <span
            class="badge"
            :class="wsConnected ? 'bg-brand-900/40 text-brand-300' : 'bg-zinc-800 text-zinc-400'"
          >
            <span class="mr-1 inline-block h-1.5 w-1.5 rounded-full" :class="wsConnected ? 'bg-brand-400' : 'bg-zinc-500'"></span>
            {{ wsConnected ? 'live' : 'connecting' }}
          </span>
        </div>
        <p class="mb-3 text-xs text-zinc-500">
          Guest responses and security alerts, the moment they happen.
        </p>
        <div class="flex-1 overflow-y-auto">
          <div v-if="feed.length === 0" class="rounded-lg border border-zinc-800/50 bg-zinc-900/30 p-5 text-center">
            <p class="mb-1 text-sm font-medium text-zinc-400">All quiet for now</p>
            <p class="text-xs leading-relaxed text-zinc-600">
              Once guests start responding, their RSVPs and any<br />
              security alerts will appear here in real time.
            </p>
          </div>
          <ul v-else class="space-y-2">
            <li
              v-for="(item, i) in feed"
              :key="`${item.type}-${item.ts}-${i}`"
              class="rounded border border-zinc-800 bg-zinc-950 p-3 text-sm"
            >
              <div class="mb-1 flex items-center justify-between text-xs">
                <span class="font-mono text-zinc-500">{{ fmtTime(item.ts) }}</span>
                <span
                  v-if="item.type === 'fraud.scored'"
                  :class="`badge-${item.band || 'low'}`"
                >{{ checkLabel(item.band) }}</span>
                <span v-else class="badge-low">RSVP</span>
              </div>
              <p class="text-zinc-200">{{ item.text }}</p>
            </li>
          </ul>
        </div>
      </aside>
    </div>
  </section>
</template>
