<script setup lang="ts">
// dezky Scheduling admin. Make a workspace user bookable (auto-provisions their
// Stalwart calendar credential), then configure event types + availability and
// view bookings. Public pages live on booking.dezky.eu/:tenant/:host/:eventType.

interface Host {
  _id: string
  email: string
  displayName: string
  slug: string
  timezone: string
  isActive: boolean
  defaultCalendarId?: string
}
interface EventType {
  _id: string
  slug: string
  title: string
  description?: string
  durationMinutes: number
  slotIntervalMinutes: number
  bufferBeforeMinutes: number
  bufferAfterMinutes: number
  minimumNoticeMinutes: number
  maximumDaysInFuture: number
  availabilityScheduleId: string
  locationType: string
  ignoreAllDayEvents: boolean
  assignment?: 'single' | 'round_robin'
  hostPool?: string[]
  isActive: boolean
}
interface MinuteInterval { startMinute: number; endMinute: number }
interface WeeklyRule { dayOfWeek: number; intervals: MinuteInterval[] }
interface DateOverride { date: string; isUnavailable: boolean; intervals: MinuteInterval[] }
interface Availability { _id: string; name: string; timezone: string; weeklyRules: WeeklyRule[]; dateOverrides?: DateOverride[] }
interface Booking {
  _id: string
  status: string
  startUtc: string
  endUtc: string
  attendeeName: string
  attendeeEmail: string
  eventTypeId: string
  lastCalendarError?: string
}

const toast = useToast()
const { tenant } = useTenant()
const slug = computed(() => tenant.value?.slug ?? '')
const { request } = useApiFetch()
const bookingBase = (useRuntimeConfig().public.bookingUrl as string) || 'https://booking.dezky.local'
const bookingHost = bookingBase.replace(/^https?:\/\//, '')

const base = computed(() => `/api/tenants/${slug.value}/scheduling`)

function toastErr(err: unknown, title: string) {
  const e = err as { data?: { message?: string | string[] }; statusMessage?: string; message?: string }
  const m = e?.data?.message ?? e?.statusMessage ?? e?.message ?? 'Unknown error'
  toast.bad(title, Array.isArray(m) ? m.join(', ') : String(m))
}

const minToTime = (m: number) => `${String(Math.floor(m / 60)).padStart(2, '0')}:${String(m % 60).padStart(2, '0')}`

// Generic confirm dialog (deletes + cancellations route through this).
const confirmBox = reactive({ open: false, title: '', message: '', confirmLabel: 'Confirm', busy: false, action: null as null | (() => Promise<void>) })
function askConfirm(opts: { title: string; message: string; confirmLabel?: string; action: () => Promise<void> }) {
  Object.assign(confirmBox, { confirmLabel: 'Confirm', ...opts, open: true, busy: false })
}
async function runConfirm() {
  if (!confirmBox.action) return
  confirmBox.busy = true
  try {
    await confirmBox.action()
    confirmBox.open = false
  } catch (err) {
    toastErr(err, 'Action failed')
  } finally {
    confirmBox.busy = false
  }
}

// ── Overview / analytics (read-only, tenant-level) ──
interface HostCount { hostId: string; displayName: string; count: number }
interface DayPoint { date: string; count: number }
interface SchedulingOverview {
  totalBookings: number
  upcomingCount: number
  byStatus: Record<string, number>
  byHost: HostCount[]
  last30Days: DayPoint[]
}
const overview = ref<SchedulingOverview | null>(null)
async function loadOverview() {
  try {
    overview.value = (await request(`${base.value}/overview`)) as SchedulingOverview
  } catch (err) {
    toastErr(err, 'Could not load overview')
  }
}
if (slug.value) loadOverview()
watch(slug, (s) => { if (s) loadOverview() })

// Series scaling for the simple bar sparkline.
const seriesMax = computed(() => Math.max(1, ...(overview.value?.last30Days ?? []).map((d) => d.count)))
function barHeight(count: number): number {
  // Min 2% so empty days still show a faint baseline tick.
  return Math.max(2, Math.round((count / seriesMax.value) * 100))
}
const dayLabel = (date: string) =>
  new Date(`${date}T00:00:00Z`).toLocaleDateString('en-GB', { day: 'numeric', month: 'short', timeZone: 'UTC' })
// Statuses shown in the breakdown, in a sensible reading order.
const STATUS_ORDER = ['confirmed', 'pending', 'rescheduled', 'cancelled', 'calendar_failed'] as const
const statusLabel: Record<string, string> = {
  confirmed: 'Confirmed',
  pending: 'Pending',
  rescheduled: 'Rescheduled',
  cancelled: 'Cancelled',
  calendar_failed: 'Calendar failed',
}
const statusBreakdown = computed(() =>
  STATUS_ORDER.map((s) => ({ status: s, label: statusLabel[s], count: overview.value?.byStatus?.[s] ?? 0 })).filter(
    (r) => r.count > 0,
  ),
)

const { data: hosts, refresh: refreshHosts } = await useFetch<Host[]>(() => `${base.value}/hosts`, {
  key: 'sched-hosts',
  default: () => [],
  immediate: !!slug.value,
  watch: [slug],
})

const selectedHostId = ref<string | null>(null)
const selectedHost = computed(() => hosts.value?.find((h) => h._id === selectedHostId.value) ?? null)
// Round-robin pool candidates: every other host in the tenant (the owning host is
// always implicitly part of the pool, so it is excluded from the selectable list).
const poolCandidates = computed(() => (hosts.value ?? []).filter((h) => h._id !== selectedHostId.value))
const detailTab = ref<'event-types' | 'availability' | 'bookings'>('event-types')

const eventTypes = ref<EventType[]>([])
const availability = ref<Availability[]>([])
const bookings = ref<Booking[]>([])

async function selectHost(id: string) {
  selectedHostId.value = id
  detailTab.value = 'event-types'
  await loadHostData()
}
async function loadHostData() {
  if (!selectedHostId.value) return
  try {
    const id = selectedHostId.value
    ;[eventTypes.value, availability.value, bookings.value] = await Promise.all([
      request(`${base.value}/hosts/${id}/event-types`) as Promise<EventType[]>,
      request(`${base.value}/hosts/${id}/availability`) as Promise<Availability[]>,
      request(`${base.value}/bookings?hostId=${id}`) as Promise<Booking[]>,
    ])
  } catch (err) {
    toastErr(err, 'Could not load host data')
  }
}

// ── Workspace users (bookable candidates need a mailbox) ──
interface WsUser { _id: string; name: string; email: string; mailboxAddress?: string }
const { data: users } = await useFetch<WsUser[]>(() => `/api/tenants/${slug.value}/users`, {
  key: 'sched-users', default: () => [], immediate: !!slug.value, watch: [slug],
})
const hostEmails = computed(() => new Set((hosts.value ?? []).map((h) => h.email)))
const takenSlugs = computed(() => new Set((hosts.value ?? []).map((h) => h.slug)))
// Only users with a mailbox, and not already a host.
const availableUsers = computed(() =>
  (users.value ?? []).filter((u) => !!u.mailboxAddress && !hostEmails.value.has(u.mailboxAddress!)),
)

const SLUG_RE = /^[a-z0-9]([a-z0-9-]{0,38}[a-z0-9])?$/
function slugify(name: string): string {
  return name.toLowerCase().normalize('NFKD').replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 40).replace(/-+$/, '')
}
function freeSlug(baseSlug: string): string {
  if (!baseSlug || !takenSlugs.value.has(baseSlug)) return baseSlug
  let i = 2
  while (takenSlugs.value.has(`${baseSlug}-${i}`)) i++
  return `${baseSlug}-${i}`
}

