<script setup lang="ts">
import type { PublicBooking, PublicInfo, SlotsResponse } from '~/types/booking'

const route = useRoute()
const tenantSlug = route.params.tenantSlug as string
const hostSlug = route.params.hostSlug as string
const eventTypeSlug = route.params.eventTypeSlug as string
const apiBase = `/api/public/${encodeURIComponent(tenantSlug)}/${encodeURIComponent(hostSlug)}/${encodeURIComponent(eventTypeSlug)}`

// Reschedule mode: the manage page links here with ?reschedule=<manageToken>.
// In this mode confirming moves the existing booking to the chosen slot.
const rescheduleToken = (route.query.reschedule as string | undefined) || undefined

// Event-type + host + branding (SSR).
const { data: info, error } = await useFetch<PublicInfo>(apiBase)

const visitorTz = ref('UTC')
const days = computed(() => nextDays(visitorTz.value, 14))
const selectedDate = ref<string | null>(null)

const slots = ref<SlotsResponse['slots']>([])
const slotsState = ref<'idle' | 'loading' | 'ready' | 'retry' | 'error'>('idle')

type Step = 'pick' | 'details' | 'done'
const step = ref<Step>('pick')
const selectedSlot = ref<{ startUtc: string; endUtc: string } | null>(null)

const form = reactive({ name: '', email: '', notes: '' })
const submitting = ref(false)
const submitError = ref<string | null>(null)
const booking = ref<PublicBooking | null>(null)

// Cloudflare Turnstile (optional). Only when a public site-key is configured do
// we load the script + render a widget on the details step; otherwise the form
// behaves exactly as before. We never solve the captcha — the widget produces a
// token that we forward to platform-api, which verifies it server-side.
const turnstileSiteKey = useRuntimeConfig().public.turnstileSiteKey as string
const captchaToken = ref<string | null>(null)
const captchaEl = ref<HTMLElement | null>(null)
let captchaWidgetId: string | null = null
const TURNSTILE_SRC = 'https://challenges.cloudflare.com/turnstile/v0/api.js'

function loadTurnstileScript(): Promise<void> {
  return new Promise((resolve, reject) => {
    if ((window as any).turnstile) return resolve()
    const existing = document.querySelector(`script[src^="${TURNSTILE_SRC}"]`) as HTMLScriptElement | null
    if (existing) {
      existing.addEventListener('load', () => resolve())
      existing.addEventListener('error', () => reject(new Error('turnstile load failed')))
      return
    }
    const s = document.createElement('script')
    s.src = TURNSTILE_SRC
    s.async = true
    s.defer = true
    s.addEventListener('load', () => resolve())
    s.addEventListener('error', () => reject(new Error('turnstile load failed')))
    document.head.appendChild(s)
  })
}

async function renderCaptcha() {
  if (!turnstileSiteKey || !import.meta.client || !captchaEl.value || captchaWidgetId) return
  try {
    await loadTurnstileScript()
    const turnstile = (window as any).turnstile
    if (!turnstile || !captchaEl.value) return
    captchaWidgetId = turnstile.render(captchaEl.value, {
      sitekey: turnstileSiteKey,
      callback: (token: string) => {
        captchaToken.value = token
      },
      'expired-callback': () => {
        captchaToken.value = null
      },
      'error-callback': () => {
        captchaToken.value = null
      },
    })
  } catch {
    // If the widget can't load we leave captchaToken null; the server decides
    // whether that's fatal (it is only when TURNSTILE_SECRET is set).
  }
}

function resetCaptcha() {
  captchaToken.value = null
  if (import.meta.client && captchaWidgetId && (window as any).turnstile) {
    ;(window as any).turnstile.reset(captchaWidgetId)
  }
}

// Render the widget whenever the details step becomes visible.
watch(step, (s) => {
  if (s === 'details' && turnstileSiteKey) void nextTick(renderCaptcha)
})