// ── Create host ──
const hostOpen = ref(false)
const hostBusy = ref(false)
const hostForm = reactive({ userId: '', slug: '', timezone: 'Europe/Copenhagen' })
const slugTaken = computed(() => takenSlugs.value.has(hostForm.slug))
const slugValid = computed(() => SLUG_RE.test(hostForm.slug))
function openHost() {
  const first = availableUsers.value[0]
  hostForm.userId = first?._id ?? ''
  hostForm.slug = first ? freeSlug(slugify(first.name)) : ''
  hostForm.timezone = 'Europe/Copenhagen'
  hostOpen.value = true
}
// Re-derive a free slug from the chosen user's display name.
function onHostUserChange() {
  const u = availableUsers.value.find((x) => x._id === hostForm.userId)
  if (u) hostForm.slug = freeSlug(slugify(u.name))
}
async function submitHost() {
  if (!hostForm.userId || !slugValid.value || slugTaken.value) return
  hostBusy.value = true
  try {
    await request(`${base.value}/hosts`, { method: 'POST', body: { ...hostForm } })
    toast.ok('Host created', 'Calendar access was provisioned automatically.')
    hostOpen.value = false
    await refreshHosts()
  } catch (err) {
    toastErr(err, 'Could not create host')
  } finally {
    hostBusy.value = false
  }
}

// ── Create availability ──
const DAYS = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
const availOpen = ref(false)
const availBusy = ref(false)
// Date-override editor rows mirror the backend shape but hold times as HH:MM
// for the <input type="time"> controls; we convert to minutes on submit.
interface OverrideRow { date: string; isUnavailable: boolean; start: string; end: string }
const availForm = reactive({
  name: 'Working hours',
  timezone: 'Europe/Copenhagen',
  days: DAYS.map((_, i) => ({ enabled: i >= 1 && i <= 5, start: '09:00', end: '17:00' })),
  overrides: [] as OverrideRow[],
})
const availEditingId = ref<string | null>(null)
function openAvail(a?: Availability) {
  if (a) {
    availEditingId.value = a._id
    availForm.name = a.name
    availForm.timezone = a.timezone
    availForm.days = DAYS.map((_, i) => {
      const intv = a.weeklyRules.find((r) => r.dayOfWeek === i)?.intervals?.[0]
      return intv
        ? { enabled: true, start: minToTime(intv.startMinute), end: minToTime(intv.endMinute) }
        : { enabled: false, start: '09:00', end: '17:00' }
    })
    availForm.overrides = (a.dateOverrides ?? []).map((o) => {
      const intv = o.intervals?.[0]
      return {
        date: o.date,
        isUnavailable: o.isUnavailable,
        start: intv ? minToTime(intv.startMinute) : '09:00',
        end: intv ? minToTime(intv.endMinute) : '17:00',
      }
    })
  } else {
    availEditingId.value = null
    availForm.name = 'Working hours'
    availForm.timezone = selectedHost.value?.timezone ?? 'Europe/Copenhagen'
    availForm.days = DAYS.map((_, i) => ({ enabled: i >= 1 && i <= 5, start: '09:00', end: '17:00' }))
    availForm.overrides = []
  }
  availOpen.value = true
}
function addOverride() {
  availForm.overrides.push({ date: '', isUnavailable: false, start: '09:00', end: '17:00' })
}
function removeOverride(idx: number) {
  availForm.overrides.splice(idx, 1)
}
const timeToMin = (t: string) => { const [h = 0, m = 0] = t.split(':').map(Number); return h * 60 + m }
async function submitAvail() {
  if (!selectedHostId.value) return
  availBusy.value = true
  try {
    const weeklyRules: WeeklyRule[] = availForm.days
      .map((d, i) => ({ dayOfWeek: i, enabled: d.enabled, start: d.start, end: d.end }))
      .filter((d) => d.enabled && timeToMin(d.end) > timeToMin(d.start))
      .map((d) => ({ dayOfWeek: d.dayOfWeek, intervals: [{ startMinute: timeToMin(d.start), endMinute: timeToMin(d.end) }] }))
    const dateOverrides: DateOverride[] = availForm.overrides
      .filter((o) => /^\d{4}-\d{2}-\d{2}$/.test(o.date))
      .filter((o) => o.isUnavailable || timeToMin(o.end) > timeToMin(o.start))
      .map((o) => ({
        date: o.date,
        isUnavailable: o.isUnavailable,
        intervals: o.isUnavailable ? [] : [{ startMinute: timeToMin(o.start), endMinute: timeToMin(o.end) }],
      }))
    const body = { name: availForm.name, timezone: availForm.timezone, weeklyRules, dateOverrides }
    if (availEditingId.value) {
      await request(`${base.value}/availability/${availEditingId.value}`, { method: 'PATCH', body })
      toast.ok('Availability updated')
    } else {
      await request(`${base.value}/hosts/${selectedHostId.value}/availability`, { method: 'POST', body })
      toast.ok('Availability saved')
    }
    availOpen.value = false
    await loadHostData()
  } catch (err) {
    toastErr(err, 'Could not save availability')
  } finally {
    availBusy.value = false
  }
}
function deleteAvail(a: Availability) {
  askConfirm({
    title: 'Delete availability schedule',
    message: `Delete “${a.name}”? Event types using it must be reassigned or deleted first.`,
    confirmLabel: 'Delete',
    action: async () => {
      await request(`${base.value}/availability/${a._id}`, { method: 'DELETE' })
      toast.ok('Schedule deleted')
      await loadHostData()
    },
  })
}

function cancelBookingAdmin(b: Booking) {
  askConfirm({
    title: 'Cancel booking',
    message: `Cancel ${b.attendeeName}’s booking? They’ll be emailed a cancellation and the calendar event is removed.`,
    confirmLabel: 'Cancel booking',
    action: async () => {
      await request(`${base.value}/bookings/${b._id}/cancel`, { method: 'POST', body: { reason: 'Cancelled by host' } })
      toast.ok('Booking cancelled')
      await loadHostData()
    },
  })
}

// Badge tone for a booking status (Badge supports ok/warn/bad/neutral).
function bookingTone(status: string): 'ok' | 'warn' | 'bad' | 'neutral' {
  if (status === 'confirmed') return 'ok'
  if (status === 'pending') return 'warn'
  if (status === 'calendar_failed') return 'bad'
  return 'neutral'
}

// "Retry now" — re-drive the Stalwart calendar write for a pending /
// calendar_failed booking. On success it confirms + emails the attendee.
const retryingId = ref<string | null>(null)
async function retryBookingAdmin(b: Booking) {
  retryingId.value = b._id
  try {
    const updated = (await request(`${base.value}/bookings/${b._id}/retry`, { method: 'POST' })) as Booking
    toast.ok(updated.status === 'confirmed' ? 'Booking confirmed' : 'Retry queued', updated.status === 'confirmed' ? 'The calendar event was written and the attendee emailed.' : 'The calendar write failed again; the background worker will keep retrying.')
    await loadHostData()
    await loadOverview()
  } catch (err) {
    toastErr(err, 'Retry failed')
  } finally {
    retryingId.value = null
  }
}

// ── Create event type ──
const etOpen = ref(false)
const etBusy = ref(false)
const etForm = reactive({
  title: '',
  slug: '',
  durationMinutes: 30,
  slotIntervalMinutes: 15,
  bufferBeforeMinutes: 0,
  bufferAfterMinutes: 0,
  minimumNoticeMinutes: 60,
  maximumDaysInFuture: 60,
  availabilityScheduleId: '',
  locationType: 'jitsi',
  ignoreAllDayEvents: true,
  assignment: 'single' as 'single' | 'round_robin',
  hostPool: [] as string[],
})
const etEditingId = ref<string | null>(null)
const etSlugTouched = ref(false)
const etSlugValid = computed(() => SLUG_RE.test(etForm.slug))
const etValid = computed(() => etForm.title.trim().length > 0 && etSlugValid.value && !!etForm.availabilityScheduleId)
function openEt(et?: EventType) {
  if (et) {
    etEditingId.value = et._id
    Object.assign(etForm, {
      title: et.title, slug: et.slug, durationMinutes: et.durationMinutes,
      slotIntervalMinutes: et.slotIntervalMinutes, bufferBeforeMinutes: et.bufferBeforeMinutes,
      bufferAfterMinutes: et.bufferAfterMinutes, minimumNoticeMinutes: et.minimumNoticeMinutes,
      maximumDaysInFuture: et.maximumDaysInFuture, availabilityScheduleId: et.availabilityScheduleId,
      locationType: et.locationType, ignoreAllDayEvents: et.ignoreAllDayEvents ?? true,
      assignment: et.assignment ?? 'single', hostPool: [...(et.hostPool ?? [])],
    })
    etSlugTouched.value = true // don't auto-rewrite an existing slug
  } else {
    etEditingId.value = null
    Object.assign(etForm, {
      title: '', slug: '', durationMinutes: 30, slotIntervalMinutes: 15,
      bufferBeforeMinutes: 0, bufferAfterMinutes: 0, minimumNoticeMinutes: 60,
      maximumDaysInFuture: 60, availabilityScheduleId: availability.value[0]?._id ?? '', locationType: 'jitsi',
      ignoreAllDayEvents: true, assignment: 'single', hostPool: [],
    })
    etSlugTouched.value = false
  }
  etOpen.value = true
}
// Auto-derive the slug from the title until the user edits the slug themselves.
function onEtTitle() {
  if (!etSlugTouched.value) etForm.slug = slugify(etForm.title)
}
async function submitEt() {
  if (!selectedHostId.value || !etValid.value) return
  etBusy.value = true
  try {
    // Only send a host pool for round-robin; single-host keeps the legacy shape.
    const payload = {
      ...etForm,
      hostPool: etForm.assignment === 'round_robin' ? [...etForm.hostPool] : [],
    }
    if (etEditingId.value) {
      // slug is immutable after creation (public links would break) → omit it.
      const { slug: _slug, ...patch } = payload
      await request(`${base.value}/event-types/${etEditingId.value}`, { method: 'PATCH', body: patch })
      toast.ok('Event type updated')
    } else {
      await request(`${base.value}/hosts/${selectedHostId.value}/event-types`, { method: 'POST', body: payload })
      toast.ok('Event type created')
    }
    etOpen.value = false
    await loadHostData()
  } catch (err) {
    toastErr(err, etEditingId.value ? 'Could not update event type' : 'Could not create event type')
  } finally {
    etBusy.value = false
  }
}
function deleteEt(et: EventType) {
  askConfirm({
    title: 'Delete event type',
    message: `Delete “${et.title}”? Its public page stops accepting new bookings. Existing bookings are unaffected.`,
    confirmLabel: 'Delete',
    action: async () => {
      await request(`${base.value}/event-types/${et._id}`, { method: 'DELETE' })
      toast.ok('Event type deleted')
      await loadHostData()
    },
  })
}

// ── Reschedule a booking (admin) ──
interface DaySlots { key: string; label: string; slots: { startUtc: string }[] }
const reschedule = reactive({
  open: false,
  busy: false,
  booking: null as Booking | null,
  hostTz: 'Europe/Copenhagen',
  state: 'idle' as 'idle' | 'loading' | 'ready' | 'error',
  days: [] as DaySlots[],
  selectedKey: '',
})
const reschedDay = computed(() => reschedule.days.find((d) => d.key === reschedule.selectedKey) ?? null)