// Whitelabel theming — drive the accent from the tenant's brandColor.
watchEffect(() => {
  if (!import.meta.client || !info.value) return
  const accent = info.value.branding.brandColor || '#1a1a1a'
  const el = document.documentElement
  el.style.setProperty('--accent', accent)
  el.style.setProperty('--accent-contrast', readableOn(accent))
})

onMounted(() => {
  visitorTz.value = detectTimezone()
  if (!selectedDate.value) selectedDate.value = days.value[0]?.iso ?? null
  void loadSlots()
})

watch([selectedDate, visitorTz], () => loadSlots())

async function loadSlots() {
  if (!selectedDate.value) return
  slotsState.value = 'loading'
  try {
    const w = dayWindowUtc(selectedDate.value, visitorTz.value)
    const res = await $fetch<SlotsResponse>(`${apiBase}/slots`, {
      query: { from: w.from, to: w.to, timezone: visitorTz.value },
    })
    slots.value = res.slots
    slotsState.value = 'ready'
  } catch (e: any) {
    slotsState.value = e?.statusCode === 503 ? 'retry' : 'error'
    slots.value = []
  }
}

function pickSlot(s: { startUtc: string; endUtc: string }) {
  selectedSlot.value = s
  submitError.value = null
  // Reschedule carries the attendee over — no details form needed.
  if (rescheduleToken) void confirm()
  else step.value = 'details'
}

async function confirm() {
  if (!selectedSlot.value) return
  submitting.value = true
  submitError.value = null
  try {
    if (rescheduleToken) {
      booking.value = await $fetch<PublicBooking>(`/api/public/bookings/${encodeURIComponent(rescheduleToken)}/reschedule`, {
        method: 'POST',
        body: { startUtc: selectedSlot.value.startUtc },
      })
    } else {
      booking.value = await $fetch<PublicBooking>(`${apiBase}/bookings`, {
        method: 'POST',
        body: {
          startUtc: selectedSlot.value.startUtc,
          attendeeName: form.name,
          attendeeEmail: form.email,
          attendeeTimezone: visitorTz.value,
          attendeeNotes: form.notes || undefined,
          captchaToken: captchaToken.value || undefined,
        },
      })
    }
    step.value = 'done'
  } catch (e: any) {
    if (e?.statusCode === 409) {
      submitError.value = 'That time was just taken. Please pick another slot.'
      step.value = 'pick'
      await loadSlots()
    } else if (e?.statusCode === 503) {
      submitError.value = 'The calendar is temporarily unavailable. Please try again in a moment.'
    } else if (e?.statusCode === 403) {
      submitError.value = 'Captcha verification failed. Please complete the challenge and try again.'
      resetCaptcha()
    } else {
      submitError.value = e?.statusMessage || 'Something went wrong. Please try again.'
    }
  } finally {
    submitting.value = false
  }
}

const addToCalUrl = computed(() =>
  booking.value
    ? googleCalUrl({
        title: `${booking.value.eventType.title} with ${booking.value.host.displayName}`,
        startUtc: booking.value.startUtc,
        endUtc: booking.value.endUtc,
        location: booking.value.locationUrl ?? undefined,
      })
    : '#',
)
const manageUrl = computed(() => (booking.value ? `/manage/${booking.value.manageToken}` : '#'))

// Timezone switcher options — ensure the visitor's own zone is present.
const tzOptions = computed(() =>
  COMMON_TIMEZONES.includes(visitorTz.value) ? COMMON_TIMEZONES : [visitorTz.value, ...COMMON_TIMEZONES],
)

// User-facing location label — "jitsi" is the technical enum; show "dezky Meet".
function locationLabel(t: string): string {
  return (({ jitsi: 'dezky Meet', phone: 'Phone', in_person: 'In person', custom: 'Custom' }) as Record<string, string>)[t] ?? t
}