async function openReschedule(b: Booking) {
  const et = eventTypes.value.find((e) => e._id === b.eventTypeId)
  if (!et || !selectedHost.value) {
    toast.bad('Cannot reschedule', 'The event type for this booking no longer exists.')
    return
  }
  Object.assign(reschedule, {
    open: true, busy: false, booking: b, hostTz: selectedHost.value.timezone, state: 'loading', days: [], selectedKey: '',
  })
  try {
    const from = new Date().toISOString()
    const to = new Date(Date.now() + 14 * 86_400_000).toISOString()
    const res = (await request(
      `/api/scheduling-slots/${slug.value}/${selectedHost.value.slug}/${et.slug}?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}&timezone=${encodeURIComponent(reschedule.hostTz)}`,
    )) as { slots: { startUtc: string }[] }
    reschedule.days = groupByDay(res.slots, reschedule.hostTz)
    reschedule.selectedKey = reschedule.days[0]?.key ?? ''
    reschedule.state = 'ready'
  } catch (err) {
    reschedule.state = 'error'
    toastErr(err, 'Could not load times')
  }
}
function groupByDay(slots: { startUtc: string }[], tz: string): DaySlots[] {
  const keyFmt = new Intl.DateTimeFormat('en-CA', { timeZone: tz, year: 'numeric', month: '2-digit', day: '2-digit' })
  const labelFmt = new Intl.DateTimeFormat('en-GB', { timeZone: tz, weekday: 'short', day: 'numeric', month: 'short' })
  const map = new Map<string, DaySlots>()
  for (const s of slots) {
    const d = new Date(s.startUtc)
    const key = keyFmt.format(d)
    if (!map.has(key)) map.set(key, { key, label: labelFmt.format(d), slots: [] })
    map.get(key)!.slots.push(s)
  }
  return [...map.values()]
}
function reschedTime(iso: string): string {
  return new Intl.DateTimeFormat('en-GB', { timeZone: reschedule.hostTz, hour: '2-digit', minute: '2-digit', hour12: false }).format(new Date(iso))
}
async function confirmReschedule(startUtc: string) {
  if (!reschedule.booking) return
  reschedule.busy = true
  try {
    await request(`${base.value}/bookings/${reschedule.booking._id}/reschedule`, { method: 'POST', body: { startUtc } })
    toast.ok('Booking rescheduled', 'The attendee has been emailed the new time.')
    reschedule.open = false
    await loadHostData()
  } catch (err) {
    toastErr(err, 'Could not reschedule')
  } finally {
    reschedule.busy = false
  }
}

function publicUrl(et: EventType): string {
  return `${bookingBase}/${slug.value}/${selectedHost.value?.slug}/${et.slug}`
}
async function copyUrl(et: EventType) {
  try {
    await navigator.clipboard.writeText(publicUrl(et))
    toast.ok('Link copied')
  } catch {
    toast.bad('Copy failed', publicUrl(et))
  }
}