function readableOn(hex: string): string {
  const c = hex.replace('#', '')
  if (c.length < 6) return '#ffffff'
  const r = parseInt(c.slice(0, 2), 16)
  const g = parseInt(c.slice(2, 4), 16)
  const b = parseInt(c.slice(4, 6), 16)
  return (0.299 * r + 0.587 * g + 0.114 * b) / 255 > 0.6 ? '#1a1a1a' : '#ffffff'
}

useHead(() => ({ title: info.value ? `${info.value.eventType.title} · ${info.value.branding.name}` : 'Book a time' }))
</script>

<template>
  <main class="wrap">
    <div v-if="error || !info" class="card empty">
      <h1>Booking page not found</h1>
      <p class="mute">This link may be incorrect or no longer active.</p>
    </div>

    <div v-else class="card">
      <!-- Header -->
      <header class="head">
        <div class="brand">{{ info.branding.name }}</div>
        <h1 class="title">{{ info.eventType.title }}</h1>
        <p class="meta">
          {{ info.host.displayName }} · {{ info.eventType.durationMinutes }} min
          <span v-if="info.eventType.locationType"> · {{ locationLabel(info.eventType.locationType) }}</span>
        </p>
        <p v-if="info.eventType.description" class="desc">{{ info.eventType.description }}</p>
      </header>

      <!-- Step: pick -->
      <section v-if="step === 'pick'" class="step">
        <div v-if="rescheduleToken" class="rebanner">Choose a new time — your details carry over.</div>
        <div class="tzrow">
          <label class="bk-label" for="tz">Times shown in</label>
          <select id="tz" v-model="visitorTz" class="bk-input tz">
            <option v-for="tz in tzOptions" :key="tz" :value="tz">{{ tz }}</option>
          </select>
        </div>

        <div class="days">
          <button
            v-for="d in days"
            :key="d.iso"
            class="day"
            :class="{ active: d.iso === selectedDate }"
            @click="selectedDate = d.iso"
          >
            <span class="dow">{{ d.weekday }}</span>
            <span class="dnum">{{ d.day }}</span>
            <span class="dmon">{{ d.month }}</span>
          </button>
        </div>

        <p v-if="submitError" class="err">{{ submitError }}</p>

        <div class="slots">
          <p v-if="slotsState === 'loading'" class="mute">Loading times…</p>
          <p v-else-if="slotsState === 'retry'" class="err">
            Couldn’t verify availability. <button class="link" @click="loadSlots">Retry</button>
          </p>
          <p v-else-if="slotsState === 'error'" class="err">Couldn’t load times. <button class="link" @click="loadSlots">Retry</button></p>
          <p v-else-if="!slots.length" class="mute">No times available on this day.</p>
          <div v-else class="slotgrid">
            <button v-for="s in slots" :key="s.startUtc" class="slot" @click="pickSlot(s)">
              {{ fmtTime(s.startUtc, visitorTz) }}
            </button>
          </div>
        </div>
      </section>

      <!-- Step: details -->
      <section v-else-if="step === 'details'" class="step">
        <button class="back" @click="step = 'pick'">← Back</button>
        <p class="chosen">{{ selectedSlot ? fmtDateLong(selectedSlot.startUtc, visitorTz) : '' }} ·
          {{ selectedSlot ? fmtTime(selectedSlot.startUtc, visitorTz) : '' }} ({{ visitorTz }})</p>

        <form class="form" @submit.prevent="confirm">
          <div class="bk-field">
            <label class="bk-label" for="name">Your name</label>
            <input id="name" v-model="form.name" class="bk-input" required maxlength="120" />
          </div>
          <div class="bk-field">
            <label class="bk-label" for="email">Email</label>
            <input id="email" v-model="form.email" type="email" class="bk-input" required maxlength="254" />
          </div>
          <div class="bk-field">
            <label class="bk-label" for="notes">Notes (optional)</label>
            <textarea id="notes" v-model="form.notes" class="bk-input" maxlength="2000" />
          </div>
          <div v-if="turnstileSiteKey" ref="captchaEl" class="captcha" />
          <p v-if="submitError" class="err">{{ submitError }}</p>
          <button
            type="submit"
            class="bk-btn bk-btn--primary"
            :disabled="submitting || (!!turnstileSiteKey && !captchaToken)"
          >
            {{ submitting ? 'Confirming…' : 'Confirm booking' }}
          </button>
        </form>
      </section>

      <!-- Step: done -->
      <section v-else class="step done">
        <div class="tick" aria-hidden="true">✓</div>
        <h2>You’re booked</h2>
        <p class="chosen" v-if="booking">{{ fmtRange(booking.startUtc, booking.endUtc, booking.attendeeTimezone) }}</p>
        <p class="mute">A confirmation with a calendar invite has been sent to {{ booking?.attendeeEmail }}.</p>
        <p v-if="booking?.locationUrl" class="loc">Location: <a :href="booking.locationUrl">{{ booking.locationUrl }}</a></p>
        <div class="actions">
          <a class="bk-btn bk-btn--primary" :href="addToCalUrl" target="_blank" rel="noopener">Add to Google Calendar</a>
          <NuxtLink class="bk-btn bk-btn--ghost" :to="manageUrl">Manage booking</NuxtLink>
        </div>
      </section>
    </div>

    <p class="powered" v-if="info">Powered by {{ info.branding.name }}</p>
  </main>
</template>

<style scoped>
.wrap { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 32px 16px; }
.card { width: 100%; max-width: 560px; background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.card::before { content: ''; display: block; height: 6px; background: var(--accent); }
.head { padding: 28px 28px 0; }
.brand { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--text-mute); }
.title { font-size: 24px; margin: 6px 0 4px; }
.meta { color: var(--text-mute); margin: 0; font-size: 14px; }
.desc { margin: 14px 0 0; color: var(--text); font-size: 15px; }
.step { padding: 22px 28px 28px; }
.rebanner { background: #fef6e6; color: #9a6a00; font-size: 13px; padding: 8px 12px; border-radius: 8px; margin-bottom: 14px; }
.tzrow { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.tz { width: auto; height: 38px; flex: 1; }
.days { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 8px; }
.day { flex: 0 0 auto; width: 64px; padding: 10px 0; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.day.active { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.dow { font-size: 11px; text-transform: uppercase; color: var(--text-mute); }
.dnum { font-size: 18px; font-weight: 600; }
.dmon { font-size: 11px; color: var(--text-mute); }
.slots { margin-top: 18px; min-height: 80px; }
.slotgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 8px; }
.slot { height: 44px; border: 1px solid var(--border); border-radius: 10px; background: var(--surface); font-size: 15px; font-weight: 600; cursor: pointer; }
.slot:hover { border-color: var(--accent); }
.form { display: flex; flex-direction: column; gap: 16px; margin-top: 14px; }
.captcha { min-height: 65px; }
.chosen { font-weight: 600; margin: 0 0 6px; }
.back { background: none; border: none; color: var(--text-mute); cursor: pointer; padding: 0 0 12px; font-size: 14px; }
.done { text-align: center; }
.tick { width: 56px; height: 56px; border-radius: 50%; background: var(--accent); color: var(--accent-contrast); font-size: 28px; display: flex; align-items: center; justify-content: center; margin: 4px auto 12px; }
.actions { display: flex; gap: 10px; justify-content: center; margin-top: 20px; flex-wrap: wrap; }
.loc { font-size: 14px; }
.mute { color: var(--text-mute); }
.err { color: #c0362c; font-size: 14px; }
.link { background: none; border: none; color: var(--accent); cursor: pointer; text-decoration: underline; padding: 0; font: inherit; }
.empty { padding: 40px; text-align: center; }
.powered { margin-top: 16px; color: #aaa; font-size: 12px; }
</style>