const fmtDateTime = (iso: string) =>
  new Date(iso).toLocaleString('en-GB', { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' })

const detailTabs = computed(() => [
  { value: 'event-types', label: 'Event types', count: eventTypes.value.length },
  { value: 'availability', label: 'Availability', count: availability.value.length },
  { value: 'bookings', label: 'Bookings', count: bookings.value.length },
])

// ── Webhooks (tenant-level: signed POSTs on booking lifecycle) ──
interface Webhook { _id: string; url: string; secret: string; events: string[]; active: boolean }
const ALL_WEBHOOK_EVENTS = ['booking.created', 'booking.cancelled', 'booking.rescheduled']
const webhooks = ref<Webhook[]>([])
const webhooksLoaded = ref(false)
const revealedSecrets = reactive<Record<string, boolean>>({})

async function loadWebhooks() {
  try {
    webhooks.value = (await request(`${base.value}/webhooks`)) as Webhook[]
  } catch (err) {
    toastErr(err, 'Could not load webhooks')
  } finally {
    webhooksLoaded.value = true
  }
}
if (slug.value) loadWebhooks()
watch(slug, (s) => { if (s) loadWebhooks() })

const whOpen = ref(false)
const whBusy = ref(false)
const whEditingId = ref<string | null>(null)
const whForm = reactive({ url: '', events: [...ALL_WEBHOOK_EVENTS], active: true })
const whUrlValid = computed(() => /^https?:\/\/.+/i.test(whForm.url.trim()))

function openWebhook(w?: Webhook) {
  if (w) {
    whEditingId.value = w._id
    Object.assign(whForm, { url: w.url, events: [...w.events], active: w.active })
  } else {
    whEditingId.value = null
    Object.assign(whForm, { url: '', events: [...ALL_WEBHOOK_EVENTS], active: true })
  }
  whOpen.value = true
}
function toggleWhEvent(ev: string) {
  const i = whForm.events.indexOf(ev)
  if (i === -1) whForm.events.push(ev)
  else whForm.events.splice(i, 1)
}
async function submitWebhook() {
  if (!whUrlValid.value || !whForm.events.length) return
  whBusy.value = true
  try {
    if (whEditingId.value) {
      await request(`${base.value}/webhooks/${whEditingId.value}`, {
        method: 'PATCH',
        body: { url: whForm.url.trim(), events: whForm.events, active: whForm.active },
      })
      toast.ok('Webhook updated')
    } else {
      await request(`${base.value}/webhooks`, { method: 'POST', body: { url: whForm.url.trim(), events: whForm.events } })
      toast.ok('Webhook created', 'Copy the signing secret now — keep it safe.')
    }
    whOpen.value = false
    await loadWebhooks()
  } catch (err) {
    toastErr(err, 'Could not save webhook')
  } finally {
    whBusy.value = false
  }
}
function deleteWebhook(w: Webhook) {
  askConfirm({
    title: 'Delete webhook',
    message: `Stop sending booking events to ${w.url}?`,
    confirmLabel: 'Delete',
    action: async () => {
      await request(`${base.value}/webhooks/${w._id}`, { method: 'DELETE' })
      await loadWebhooks()
      toast.ok('Webhook deleted')
    },
  })
}
function rotateWebhookSecret(w: Webhook) {
  askConfirm({
    title: 'Rotate signing secret',
    message: 'The old secret stops working immediately. Update your receiver after rotating.',
    confirmLabel: 'Rotate',
    action: async () => {
      await request(`${base.value}/webhooks/${w._id}/rotate-secret`, { method: 'POST' })
      revealedSecrets[w._id] = true
      await loadWebhooks()
      toast.ok('Secret rotated', 'Copy the new signing secret now.')
    },
  })
}
async function copySecret(secret: string) {
  try {
    await navigator.clipboard.writeText(secret)
    toast.ok('Secret copied')
  } catch {
    toast.bad('Copy failed')
  }
}
const maskSecret = (s: string) => (s.length > 12 ? `${s.slice(0, 9)}…${s.slice(-4)}` : s)
</script>

<template>
  <div>
    <PageHeader eyebrow="Workspace" title="Scheduling" subtitle="Public booking pages backed by your team's calendars.">
      <template #actions>
        <UiButton variant="primary" @click="openHost">
          <template #leading><UiIcon name="plus" :size="14" /></template>
          Add bookable host
        </UiButton>
      </template>
    </PageHeader>

    <div class="content">
      <!-- Overview / analytics (read-only) -->
      <section v-if="overview" class="overview">
        <div class="statcards">
          <Card class="statcard">
            <div class="statlabel">Total bookings</div>
            <div class="statvalue">{{ overview.totalBookings }}</div>
          </Card>
          <Card class="statcard">
            <div class="statlabel">Upcoming</div>
            <div class="statvalue">{{ overview.upcomingCount }}</div>
            <div class="statsub mute small">confirmed, in the future</div>
          </Card>
          <Card class="statcard">
            <div class="statlabel">Active hosts</div>
            <div class="statvalue">{{ overview.byHost.length }}</div>
            <div class="statsub mute small">with bookings</div>
          </Card>
          <Card class="statcard">
            <div class="statlabel">Last 30 days</div>
            <div class="statvalue">{{ overview.last30Days.reduce((s, d) => s + d.count, 0) }}</div>
            <div class="statsub mute small">bookings created</div>
          </Card>
        </div>

        <div class="overgrid">
          <Card class="chartcard">
            <div class="cardhead">
              <Eyebrow>Bookings created · last 30 days</Eyebrow>
            </div>
            <div class="spark">
              <div
                v-for="d in overview.last30Days"
                :key="d.date"
                class="sparkbar"
                :style="{ height: barHeight(d.count) + '%' }"
                :title="`${dayLabel(d.date)}: ${d.count}`"
              />
            </div>
          </Card>

          <Card class="brkcard">
            <div class="cardhead"><Eyebrow>By status</Eyebrow></div>
            <p v-if="!statusBreakdown.length" class="mute small">No bookings yet.</p>
            <div v-else class="brklist">
              <div v-for="r in statusBreakdown" :key="r.status" class="brkrow">
                <Badge :tone="r.status === 'confirmed' ? 'ok' : r.status === 'cancelled' || r.status === 'calendar_failed' ? 'bad' : 'neutral'">{{ r.label }}</Badge>
                <span class="brkcount">{{ r.count }}</span>
              </div>
            </div>
          </Card>

          <Card class="brkcard">
            <div class="cardhead"><Eyebrow>By host</Eyebrow></div>
            <p v-if="!overview.byHost.length" class="mute small">No bookings yet.</p>
            <div v-else class="brklist">
              <div v-for="h in overview.byHost" :key="h.hostId" class="brkrow">
                <span class="brkname">{{ h.displayName }}</span>
                <span class="brkcount">{{ h.count }}</span>
              </div>
            </div>
          </Card>
        </div>
      </section>

      <Card v-if="!hosts || !hosts.length" class="notice">
        No bookable hosts yet. Add a host to create their booking pages — calendar access is provisioned automatically.
      </Card>

      <div v-else class="layout">
        <!-- Hosts list -->
        <aside class="hosts">
          <button
            v-for="h in hosts"
            :key="h._id"
            class="hostrow"
            :class="{ active: h._id === selectedHostId }"
            @click="selectHost(h._id)"
          >
            <div class="hname">{{ h.displayName }}</div>
            <div class="hmail">{{ h.email }}</div>
            <Badge :tone="h.isActive ? 'ok' : 'neutral'" dot>{{ h.isActive ? 'active' : 'inactive' }}</Badge>
          </button>
        </aside>

        <!-- Detail -->
        <section class="detail">
          <Card v-if="!selectedHost" class="notice">Select a host to manage their event types and availability.</Card>
          <template v-else>
            <Tabs v-model="detailTab" :items="detailTabs" />

            <!-- Event types -->
            <div v-if="detailTab === 'event-types'" class="pane">
              <div class="panehead">
                <span class="mute">{{ eventTypes.length }} event type(s)</span>
                <UiButton variant="secondary" :disabled="!availability.length" @click="openEt">New event type</UiButton>
              </div>
              <p v-if="!availability.length" class="hint">Create an availability schedule first.</p>
              <Card v-for="et in eventTypes" :key="et._id" class="item">
                <div class="itemmain">
                  <div class="ititle">
                    {{ et.title }} <span class="mute">· {{ et.durationMinutes }} min</span>
                    <Badge v-if="et.assignment === 'round_robin'" tone="neutral">round-robin</Badge>
                  </div>
                  <a class="link" :href="publicUrl(et)" target="_blank" rel="noopener">{{ publicUrl(et) }}</a>
                </div>
                <div class="itemactions">
                  <UiButton variant="ghost" @click="copyUrl(et)"><UiIcon name="copy" :size="14" /> Copy</UiButton>
                  <UiButton variant="ghost" @click="openEt(et)">Edit</UiButton>
                  <UiButton variant="ghost" @click="deleteEt(et)">Delete</UiButton>
                </div>
              </Card>
            </div>

            <!-- Availability -->
            <div v-else-if="detailTab === 'availability'" class="pane">
              <div class="panehead">
                <span class="mute">{{ availability.length }} schedule(s)</span>
                <UiButton variant="secondary" @click="openAvail">New schedule</UiButton>
              </div>
              <Card v-for="a in availability" :key="a._id" class="item">
                <div class="itemmain">
                  <div class="ititle">{{ a.name }} <span class="mute">· {{ a.timezone }}</span></div>
                  <div class="mute small">
                    {{ a.weeklyRules.map((r) => DAYS[r.dayOfWeek]).join(', ') || 'No days set' }}
                  </div>
                </div>
                <div class="itemactions">
                  <UiButton variant="ghost" @click="openAvail(a)">Edit</UiButton>
                  <UiButton variant="ghost" @click="deleteAvail(a)">Delete</UiButton>
                </div>
              </Card>
            </div>

            <!-- Bookings -->
            <div v-else class="pane">
              <Card v-if="!bookings.length" class="notice">No bookings yet.</Card>
              <Card v-for="b in bookings" :key="b._id" class="item">
                <div class="itemmain">
                  <div class="ititle">{{ b.attendeeName }} <span class="mute">· {{ b.attendeeEmail }}</span></div>
                  <div class="mute small">{{ fmtDateTime(b.startUtc) }} – {{ fmtDateTime(b.endUtc) }}</div>
                </div>
                <div class="itemactions">
                  <Badge :tone="bookingTone(b.status)" :title="b.lastCalendarError || undefined">{{ statusLabel[b.status] ?? b.status }}</Badge>
                  <UiButton v-if="b.status === 'confirmed'" variant="ghost" @click="openReschedule(b)">Reschedule</UiButton>
                  <UiButton v-if="b.status === 'confirmed'" variant="ghost" @click="cancelBookingAdmin(b)">Cancel</UiButton>
                  <UiButton
                    v-if="b.status === 'calendar_failed' || b.status === 'pending'"
                    variant="ghost"
                    :disabled="retryingId === b._id"
                    @click="retryBookingAdmin(b)"
                  >{{ retryingId === b._id ? 'Retrying…' : 'Retry now' }}</UiButton>
                </div>
              </Card>
            </div>
          </template>
        </section>
      </div>

      <!-- Webhooks (tenant-level) -->
      <section class="webhooks">
        <div class="whhead">
          <div>
            <Eyebrow>Integrations</Eyebrow>
            <h2 class="whtitle">Webhooks</h2>
            <p class="mute small">Receive signed POSTs when bookings are created, cancelled or rescheduled. Verify the HMAC-SHA256 signature in the <code>X-Dezky-Signature</code> header using the signing secret.</p>
          </div>
          <UiButton variant="secondary" @click="openWebhook()">
            <template #leading><UiIcon name="plus" :size="14" /></template>
            Add webhook
          </UiButton>
        </div>

        <Card v-if="webhooksLoaded && !webhooks.length" class="notice">
          No webhooks yet. Add one to forward booking lifecycle events to your own systems.
        </Card>

        <Card v-for="w in webhooks" :key="w._id" class="item">
          <div class="itemmain">
            <div class="ititle">
              {{ w.url }}
              <Badge :tone="w.active ? 'ok' : 'neutral'" dot>{{ w.active ? 'active' : 'paused' }}</Badge>
            </div>
            <div class="mute small whevents">
              <Badge v-for="ev in w.events" :key="ev" tone="neutral">{{ ev }}</Badge>
            </div>
            <div class="secretrow mute small">
              <span class="secretlabel">Signing secret</span>
              <code>{{ revealedSecrets[w._id] ? w.secret : maskSecret(w.secret) }}</code>
              <button class="linkbtn" @click="revealedSecrets[w._id] = !revealedSecrets[w._id]">
                {{ revealedSecrets[w._id] ? 'Hide' : 'Reveal' }}
              </button>
              <button class="linkbtn" @click="copySecret(w.secret)">Copy</button>
            </div>
          </div>
          <div class="itemactions">
            <UiButton variant="ghost" @click="openWebhook(w)">Edit</UiButton>
            <UiButton variant="ghost" @click="rotateWebhookSecret(w)">Rotate secret</UiButton>
            <UiButton variant="ghost" @click="deleteWebhook(w)">Delete</UiButton>
          </div>
        </Card>
      </section>
    </div>

    <!-- Webhook modal -->
    <Modal
      :open="whOpen"
      eyebrow="Integrations"
      :title="whEditingId ? 'Edit webhook' : 'Add webhook'"
      size="md"
      @close="whOpen = false"
    >
      <div class="form-stack">
        <label class="field"><Eyebrow>Endpoint URL</Eyebrow>
          <input class="input" v-model="whForm.url" placeholder="https://example.com/hooks/dezky" />
          <span class="slughint" :class="{ bad: !!whForm.url && !whUrlValid }">
            <template v-if="!whForm.url">We POST a signed JSON body here for each subscribed event.</template>
            <template v-else-if="!whUrlValid">Enter a valid http(s) URL.</template>
            <template v-else>Each delivery carries the <code>X-Dezky-Signature</code> HMAC header.</template>
          </span>
        </label>
        <div class="field"><Eyebrow>Events</Eyebrow>
          <label v-for="ev in ALL_WEBHOOK_EVENTS" :key="ev" class="checkrow">
            <input type="checkbox" :checked="whForm.events.includes(ev)" @change="toggleWhEvent(ev)" />
            <code>{{ ev }}</code>
          </label>
          <span class="slughint" :class="{ bad: !whForm.events.length }" v-if="!whForm.events.length">Select at least one event.</span>
        </div>
        <label v-if="whEditingId" class="checkrow">
          <input type="checkbox" v-model="whForm.active" />
          Active (deliver events)
        </label>
      </div>
      <template #footer>
        <UiButton variant="ghost" @click="whOpen = false">Cancel</UiButton>
        <UiButton variant="primary" :disabled="whBusy || !whUrlValid || !whForm.events.length" @click="submitWebhook">
          {{ whEditingId ? 'Save' : 'Create webhook' }}
        </UiButton>
      </template>
    </Modal>

    <!-- Add host modal -->
    <Modal :open="hostOpen" eyebrow="Scheduling" title="Add bookable host" size="md" @close="hostOpen = false">
      <div class="form-stack">
        <p v-if="!availableUsers.length" class="hint">
          No eligible users. A bookable host needs a workspace mailbox (and isn't already a host) — add a user with a mailbox under Users &amp; groups first.
        </p>
        <template v-else>
          <label class="field"><Eyebrow>User</Eyebrow>
            <select class="input" v-model="hostForm.userId" @change="onHostUserChange">
              <option v-for="u in availableUsers" :key="u._id" :value="u._id">{{ u.name }} — {{ u.mailboxAddress }}</option>
            </select>
            <span class="slughint">Calendar access is provisioned automatically — no connect step.</span>
          </label>
          <label class="field"><Eyebrow>URL slug</Eyebrow>
            <input class="input" v-model="hostForm.slug" placeholder="anne" />
            <span class="slughint" :class="{ bad: !!hostForm.slug && (slugTaken || !slugValid) }">
              <template v-if="!hostForm.slug">Used in the public booking link.</template>
              <template v-else-if="!slugValid">Lowercase letters, numbers and hyphens (2–40 chars).</template>
              <template v-else-if="slugTaken">“{{ hostForm.slug }}” is already taken.</template>
              <template v-else>{{ bookingHost }}/{{ slug }}/{{ hostForm.slug }} · available ✓</template>
            </span>
          </label>
          <label class="field"><Eyebrow>Timezone</Eyebrow>
            <select class="input" v-model="hostForm.timezone">
              <option>Europe/Copenhagen</option><option>Europe/London</option><option>Europe/Berlin</option><option>UTC</option>
            </select>
          </label>
        </template>
      </div>
      <template #footer>
        <UiButton variant="ghost" @click="hostOpen = false">Cancel</UiButton>
        <UiButton
          variant="primary"
          :disabled="hostBusy || !availableUsers.length || !hostForm.userId || slugTaken || !slugValid"
          @click="submitHost"
        >{{ hostBusy ? 'Provisioning…' : 'Create host' }}</UiButton>
      </template>
    </Modal>

    <!-- Availability modal -->
    <Modal :open="availOpen" eyebrow="Scheduling" :title="availEditingId ? 'Edit availability schedule' : 'New availability schedule'" size="md" @close="availOpen = false">
      <div class="form-stack">
        <label class="field"><Eyebrow>Name</Eyebrow><input class="input" v-model="availForm.name" /></label>
        <label class="field"><Eyebrow>Timezone</Eyebrow>
          <select class="input" v-model="availForm.timezone">
            <option>Europe/Copenhagen</option><option>Europe/London</option><option>Europe/Berlin</option><option>UTC</option>
          </select>
        </label>
        <div class="weekgrid">
          <div v-for="(d, i) in availForm.days" :key="i" class="weekrow">
            <label class="daytoggle"><input type="checkbox" v-model="d.enabled" /> {{ DAYS[i] }}</label>
            <input class="input time" type="time" v-model="d.start" :disabled="!d.enabled" />
            <span class="dash">–</span>
            <input class="input time" type="time" v-model="d.end" :disabled="!d.enabled" />
          </div>
        </div>
        <div class="overrides">
          <div class="overrides-head">
            <Eyebrow>Date overrides</Eyebrow>
            <UiButton variant="ghost" size="sm" @click="addOverride">Add override</UiButton>
          </div>
          <p class="mute small" v-if="!availForm.overrides.length">Block specific dates or set custom hours that replace the weekly rules.</p>
          <div v-for="(o, i) in availForm.overrides" :key="i" class="overrow">
            <input class="input date" type="date" v-model="o.date" />
            <label class="daytoggle"><input type="checkbox" v-model="o.isUnavailable" /> Unavailable</label>
            <input class="input time" type="time" v-model="o.start" :disabled="o.isUnavailable" />
            <span class="dash">–</span>
            <input class="input time" type="time" v-model="o.end" :disabled="o.isUnavailable" />
            <button type="button" class="removeov" aria-label="Remove override" @click="removeOverride(i)"><UiIcon name="x" /></button>
          </div>
        </div>
      </div>
      <template #footer>
        <UiButton variant="ghost" @click="availOpen = false">Cancel</UiButton>
        <UiButton variant="primary" :disabled="availBusy" @click="submitAvail">{{ availBusy ? 'Saving…' : 'Save' }}</UiButton>
      </template>
    </Modal>

    <!-- Event type modal -->
    <Modal :open="etOpen" eyebrow="Scheduling" :title="etEditingId ? 'Edit event type' : 'New event type'" size="md" @close="etOpen = false">
      <div class="form-stack">
        <label class="field"><Eyebrow>Title</Eyebrow><input class="input" v-model="etForm.title" @input="onEtTitle" placeholder="30-min consultation" /></label>
        <label class="field"><Eyebrow>URL slug</Eyebrow>
          <input class="input" v-model="etForm.slug" :disabled="!!etEditingId" @input="etSlugTouched = true" placeholder="consult" />
          <span class="slughint" :class="{ bad: !etEditingId && !!etForm.slug && !etSlugValid }">
            <template v-if="etEditingId">The slug can’t change after creation (it would break public links).</template>
            <template v-else-if="!etForm.slug">Part of the public booking link.</template>
            <template v-else-if="!etSlugValid">Lowercase letters, numbers and hyphens (2–40 chars).</template>
            <template v-else>/{{ selectedHost?.slug }}/{{ etForm.slug }}</template>
          </span>
        </label>
        <div class="row2">
          <label class="field"><Eyebrow>Duration (min)</Eyebrow><input class="input" type="number" v-model.number="etForm.durationMinutes" /></label>
          <label class="field"><Eyebrow>Slot step (min)</Eyebrow><input class="input" type="number" v-model.number="etForm.slotIntervalMinutes" /></label>
        </div>
        <div class="row2">
          <label class="field"><Eyebrow>Buffer before</Eyebrow><input class="input" type="number" v-model.number="etForm.bufferBeforeMinutes" /></label>
          <label class="field"><Eyebrow>Buffer after</Eyebrow><input class="input" type="number" v-model.number="etForm.bufferAfterMinutes" /></label>
        </div>
        <div class="row2">
          <label class="field"><Eyebrow>Min notice (min)</Eyebrow><input class="input" type="number" v-model.number="etForm.minimumNoticeMinutes" /></label>
          <label class="field"><Eyebrow>Horizon (days)</Eyebrow><input class="input" type="number" v-model.number="etForm.maximumDaysInFuture" /></label>
        </div>
        <label class="field"><Eyebrow>Availability schedule</Eyebrow>
          <select class="input" v-model="etForm.availabilityScheduleId">
            <option v-for="a in availability" :key="a._id" :value="a._id">{{ a.name }}</option>
          </select>
        </label>
        <label class="field"><Eyebrow>Location</Eyebrow>
          <select class="input" v-model="etForm.locationType">
            <option value="jitsi">dezky Meet (video)</option><option value="phone">Phone</option>
            <option value="in_person">In person</option><option value="custom">Custom</option>
          </select>
        </label>
        <label class="daytoggle">
          <input type="checkbox" v-model="etForm.ignoreAllDayEvents" />
          Ignore all-day events when checking availability
        </label>
        <label class="field"><Eyebrow>Host assignment</Eyebrow>
          <select class="input" v-model="etForm.assignment">
            <option value="single">Single host (this host)</option>
            <option value="round_robin">Round-robin (share across a team)</option>
          </select>
          <span class="slughint">
            <template v-if="etForm.assignment === 'round_robin'">
              Bookings are spread across the selected team. Visitors see the combined free time of everyone in the pool; each booking is assigned to a free team member.
            </template>
            <template v-else>Every booking goes to {{ selectedHost?.displayName ?? 'this host' }}.</template>
          </span>
        </label>
        <div v-if="etForm.assignment === 'round_robin'" class="field">
          <Eyebrow>Team pool</Eyebrow>
          <p class="slughint">{{ selectedHost?.displayName ?? 'This host' }} is always included. Add other hosts to share bookings with:</p>
          <div class="poollist">
            <label v-for="h in poolCandidates" :key="h._id" class="daytoggle">
              <input type="checkbox" :value="h._id" v-model="etForm.hostPool" />
              {{ h.displayName }} <span class="mute small">· {{ h.slug }}</span>
            </label>
            <p v-if="!poolCandidates.length" class="mute small">No other hosts in this tenant yet.</p>
          </div>
        </div>
      </div>
      <template #footer>
        <UiButton variant="ghost" @click="etOpen = false">Cancel</UiButton>
        <UiButton variant="primary" :disabled="etBusy || !etValid" @click="submitEt">{{ etBusy ? 'Saving…' : etEditingId ? 'Save changes' : 'Create' }}</UiButton>
      </template>
    </Modal>

    <!-- Reschedule modal -->
    <Modal :open="reschedule.open" eyebrow="Scheduling" title="Reschedule booking" size="md" @close="reschedule.open = false">
      <div class="form-stack">
        <p v-if="reschedule.booking" class="mute small">
          {{ reschedule.booking.attendeeName }} — currently {{ fmtDateTime(reschedule.booking.startUtc) }}.
          Pick a new time (shown in {{ reschedule.hostTz }}).
        </p>
        <p v-if="reschedule.state === 'loading'" class="mute">Loading times…</p>
        <p v-else-if="reschedule.state === 'error'" class="err">Couldn’t load available times.</p>
        <template v-else-if="reschedule.state === 'ready'">
          <p v-if="!reschedule.days.length" class="mute">No available times in the next 14 days.</p>
          <template v-else>
            <div class="days">
              <button
                v-for="d in reschedule.days"
                :key="d.key"
                class="day"
                :class="{ active: d.key === reschedule.selectedKey }"
                @click="reschedule.selectedKey = d.key"
              >
                <span class="dlabel">{{ d.label }}</span>
                <span class="dcount">{{ d.slots.length }}</span>
              </button>
            </div>
            <div v-if="reschedDay" class="slotgrid">
              <button
                v-for="s in reschedDay.slots"
                :key="s.startUtc"
                class="slotbtn"
                :disabled="reschedule.busy"
                @click="confirmReschedule(s.startUtc)"
              >{{ reschedTime(s.startUtc) }}</button>
            </div>
          </template>
        </template>
      </div>
      <template #footer>
        <UiButton variant="ghost" @click="reschedule.open = false">Close</UiButton>
      </template>
    </Modal>

    <ConfirmDialog
      :open="confirmBox.open"
      eyebrow="Scheduling"
      :title="confirmBox.title"
      :confirm-label="confirmBox.confirmLabel"
      :busy="confirmBox.busy"
      tone="danger"
      @close="confirmBox.open = false"
      @confirm="runConfirm"
    >
      {{ confirmBox.message }}
    </ConfirmDialog>
  </div>
</template>

<style scoped>
.content { padding: 20px 40px 64px; }
.notice { display: flex; align-items: center; gap: 10px; color: var(--text-mute); padding: 18px; }
.overview { display: flex; flex-direction: column; gap: 16px; margin-bottom: 24px; }
.statcards { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; }
.statcard { padding: 16px 18px; display: flex; flex-direction: column; gap: 4px; }
.statlabel { font-size: 12px; color: var(--text-mute); text-transform: uppercase; letter-spacing: 0.04em; }
.statvalue { font-size: 28px; font-weight: 700; line-height: 1.1; }
.statsub { margin-top: 2px; }
.overgrid { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 14px; }
.chartcard, .brkcard { padding: 16px 18px; display: flex; flex-direction: column; gap: 12px; }
.cardhead { display: flex; align-items: center; justify-content: space-between; }
.spark { display: flex; align-items: flex-end; gap: 2px; height: 96px; }
.sparkbar { flex: 1 1 0; min-width: 0; background: var(--text); opacity: 0.7; border-radius: 2px 2px 0 0; transition: opacity 0.15s; }
.sparkbar:hover { opacity: 1; }
.brklist { display: flex; flex-direction: column; gap: 8px; }
.brkrow { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.brkname { font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.brkcount { font-weight: 600; font-variant-numeric: tabular-nums; }
@media (max-width: 980px) {
  .statcards { grid-template-columns: repeat(2, 1fr); }
  .overgrid { grid-template-columns: 1fr; }
}
.layout { display: grid; grid-template-columns: 280px 1fr; gap: 18px; }
.hosts { display: flex; flex-direction: column; gap: 8px; }
.hostrow { text-align: left; background: var(--surface); border: 1px solid var(--border); border-radius: 10px; padding: 12px 14px; cursor: pointer; display: flex; flex-direction: column; gap: 4px; }
.hostrow.active { border-color: var(--text); box-shadow: inset 0 0 0 1px var(--text); }
.hname { font-weight: 600; }
.hmail { font-size: 12px; color: var(--text-mute); }
.detail { min-width: 0; }
.pane { margin-top: 14px; display: flex; flex-direction: column; gap: 10px; }
.panehead { display: flex; align-items: center; justify-content: space-between; }
.item { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; }
.itemmain { min-width: 0; }
.itemactions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.ititle { font-weight: 600; }
.link { font-size: 12px; color: var(--text-mute); word-break: break-all; }
.mute { color: var(--text-mute); }
.small { font-size: 12px; }
.hint { color: var(--warn, #9a6a00); font-size: 13px; }
.slughint { font-size: 12px; color: var(--text-mute); }
.slughint.bad { color: var(--bad, #c0362c); }
.err { color: var(--bad, #c0362c); font-size: 13px; }
.days { display: flex; gap: 8px; overflow-x: auto; padding-bottom: 6px; }
.day { flex: 0 0 auto; min-width: 78px; padding: 8px 10px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); cursor: pointer; display: flex; flex-direction: column; align-items: center; gap: 2px; }
.day.active { border-color: var(--text); box-shadow: inset 0 0 0 1px var(--text); }
.dlabel { font-size: 12px; }
.dcount { font-size: 11px; color: var(--text-mute); }
.slotgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); gap: 8px; margin-top: 12px; }
.slotbtn { height: 38px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); font-size: 14px; font-weight: 600; color: var(--text); cursor: pointer; }
.slotbtn:hover:not(:disabled) { border-color: var(--text); }
.slotbtn:disabled { opacity: .5; cursor: not-allowed; }
.form-stack { display: flex; flex-direction: column; gap: 14px; }
.field { display: flex; flex-direction: column; gap: 6px; }
.row2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.input { height: 36px; padding: 0 12px; background: var(--surface); border: 1px solid var(--border); border-radius: 6px; font-size: 13px; color: var(--text); }
.weekgrid { display: flex; flex-direction: column; gap: 8px; }
.weekrow { display: grid; grid-template-columns: 90px 1fr auto 1fr; align-items: center; gap: 8px; }
.daytoggle { display: flex; align-items: center; gap: 6px; font-size: 13px; }
.time { width: 100%; }
.dash { text-align: center; color: var(--text-mute); }
.overrides { display: flex; flex-direction: column; gap: 8px; border-top: 1px solid var(--border); padding-top: 12px; }
.overrides-head { display: flex; align-items: center; justify-content: space-between; }
.small { font-size: 12px; }
.overrow { display: grid; grid-template-columns: 150px auto 1fr auto 1fr auto; align-items: center; gap: 8px; }
.date { width: 100%; }
.removeov { display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border: 1px solid var(--border); border-radius: 6px; background: var(--surface); color: var(--text-mute); cursor: pointer; }
.removeov:hover { background: rgba(226, 48, 48, 0.08); color: var(--bad); }
.webhooks { margin-top: 32px; border-top: 1px solid var(--border); padding-top: 24px; display: flex; flex-direction: column; gap: 10px; }
.whhead { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; margin-bottom: 4px; }
.whtitle { font-size: 16px; font-weight: 600; margin: 4px 0 6px; }
.whhead p { max-width: 620px; }
.whevents { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.secretrow { display: flex; align-items: center; gap: 8px; margin-top: 8px; }
.secretlabel { font-weight: 600; }
.checkrow { display: flex; align-items: center; gap: 8px; font-size: 13px; }
.linkbtn { background: none; border: none; padding: 0; color: var(--text); font-size: 12px; text-decoration: underline; cursor: pointer; }
code { font-family: var(--font-mono, ui-monospace, monospace); font-size: 12px; }
</style>
