<script setup lang="ts">
import {
  BellRing,
  Building2,
  CheckCircle2,
  ChevronRight,
  CircleAlert,
  Flame,
  HeartPulse,
  History,
  Info,
  LocateFixed,
  MapPinned,
  Megaphone,
  Plus,
  RadioTower,
  RefreshCw,
  ShieldAlert,
  ShieldCheck,
  Siren,
  TriangleAlert,
  Users,
} from 'lucide-vue-next'
import { computed, onMounted, ref } from 'vue'
import { useRoute } from 'vue-router'

import { defaultMapWorldToPercent } from '@/features/map/defaultMapGeometry'
import {
  CITYWARN_CATEGORIES,
  CITYWARN_SEVERITIES,
  useCityWarnStore,
} from '@/stores/citywarn'
import { usePhoneStore } from '@/stores/phone'
import type {
  CityWarnAlert,
  CityWarnAreaType,
  CityWarnCategory,
  CityWarnSeverity,
} from '@/types/citywarn'
import {
  SkyAppPage,
  SkyButton,
  SkyCard,
  SkyEmptyState,
  SkyField,
  SkyNavbar,
  SkyPillNavigation,
  SkyScrollArea,
  SkySegmented,
  SkySegmentedButton,
  SkySettingsGroup,
  SkySettingsRow,
  SkySheet,
  SkySpinner,
} from '@/ui'
import { nuiCall } from '@/utils/nui'

type CityWarnTab = 'active' | 'map' | 'archive' | 'settings'
type ManageMode = 'resolve' | 'update'

const route = useRoute()
const phone = usePhoneStore()
const citywarn = useCityWarnStore()
const tab = ref<CityWarnTab>('active')
const selected = ref<CityWarnAlert | null>(null)
const composeOpened = ref(false)
const composeStep = ref(1)
const publishing = ref(false)
const composeError = ref('')
const feedback = ref('')
const manageOpened = ref(false)
const manageMode = ref<ManageMode>('update')
const manageText = ref('')
const managing = ref(false)

const draftCategory = ref<CityWarnCategory>('public_safety')
const draftSeverity = ref<CityWarnSeverity>('warning')
const draftAreaType = ref<CityWarnAreaType>('radius')
const draftAreaLabel = ref('')
const draftCenterX = ref('')
const draftCenterY = ref('')
const draftRadius = ref('800')
const draftTitle = ref('')
const draftBody = ref('')
const draftInstructions = ref('')
const draftDuration = ref('60')
const locating = ref(false)

const severityRank: Record<CityWarnSeverity, number> = {
  danger: 2,
  extreme: 3,
  information: 0,
  warning: 1,
}

const categoryIcons = {
  evacuation: Users,
  fire: Flame,
  infrastructure: RadioTower,
  medical: HeartPulse,
  police: ShieldAlert,
  public_safety: Siren,
}

const tabs: Array<{ icon: typeof BellRing; id: CityWarnTab }> = [
  { icon: BellRing, id: 'active' },
  { icon: MapPinned, id: 'map' },
  { icon: History, id: 'archive' },
  { icon: ShieldCheck, id: 'settings' },
]

const availableCategories = computed(
  () => citywarn.context?.allowedCategories ?? CITYWARN_CATEGORIES,
)
const availableSeverities = computed(() => {
  const maximum = citywarn.context?.maximumSeverity
  if (!maximum) return CITYWARN_SEVERITIES
  return CITYWARN_SEVERITIES.filter(
    (severity) => severityRank[severity] <= severityRank[maximum],
  )
})
const composeValid = computed(() => {
  if (composeStep.value === 1) return true
  if (composeStep.value === 2) {
    if (!draftAreaLabel.value.trim()) return false
    if (draftAreaType.value !== 'radius') return true
    return (
      Number.isFinite(Number(draftCenterX.value)) &&
      Number.isFinite(Number(draftCenterY.value)) &&
      Number(draftRadius.value) >= 100
    )
  }
  if (composeStep.value === 3) {
    return Boolean(
      draftTitle.value.trim() &&
        draftBody.value.trim() &&
        Number(draftDuration.value) > 0,
    )
  }
  return true
})

function t(key: string, values?: Record<string, string>): string {
  const path = `Apps.citywarn.${key}`
  const value = phone.t(path, values)
  if (value === path && key.startsWith('errors.')) {
    return phone.t('Apps.citywarn.errors.default')
  }
  return value
}

function categoryLabel(category: CityWarnCategory): string {
  return t(`categories.${category}`)
}

function severityLabel(severity: CityWarnSeverity): string {
  return t(`severity.${severity}`)
}

function formatDate(value: number): string {
  return new Intl.DateTimeFormat(undefined, {
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
    month: 'short',
  }).format(value)
}

function formatRelative(value: number): string {
  const minutes = Math.max(0, Math.round((Date.now() - value) / 60000))
  if (minutes < 1) return t('updated')
  if (minutes < 60) return `${minutes} min`
  if (minutes < 1440) return `${Math.round(minutes / 60)} h`
  return formatDate(value)
}

function openAlert(alert: CityWarnAlert): void {
  selected.value = alert
}

function closeDetail(): void {
  selected.value = null
}

function resetDraft(): void {
  draftCategory.value = availableCategories.value[0] ?? 'public_safety'
  draftSeverity.value = availableSeverities.value.includes('warning')
    ? 'warning'
    : (availableSeverities.value[0] ?? 'information')
  draftAreaType.value = 'radius'
  draftAreaLabel.value = ''
  draftCenterX.value = ''
  draftCenterY.value = ''
  draftRadius.value = '800'
  draftTitle.value = ''
  draftBody.value = ''
  draftInstructions.value = ''
  draftDuration.value = '60'
  composeStep.value = 1
  composeError.value = ''
}

function openCompose(): void {
  resetDraft()
  composeOpened.value = true
}

async function useCurrentLocation(): Promise<void> {
  locating.value = true
  const response = await nuiCall<{
    coords?: { x: number; y: number; z?: number }
  }>('map:getPlayerCoords')
  locating.value = false
  if (!response.success || !response.data?.coords) return
  draftCenterX.value = response.data.coords.x.toFixed(1)
  draftCenterY.value = response.data.coords.y.toFixed(1)
  if (!draftAreaLabel.value) draftAreaLabel.value = t('currentLocation')
}

function nextComposeStep(): void {
  composeError.value = ''
  if (!composeValid.value) {
    composeError.value = t('errors.invalid_warning')
    return
  }
  composeStep.value = Math.min(4, composeStep.value + 1)
}

async function publishWarning(): Promise<void> {
  if (publishing.value) return
  publishing.value = true
  composeError.value = ''
  const response = await citywarn.publish({
    area: {
      centerX:
        draftAreaType.value === 'city' || !draftCenterX.value
          ? null
          : Number(draftCenterX.value),
      centerY:
        draftAreaType.value === 'city' || !draftCenterY.value
          ? null
          : Number(draftCenterY.value),
      label: draftAreaLabel.value.trim(),
      radius:
        draftAreaType.value === 'radius' ? Number(draftRadius.value) : null,
      type: draftAreaType.value,
    },
    body: draftBody.value.trim(),
    category: draftCategory.value,
    durationMinutes: Number(draftDuration.value),
    instructions: draftInstructions.value.trim(),
    severity: draftSeverity.value,
    title: draftTitle.value.trim(),
  })
  publishing.value = false
  if (!response.success || !response.data) {
    composeError.value = t(`errors.${response.error ?? 'request_failed'}`)
    return
  }
  composeOpened.value = false
  selected.value = response.data.alert
  feedback.value = t('compose.success')
}

function openManage(mode: ManageMode): void {
  manageMode.value = mode
  manageText.value = ''
  manageOpened.value = true
  composeError.value = ''
}

async function submitManage(): Promise<void> {
  const alert = selected.value
  if (!alert || !manageText.value.trim() || managing.value) return
  managing.value = true
  const response =
    manageMode.value === 'update'
      ? await citywarn.addUpdate(alert, manageText.value.trim())
      : await citywarn.resolve(alert, manageText.value.trim())
  managing.value = false
  if (!response.success || !response.data) {
    composeError.value = t(`errors.${response.error ?? 'request_failed'}`)
    return
  }
  selected.value = response.data.alert
  manageOpened.value = false
  feedback.value = manageMode.value === 'resolve' ? t('manage.resolved') : ''
}

function alertMapStyle(alert: CityWarnAlert): Record<string, string> {
  if (alert.area.type === 'city') return {}
  const percent = defaultMapWorldToPercent({
    x: alert.area.centerX ?? 0,
    y: alert.area.centerY ?? 0,
  })
  const size =
    alert.area.type === 'radius'
      ? Math.min(130, Math.max(42, (alert.area.radius ?? 500) / 11))
      : 88
  return {
    height: `${size}px`,
    left: `${percent.x * 100}%`,
    top: `${percent.y * 100}%`,
    width: `${size}px`,
  }
}

function setAreaType(type: CityWarnAreaType): void {
  draftAreaType.value = type
  if (type === 'city') {
    draftAreaLabel.value = 'Los Santos'
    draftCenterX.value = ''
    draftCenterY.value = ''
  }
}

onMounted(async () => {
  await citywarn.load()
  const alertId =
    typeof route.query.alertId === 'string' ? route.query.alertId : ''
  if (alertId) {
    selected.value =
      [...citywarn.active, ...citywarn.archive].find(
        (alert) => alert.id === alertId,
      ) ?? null
  }
})
</script>

<template>
  <SkyAppPage class="citywarn-app" accent="#dc2626" accent-soft="#fee2e2">
    <SkyNavbar
      v-if="selected"
      :title="t('details')"
      :show-back="true"
      :back-label="t('name')"
      outline
      @back="closeDetail"
    />
    <SkyNavbar v-else :title="t('name')" outline>
      <template #title>
        <span class="citywarn-brand"><Siren :size="17" /> CityWarn</span>
      </template>
      <template v-if="citywarn.context?.canPublish" #right>
        <SkyButton
          class="citywarn-add"
          icon-only
          clear
          :aria-label="t('compose.new')"
          @click="openCompose"
        >
          <Plus :size="22" />
        </SkyButton>
      </template>
    </SkyNavbar>

    <SkyScrollArea v-if="selected" class="citywarn-scroll citywarn-detail">
      <section
        class="citywarn-detail-hero"
        :class="`severity-${selected.severity}`"
      >
        <div class="citywarn-detail-icon">
          <component :is="categoryIcons[selected.category]" :size="28" />
        </div>
        <span class="citywarn-severity">{{
          severityLabel(selected.severity)
        }}</span>
        <h2>{{ selected.title }}</h2>
        <p>{{ selected.area.label }}</p>
      </section>

      <div class="citywarn-detail-content">
        <div class="citywarn-meta-grid">
          <div>
            <Megaphone :size="16" /><span>{{ t('issuedBy') }}</span
            ><strong>{{ selected.sourceLabel }}</strong>
          </div>
          <div>
            <MapPinned :size="16" /><span>{{ t('affectedArea') }}</span
            ><strong>{{ selected.area.label }}</strong>
          </div>
          <div>
            <RefreshCw :size="16" /><span>{{ t('updated') }}</span
            ><strong>{{ formatDate(selected.updatedAt) }}</strong>
          </div>
          <div>
            <History :size="16" /><span>{{ t('expires') }}</span
            ><strong>{{ formatDate(selected.expiresAt) }}</strong>
          </div>
        </div>

        <section class="citywarn-copy">
          <p>{{ selected.body }}</p>
        </section>

        <section v-if="selected.instructions" class="citywarn-instructions">
          <div>
            <CircleAlert :size="20" /><strong>{{ t('instructions') }}</strong>
          </div>
          <p>{{ selected.instructions }}</p>
        </section>

        <section class="citywarn-timeline">
          <h3>{{ t('timeline') }}</h3>
          <article v-for="update in selected.updates" :key="update.id">
            <span :class="`timeline-dot timeline-dot--${update.kind}`"></span>
            <div>
              <strong>{{ update.message }}</strong>
              <small
                >{{ update.actorName }} ·
                {{ formatDate(update.createdAt) }}</small
              >
            </div>
          </article>
        </section>

        <div
          v-if="citywarn.context?.canPublish && selected.status === 'active'"
          class="citywarn-manage"
        >
          <SkyButton block outline @click="openManage('update')">
            <RefreshCw :size="17" /> {{ t('manage.update') }}
          </SkyButton>
          <SkyButton block variant="danger" @click="openManage('resolve')">
            <CheckCircle2 :size="17" /> {{ t('manage.resolve') }}
          </SkyButton>
        </div>
      </div>
    </SkyScrollArea>

    <SkyScrollArea v-else class="citywarn-scroll" with-tabbar>
      <div
        v-if="citywarn.isLoading && !citywarn.initialized"
        class="citywarn-loading"
      >
        <SkySpinner :size="28" />
        <span>{{ t('loading') }}</span>
      </div>

      <SkyEmptyState
        v-else-if="citywarn.error"
        :title="t('errors.default')"
        :body="t(`errors.${citywarn.error}`)"
        tone="danger"
      >
        <template #actions>
          <SkyButton @click="citywarn.load()">{{ t('tabs.active') }}</SkyButton>
        </template>
      </SkyEmptyState>

      <template v-else-if="tab === 'active'">
        <section
          class="citywarn-overview"
          :class="{
            'citywarn-overview--active': citywarn.visibleActive.length,
          }"
        >
          <div class="citywarn-overview-symbol">
            <TriangleAlert v-if="citywarn.visibleActive.length" :size="27" />
            <ShieldCheck v-else :size="27" />
          </div>
          <div>
            <strong>
              {{
                citywarn.visibleActive.length
                  ? t('hero.active', {
                      count: String(citywarn.visibleActive.length),
                    })
                  : t('hero.safe')
              }}
            </strong>
            <p>
              {{
                citywarn.visibleActive.length
                  ? t('hero.activeBody')
                  : t('hero.safeBody')
              }}
            </p>
          </div>
        </section>

        <div class="citywarn-feed">
          <article
            v-for="alert in citywarn.visibleActive"
            :key="alert.id"
            class="citywarn-alert-card"
            :class="`severity-${alert.severity}`"
            tabindex="0"
            role="button"
            @click="openAlert(alert)"
            @keydown.enter="openAlert(alert)"
          >
            <div class="citywarn-card-stripe"></div>
            <div class="citywarn-card-top">
              <div class="citywarn-card-icon">
                <component :is="categoryIcons[alert.category]" :size="20" />
              </div>
              <div>
                <span>{{ severityLabel(alert.severity) }}</span>
                <small>{{ formatRelative(alert.updatedAt) }}</small>
              </div>
              <ChevronRight :size="19" />
            </div>
            <h3>{{ alert.title }}</h3>
            <p>{{ alert.body }}</p>
            <footer>
              <span><MapPinned :size="14" /> {{ alert.area.label }}</span>
              <span>{{ alert.sourceLabel }}</span>
            </footer>
          </article>

          <SkyEmptyState
            v-if="!citywarn.visibleActive.length && citywarn.active.length"
            :title="t('emptyFiltered')"
            :body="t('emptyFilteredBody')"
            compact
          />
        </div>

        <SkyCard
          v-if="citywarn.context"
          class="citywarn-publisher-card"
          outline
        >
          <div class="citywarn-publisher-icon"><Building2 :size="20" /></div>
          <div>
            <strong>{{ t('publisher.title') }}</strong>
            <p v-if="citywarn.context.canPublish">
              {{
                t('publisher.body', {
                  job: citywarn.context.jobLabel ?? '',
                  grade: citywarn.context.gradeLabel ?? '',
                })
              }}
            </p>
            <p
              v-else-if="
                citywarn.context.requiresDuty &&
                !citywarn.context.onDuty &&
                citywarn.context.jobLabel
              "
            >
              {{ t('publisher.offDuty') }}
            </p>
            <p v-else>{{ t('publisher.unavailable') }}</p>
          </div>
        </SkyCard>
      </template>

      <template v-else-if="tab === 'map'">
        <section class="citywarn-map-heading">
          <span><MapPinned :size="20" /></span>
          <div>
            <h2>{{ t('mapTitle') }}</h2>
            <p>{{ t('mapBody') }}</p>
          </div>
        </section>
        <div class="citywarn-map">
          <img src="/img/maps/gtav-map.svg" alt="" />
          <button
            v-for="alert in citywarn.visibleActive"
            :key="alert.id"
            class="citywarn-map-zone"
            :class="[
              `severity-${alert.severity}`,
              { 'citywarn-map-zone--city': alert.area.type === 'city' },
            ]"
            :style="alertMapStyle(alert)"
            :aria-label="alert.title"
            @click="openAlert(alert)"
          ></button>
        </div>
        <div class="citywarn-map-list">
          <button
            v-for="alert in citywarn.visibleActive"
            :key="alert.id"
            @click="openAlert(alert)"
          >
            <span :class="`map-list-dot severity-${alert.severity}`"></span>
            <div>
              <strong>{{ alert.area.label }}</strong
              ><small>{{ alert.title }}</small>
            </div>
            <ChevronRight :size="18" />
          </button>
        </div>
      </template>

      <template v-else-if="tab === 'archive'">
        <div class="citywarn-feed citywarn-feed--archive">
          <article
            v-for="alert in citywarn.archive"
            :key="alert.id"
            class="citywarn-archive-card"
            role="button"
            tabindex="0"
            @click="openAlert(alert)"
            @keydown.enter="openAlert(alert)"
          >
            <span :class="`archive-icon severity-${alert.severity}`"
              ><component :is="categoryIcons[alert.category]" :size="18"
            /></span>
            <div>
              <small
                >{{ t(`status.${alert.status}`) }} ·
                {{ formatDate(alert.updatedAt) }}</small
              ><strong>{{ alert.title }}</strong
              ><span>{{ alert.area.label }}</span>
            </div>
            <ChevronRight :size="18" />
          </article>
          <SkyEmptyState
            v-if="!citywarn.archive.length"
            :title="t('emptyArchive')"
            :body="t('emptyArchiveBody')"
          />
        </div>
      </template>

      <template v-else>
        <div class="citywarn-settings">
          <SkySettingsGroup
            :title="t('settings.locationTitle')"
            :footer="t('settings.locationBody')"
          >
            <SkySettingsRow
              kind="toggle"
              :title="t('settings.locationTitle')"
              :model-value="citywarn.preferences.locationAlerts"
              @update:model-value="citywarn.setLocationAlerts"
              ><template #leading><LocateFixed :size="20" /></template
            ></SkySettingsRow>
          </SkySettingsGroup>
          <SkySettingsGroup
            :title="t('settings.levelTitle')"
            :footer="t('settings.levelBody')"
          >
            <SkySettingsRow
              v-for="severity in CITYWARN_SEVERITIES"
              :key="severity"
              kind="choice"
              :selected="citywarn.preferences.minimumSeverity === severity"
              :title="severityLabel(severity)"
              @activate="citywarn.setMinimumSeverity(severity)"
              ><template #leading
                ><span
                  :class="`settings-dot severity-${severity}`"
                ></span></template
            ></SkySettingsRow>
          </SkySettingsGroup>
          <SkySettingsGroup
            :title="t('settings.categoryTitle')"
            :footer="t('settings.categoryBody')"
          >
            <SkySettingsRow
              v-for="category in CITYWARN_CATEGORIES"
              :key="category"
              kind="toggle"
              :title="categoryLabel(category)"
              :model-value="citywarn.preferences.categories[category]"
              @update:model-value="citywarn.setCategory(category, $event)"
              ><template #leading
                ><component
                  :is="categoryIcons[category]"
                  :size="19" /></template
            ></SkySettingsRow>
          </SkySettingsGroup>
          <p class="citywarn-settings-hint">
            <Info :size="16" /> {{ t('settings.notificationHint') }}
          </p>
        </div>
      </template>
    </SkyScrollArea>

    <SkyPillNavigation v-if="!selected" :label="t('navigation')" layout="full">
      <SkySegmented strong rounded navigation>
        <SkySegmentedButton
          v-for="item in tabs"
          :key="item.id"
          :active="tab === item.id"
          tab
          @click="tab = item.id"
        >
          <span class="citywarn-navigation-item">
            <component :is="item.icon" :size="16" />
            <span>{{ t(`tabs.${item.id}`) }}</span>
          </span>
        </SkySegmentedButton>
      </SkySegmented>
    </SkyPillNavigation>

    <Transition name="citywarn-toast">
      <div v-if="feedback" class="citywarn-feedback" @click="feedback = ''">
        <CheckCircle2 :size="17" /> {{ feedback }}
      </div>
    </Transition>

    <SkySheet
      class="citywarn-compose-sheet"
      :opened="composeOpened"
      swipe-to-close
      :aria-label="t('compose.new')"
      @backdropclick="composeOpened = false"
      @escape="composeOpened = false"
      @swipeclose="composeOpened = false"
    >
      <section class="citywarn-compose">
        <header>
          <span aria-hidden="true"></span>
          <div>
            <small>{{
              t('compose.step', { step: String(composeStep) })
            }}</small>
            <h2>{{ t('compose.new') }}</h2>
          </div>
          <span></span>
        </header>
        <div class="compose-progress">
          <i
            v-for="step in 4"
            :key="step"
            :class="{ active: step <= composeStep }"
          ></i>
        </div>

        <div class="compose-body">
          <template v-if="composeStep === 1">
            <h3>{{ t('compose.categoryTitle') }}</h3>
            <p>{{ t('compose.categoryBody') }}</p>
            <div class="compose-category-grid">
              <button
                v-for="category in availableCategories"
                :key="category"
                :class="{ active: draftCategory === category }"
                @click="draftCategory = category"
              >
                <component :is="categoryIcons[category]" :size="21" /><span>{{
                  categoryLabel(category)
                }}</span>
              </button>
            </div>
            <h3>{{ t('compose.severityTitle') }}</h3>
            <div class="compose-severity-list">
              <button
                v-for="severity in availableSeverities"
                :key="severity"
                :class="[
                  `severity-${severity}`,
                  { active: draftSeverity === severity },
                ]"
                @click="draftSeverity = severity"
              >
                <span></span>{{ severityLabel(severity)
                }}<CheckCircle2 v-if="draftSeverity === severity" :size="18" />
              </button>
            </div>
          </template>

          <template v-else-if="composeStep === 2">
            <h3>{{ t('compose.areaTitle') }}</h3>
            <p>{{ t('compose.areaBody') }}</p>
            <SkySegmented class="compose-area-tabs">
              <SkySegmentedButton
                :active="draftAreaType === 'radius'"
                @click="setAreaType('radius')"
                >{{ t('compose.areaTypes.radius') }}</SkySegmentedButton
              >
              <SkySegmentedButton
                :active="draftAreaType === 'district'"
                @click="setAreaType('district')"
                >{{ t('compose.areaTypes.district') }}</SkySegmentedButton
              >
              <SkySegmentedButton
                v-if="citywarn.context?.canCityWide"
                :active="draftAreaType === 'city'"
                @click="setAreaType('city')"
                >{{ t('compose.areaTypes.city') }}</SkySegmentedButton
              >
            </SkySegmented>
            <div class="compose-fields">
              <SkyField
                v-model="draftAreaLabel"
                component="div"
                outline
                :label="t('compose.areaLabel')"
                :placeholder="t('compose.areaPlaceholder')"
              />
              <template v-if="draftAreaType === 'radius'">
                <SkyField
                  v-model="draftRadius"
                  component="div"
                  outline
                  type="number"
                  min="100"
                  max="10000"
                  :label="t('compose.radius')"
                />
                <SkyButton
                  block
                  outline
                  :disabled="locating"
                  @click="useCurrentLocation"
                  ><LocateFixed :size="17" />
                  {{
                    locating
                      ? t('loading')
                      : draftCenterX
                        ? t('compose.locationSet')
                        : t('compose.useLocation')
                  }}</SkyButton
                >
              </template>
            </div>
          </template>

          <template v-else-if="composeStep === 3">
            <h3>{{ t('compose.contentTitle') }}</h3>
            <div class="compose-fields">
              <SkyField
                v-model="draftTitle"
                component="div"
                outline
                :maxlength="120"
                :label="t('compose.title')"
                :placeholder="t('compose.titlePlaceholder')"
              />
              <SkyField
                v-model="draftBody"
                component="div"
                outline
                type="textarea"
                :rows="5"
                :maxlength="2000"
                :label="t('compose.body')"
                :placeholder="t('compose.bodyPlaceholder')"
              />
              <SkyField
                v-model="draftInstructions"
                component="div"
                outline
                type="textarea"
                :rows="4"
                :maxlength="2000"
                :label="t('compose.instructions')"
                :placeholder="t('compose.instructionsPlaceholder')"
              />
              <SkyField
                v-model="draftDuration"
                component="div"
                outline
                type="select"
                dropdown
                :label="t('compose.duration')"
                :options="[
                  {
                    value: '30',
                    label: t('compose.durationMinutes', { count: '30' }),
                  },
                  {
                    value: '60',
                    label: t('compose.durationMinutes', { count: '60' }),
                  },
                  {
                    value: '180',
                    label: t('compose.durationMinutes', { count: '180' }),
                  },
                  {
                    value: '360',
                    label: t('compose.durationMinutes', { count: '360' }),
                  },
                ]"
              />
            </div>
          </template>

          <template v-else>
            <h3>{{ t('compose.previewTitle') }}</h3>
            <article
              class="compose-preview"
              :class="`severity-${draftSeverity}`"
            >
              <span
                >{{ severityLabel(draftSeverity) }} ·
                {{ categoryLabel(draftCategory) }}</span
              >
              <h4>{{ draftTitle }}</h4>
              <p>{{ draftBody }}</p>
              <footer><MapPinned :size="15" /> {{ draftAreaLabel }}</footer>
            </article>
            <div class="compose-recipient">
              <Users :size="22" />
              <div>
                <strong>{{
                  t('compose.recipients', {
                    count: String(citywarn.onlinePlayers),
                  })
                }}</strong>
                <p>{{ t('compose.legal') }}</p>
              </div>
            </div>
          </template>
          <p v-if="composeError" class="compose-error">{{ composeError }}</p>
        </div>

        <footer class="compose-actions">
          <SkyButton v-if="composeStep > 1" outline @click="composeStep -= 1">{{
            t('compose.back')
          }}</SkyButton>
          <SkyButton
            v-if="composeStep < 4"
            :block="composeStep === 1"
            :disabled="!composeValid"
            @click="nextComposeStep"
            >{{ t('compose.next') }}</SkyButton
          >
          <SkyButton
            v-else
            block
            variant="danger"
            :disabled="publishing"
            @click="publishWarning"
            ><SkySpinner v-if="publishing" :size="17" /><Siren
              v-else
              :size="17"
            />
            {{
              publishing ? t('compose.publishing') : t('compose.publish')
            }}</SkyButton
          >
        </footer>
      </section>
    </SkySheet>

    <SkySheet
      class="citywarn-manage-overlay"
      :opened="manageOpened"
      swipe-to-close
      :aria-label="t(`manage.${manageMode}Title`)"
      @backdropclick="manageOpened = false"
      @escape="manageOpened = false"
      @swipeclose="manageOpened = false"
    >
      <section class="citywarn-manage-sheet">
        <h2>{{ t(`manage.${manageMode}Title`) }}</h2>
        <SkyField
          v-model="manageText"
          component="div"
          outline
          type="textarea"
          :rows="5"
          :maxlength="2000"
          :placeholder="t(`manage.${manageMode}Placeholder`)"
        />
        <p v-if="composeError" class="compose-error">{{ composeError }}</p>
        <SkyButton
          block
          :variant="manageMode === 'resolve' ? 'danger' : 'primary'"
          :disabled="!manageText.trim() || managing"
          @click="submitManage"
        >
          <SkySpinner v-if="managing" :size="17" />
          {{ t(manageMode === 'resolve' ? 'manage.confirm' : 'manage.send') }}
        </SkyButton>
      </section>
    </SkySheet>
  </SkyAppPage>
</template>

<style scoped>
.citywarn-app {
  --citywarn-red: #dc2626;
  --citywarn-ink: #111827;
  color: var(--citywarn-ink);
  text-rendering: geometricprecision;
}
.citywarn-app,
.citywarn-app :deep(*),
.citywarn-app :deep(*::before),
.citywarn-app :deep(*::after) {
  -webkit-backdrop-filter: none !important;
  backdrop-filter: none !important;
  filter: none !important;
  will-change: auto !important;
}
.citywarn-app :deep(.sky-settings-row__frame),
.citywarn-app :deep(.sky-button),
.citywarn-app :deep(.sky-segmented-button),
.citywarn-alert-card,
.citywarn-archive-card {
  transform: none !important;
}
.citywarn-app :deep(.sky-navbar__blur),
.citywarn-app :deep(.sky-glass),
.citywarn-app :deep(.sky-glass-surface) {
  -webkit-backdrop-filter: none;
  backdrop-filter: none;
}
.citywarn-app :deep(.sky-pill-navigation .sky-segmented) {
  background: rgb(255 255 255 / 98%);
}
.citywarn-brand {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 800;
  letter-spacing: -0.3px;
}
.citywarn-brand svg {
  color: var(--citywarn-red);
}
.citywarn-add {
  width: 34px;
  height: 34px;
  color: var(--citywarn-red);
}
.citywarn-navigation-item {
  display: flex;
  align-items: center;
  flex-direction: column;
  gap: 3px;
  font-size: 9.5px;
  line-height: 1;
}
.citywarn-navigation-item > svg {
  width: 18px;
  height: 18px;
}
.citywarn-scroll {
  height: 100%;
  padding: 12px 13px calc(28px + env(safe-area-inset-bottom));
  overflow-y: auto;
}
.citywarn-scroll.sky-scroll-area--tabbar {
  padding-bottom: calc(var(--sky-safe-area-bottom) + 84px);
  scroll-padding-bottom: calc(var(--sky-safe-area-bottom) + 84px);
}
.citywarn-loading {
  display: grid;
  height: 58vh;
  place-content: center;
  justify-items: center;
  gap: 10px;
  color: #6b7280;
  font-size: 12px;
}
.citywarn-overview {
  display: flex;
  padding: 15px;
  align-items: center;
  gap: 12px;
  border: 1px solid #bbf7d0;
  border-radius: 18px;
  background: linear-gradient(135deg, #f0fdf4, #ecfdf5);
}
.citywarn-overview--active {
  border-color: #fecaca;
  background: linear-gradient(135deg, #fff7ed, #fef2f2);
}
.citywarn-overview-symbol {
  display: grid;
  width: 46px;
  height: 46px;
  flex: 0 0 auto;
  place-items: center;
  border-radius: 15px;
  color: #15803d;
  background: #dcfce7;
}
.citywarn-overview--active .citywarn-overview-symbol {
  color: #b91c1c;
  background: #fee2e2;
}
.citywarn-overview strong {
  font-size: 15px;
  font-weight: 800;
}
.citywarn-overview p {
  margin: 3px 0 0;
  color: #6b7280;
  font-size: 12px;
  line-height: 1.4;
}
.citywarn-feed {
  display: flex;
  margin-top: 12px;
  flex-direction: column;
  gap: 10px;
}
.citywarn-alert-card {
  position: relative;
  padding: 13px 13px 12px 17px;
  overflow: hidden;
  border: 1px solid #e5e7eb;
  border-radius: 17px;
  background: #fff;
  box-shadow: 0 4px 15px rgb(15 23 42 / 5%);
  cursor: pointer;
}
.citywarn-card-stripe {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  width: 4px;
  background: var(--severity);
}
.citywarn-card-top {
  display: grid;
  align-items: center;
  grid-template-columns: 32px 1fr auto;
  gap: 8px;
}
.citywarn-card-icon {
  display: grid;
  width: 32px;
  height: 32px;
  place-items: center;
  border-radius: 10px;
  color: var(--severity);
  background: color-mix(in srgb, var(--severity) 12%, white);
}
.citywarn-card-top div:nth-child(2) {
  display: flex;
  flex-direction: column;
}
.citywarn-card-top span {
  color: var(--severity);
  font-size: 10.5px;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.6px;
}
.citywarn-card-top small {
  margin-top: 1px;
  color: #9ca3af;
  font-size: 10px;
}
.citywarn-card-top > svg {
  color: #9ca3af;
}
.citywarn-alert-card h3 {
  margin: 10px 0 5px;
  font-size: 16px;
  line-height: 1.18;
}
.citywarn-alert-card > p {
  display: -webkit-box;
  margin: 0;
  overflow: hidden;
  color: #4b5563;
  font-size: 12.5px;
  line-height: 1.45;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
}
.citywarn-alert-card footer {
  display: flex;
  margin-top: 11px;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  color: #6b7280;
  font-size: 10.5px;
}
.citywarn-alert-card footer span {
  display: inline-flex;
  min-width: 0;
  align-items: center;
  gap: 4px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.citywarn-publisher-card {
  margin-top: 12px;
}
.citywarn-publisher-card :deep(.sky-card__content) {
  display: flex;
  padding: 12px;
  gap: 10px;
}
.citywarn-publisher-icon {
  display: grid;
  width: 36px;
  height: 36px;
  flex: 0 0 auto;
  place-items: center;
  border-radius: 11px;
  color: #1d4ed8;
  background: #dbeafe;
}
.citywarn-publisher-card strong {
  font-size: 13.5px;
}
.citywarn-publisher-card p {
  margin: 3px 0 0;
  color: #6b7280;
  font-size: 11px;
  line-height: 1.4;
}
.severity-information {
  --severity: #2563eb;
}
.severity-warning {
  --severity: #d97706;
}
.severity-danger {
  --severity: #dc2626;
}
.severity-extreme {
  --severity: #7f1d1d;
}
.citywarn-detail {
  padding: 0 0 calc(26px + env(safe-area-inset-bottom));
}
.citywarn-detail-hero {
  position: relative;
  padding: 24px 18px 22px;
  color: white;
  background: linear-gradient(
    145deg,
    var(--severity),
    color-mix(in srgb, var(--severity) 65%, #111827)
  );
}
.citywarn-detail-icon {
  display: grid;
  width: 49px;
  height: 49px;
  margin-bottom: 12px;
  place-items: center;
  border: 1px solid rgb(255 255 255 / 28%);
  border-radius: 15px;
  background: rgb(255 255 255 / 15%);
}
.citywarn-severity {
  font-size: 10.5px;
  font-weight: 850;
  text-transform: uppercase;
  letter-spacing: 1px;
}
.citywarn-detail-hero h2 {
  margin: 6px 0;
  font-size: 23px;
  line-height: 1.08;
  letter-spacing: -0.6px;
}
.citywarn-detail-hero p {
  margin: 0;
  color: rgb(255 255 255 / 76%);
  font-size: 12px;
}
.citywarn-detail-content {
  min-width: 0;
  padding: 13px;
}
.citywarn-meta-grid {
  display: grid;
  width: 100%;
  min-width: 0;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px;
}
.citywarn-meta-grid > div {
  display: grid;
  min-width: 0;
  padding: 10px;
  border: 1px solid #e5e7eb;
  border-radius: 13px;
  grid-template-columns: auto 1fr;
  column-gap: 6px;
  background: #fff;
}
.citywarn-meta-grid svg {
  color: var(--severity, #dc2626);
  grid-row: span 2;
}
.citywarn-meta-grid span {
  overflow: hidden;
  color: #9ca3af;
  font-size: 9.5px;
  text-overflow: ellipsis;
  text-transform: uppercase;
  letter-spacing: 0.4px;
  white-space: nowrap;
}
.citywarn-meta-grid strong {
  overflow: hidden;
  font-size: 11px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.citywarn-copy {
  padding: 16px 3px 8px;
}
.citywarn-copy p {
  margin: 0;
  color: #374151;
  font-size: 13px;
  line-height: 1.6;
}
.citywarn-instructions {
  margin-top: 8px;
  padding: 13px;
  border: 1px solid #fecaca;
  border-radius: 15px;
  background: #fef2f2;
}
.citywarn-instructions > div {
  display: flex;
  align-items: center;
  gap: 7px;
  color: #b91c1c;
  font-size: 13px;
}
.citywarn-instructions p {
  margin: 7px 0 0;
  color: #7f1d1d;
  font-size: 12.5px;
  line-height: 1.5;
}
.citywarn-timeline {
  margin-top: 18px;
}
.citywarn-timeline h3 {
  margin: 0 0 10px;
  font-size: 16px;
}
.citywarn-timeline article {
  position: relative;
  display: flex;
  min-height: 44px;
  margin-left: 7px;
  padding: 0 0 13px 19px;
  border-left: 1px solid #d1d5db;
}
.timeline-dot {
  position: absolute;
  top: 1px;
  left: -5px;
  width: 9px;
  height: 9px;
  border: 2px solid #fff;
  border-radius: 50%;
  background: #6b7280;
  box-shadow: 0 0 0 1px #9ca3af;
}
.timeline-dot--resolved {
  background: #16a34a;
}
.timeline-dot--update {
  background: #dc2626;
}
.citywarn-timeline article div {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.citywarn-timeline strong {
  font-size: 12.5px;
  line-height: 1.4;
}
.citywarn-timeline small {
  color: #9ca3af;
  font-size: 10px;
}
.citywarn-manage {
  display: grid;
  margin-top: 5px;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.citywarn-manage :deep(button) {
  min-height: 42px;
  gap: 5px;
  font-size: 12px;
}
.citywarn-map-heading {
  display: flex;
  padding: 4px 3px 12px;
  gap: 10px;
}
.citywarn-map-heading > span {
  display: grid;
  width: 38px;
  height: 38px;
  place-items: center;
  border-radius: 12px;
  color: #b91c1c;
  background: #fee2e2;
}
.citywarn-map-heading h2 {
  margin: 0;
  font-size: 18px;
}
.citywarn-map-heading p {
  margin: 2px 0 0;
  color: #6b7280;
  font-size: 11.5px;
  line-height: 1.4;
}
.citywarn-map {
  position: relative;
  height: 430px;
  overflow: hidden;
  border: 1px solid #d1d5db;
  border-radius: 18px;
  background: #dfe6dc;
}
.citywarn-map > img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.86;
}
.citywarn-map-zone {
  position: absolute;
  padding: 0;
  border: 2px solid color-mix(in srgb, var(--severity) 75%, white);
  border-radius: 50%;
  background: color-mix(in srgb, var(--severity) 26%, transparent);
  box-shadow: 0 0 0 5px color-mix(in srgb, var(--severity) 10%, transparent);
  transform: translate(-50%, -50%);
}
.citywarn-map-zone--city {
  inset: 8px;
  width: auto !important;
  height: auto !important;
  border-radius: 14px;
  transform: none;
}
.citywarn-map-list {
  margin-top: 10px;
  overflow: hidden;
  border: 1px solid #e5e7eb;
  border-radius: 15px;
  background: #fff;
}
.citywarn-map-list button {
  display: grid;
  width: 100%;
  min-height: 58px;
  padding: 13px;
  align-items: center;
  border: 0;
  border-bottom: 1px solid #f3f4f6;
  color: inherit;
  background: transparent;
  grid-template-columns: 9px 1fr auto;
  gap: 9px;
  text-align: left;
}
.citywarn-map-list button:last-child {
  border-bottom: 0;
}
.map-list-dot,
.settings-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--severity);
}
.citywarn-map-list div {
  display: flex;
  min-width: 0;
  flex-direction: column;
}
.citywarn-map-list strong {
  font-size: 12.5px;
}
.citywarn-map-list small {
  overflow: hidden;
  color: #6b7280;
  font-size: 10.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.citywarn-feed--archive {
  margin-top: 0;
}
.citywarn-archive-card {
  display: grid;
  min-height: 68px;
  padding: 13px;
  align-items: center;
  border: 1px solid #e5e7eb;
  border-radius: 15px;
  background: #fff;
  grid-template-columns: 36px 1fr auto;
  gap: 10px;
  cursor: pointer;
}
.archive-icon {
  display: grid;
  width: 36px;
  height: 36px;
  place-items: center;
  border-radius: 11px;
  color: var(--severity);
  background: color-mix(in srgb, var(--severity) 12%, white);
}
.citywarn-archive-card div {
  display: flex;
  min-width: 0;
  flex-direction: column;
}
.citywarn-archive-card small {
  color: #9ca3af;
  font-size: 9.5px;
  text-transform: uppercase;
}
.citywarn-archive-card strong {
  margin: 2px 0;
  overflow: hidden;
  font-size: 13.5px;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.citywarn-archive-card span {
  color: #6b7280;
  font-size: 10.5px;
}
.citywarn-settings {
  display: flex;
  flex-direction: column;
  gap: 15px;
}
.citywarn-settings-hint {
  display: flex;
  margin: 0 8px;
  align-items: flex-start;
  gap: 7px;
  color: #6b7280;
  font-size: 11px;
  line-height: 1.45;
}
.citywarn-feedback {
  position: absolute;
  z-index: 20;
  right: 14px;
  bottom: calc(18px + env(safe-area-inset-bottom));
  left: 14px;
  display: flex;
  padding: 11px 13px;
  align-items: center;
  border-radius: 13px;
  color: #fff;
  background: rgb(17 24 39 / 94%);
  box-shadow: 0 8px 24px rgb(0 0 0 / 24%);
  gap: 7px;
  font-size: 10px;
  font-weight: 700;
}
.citywarn-toast-enter-active,
.citywarn-toast-leave-active {
  transition: all 0.22s ease;
}
.citywarn-toast-enter-from,
.citywarn-toast-leave-to {
  opacity: 0;
  transform: translateY(12px);
}
.citywarn-compose {
  box-sizing: border-box;
  display: flex;
  width: 100%;
  height: calc(100% - 32px);
  min-height: 0;
  max-height: none;
  padding: 13px 14px calc(var(--sky-safe-area-bottom) + 10px);
  flex-direction: column;
  color: #111827;
}
.citywarn-compose-sheet :deep(.sky-sheet__panel) {
  height: 88%;
  max-height: 88%;
  overflow: hidden;
}
.citywarn-compose > header {
  display: grid;
  align-items: center;
  grid-template-columns: 34px 1fr 34px;
  text-align: center;
}
.citywarn-compose header small {
  color: #9ca3af;
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.6px;
}
.citywarn-compose h2 {
  margin: 1px 0 0;
  font-size: 18px;
}
.compose-progress {
  display: flex;
  margin: 12px 0 13px;
  gap: 5px;
}
.compose-progress i {
  height: 3px;
  flex: 1;
  border-radius: 4px;
  background: #e5e7eb;
}
.compose-progress i.active {
  background: #dc2626;
}
.compose-body {
  flex: 1;
  overflow-y: auto;
}
.compose-body h3 {
  margin: 4px 0 3px;
  font-size: 17px;
}
.compose-body > p {
  margin: 0 0 13px;
  color: #6b7280;
  font-size: 11.5px;
  line-height: 1.4;
}
.compose-category-grid {
  display: grid;
  margin: 10px 0 18px;
  grid-template-columns: 1fr 1fr;
  gap: 7px;
}
.compose-category-grid button {
  display: flex;
  min-height: 72px;
  padding: 10px;
  align-items: flex-start;
  flex-direction: column;
  justify-content: space-between;
  border: 1px solid #e5e7eb;
  border-radius: 13px;
  color: #374151;
  background: #fff;
  font-size: 11.5px;
  font-weight: 700;
  text-align: left;
}
.compose-category-grid button.active {
  border-color: #dc2626;
  color: #b91c1c;
  background: #fef2f2;
  box-shadow: inset 0 0 0 1px #dc2626;
}
.compose-severity-list {
  display: flex;
  margin-top: 9px;
  flex-direction: column;
  gap: 6px;
}
.compose-severity-list button {
  display: grid;
  padding: 10px;
  align-items: center;
  border: 1px solid #e5e7eb;
  border-radius: 12px;
  color: #374151;
  background: #fff;
  grid-template-columns: 9px 1fr auto;
  gap: 8px;
  font-size: 12px;
  font-weight: 700;
  text-align: left;
}
.compose-severity-list button > span {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--severity);
}
.compose-severity-list button.active {
  border-color: var(--severity);
  color: var(--severity);
  background: color-mix(in srgb, var(--severity) 7%, white);
}
.compose-area-tabs {
  margin: 13px 0;
}
.compose-area-tabs :deep(button) {
  font-size: 11px;
}
.compose-fields {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.compose-fields :deep(.sky-field) {
  margin: 0;
}
.compose-fields :deep(.sky-field__input) {
  font-size: 13px;
}
.compose-preview {
  padding: 15px;
  border: 1px solid color-mix(in srgb, var(--severity) 45%, #e5e7eb);
  border-radius: 16px;
  background: color-mix(in srgb, var(--severity) 6%, white);
}
.compose-preview > span {
  color: var(--severity);
  font-size: 10px;
  font-weight: 850;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.compose-preview h4 {
  margin: 7px 0 5px;
  font-size: 18px;
}
.compose-preview p {
  margin: 0;
  color: #4b5563;
  font-size: 12px;
  line-height: 1.45;
}
.compose-preview footer {
  display: flex;
  margin-top: 12px;
  align-items: center;
  gap: 5px;
  color: #6b7280;
  font-size: 10.5px;
}
.compose-recipient {
  display: flex;
  margin-top: 12px;
  padding: 12px;
  border-radius: 14px;
  color: #1e3a8a;
  background: #eff6ff;
  gap: 9px;
}
.compose-recipient strong {
  font-size: 12px;
}
.compose-recipient p {
  margin: 3px 0 0;
  color: #64748b;
  font-size: 10.5px;
  line-height: 1.4;
}
.compose-error {
  margin: 9px 0 0 !important;
  color: #dc2626 !important;
  font-size: 11px !important;
}
.compose-actions {
  display: flex;
  padding-top: 12px;
  gap: 8px;
}
.compose-actions :deep(button) {
  min-height: 42px;
  gap: 6px;
  font-size: 12.5px;
}
.compose-actions :deep(button:not(.sky-button--block)) {
  flex: 1;
}
.citywarn-manage-sheet {
  display: flex;
  min-height: 330px;
  padding: 20px 15px calc(var(--sky-safe-area-bottom) + 10px);
  flex-direction: column;
  color: #111827;
  gap: 13px;
}
.citywarn-manage-sheet h2 {
  margin: 0;
  font-size: 20px;
  text-align: center;
}
.citywarn-manage-sheet :deep(.sky-button) {
  margin-top: auto;
  min-height: 43px;
}
.sky-app-page--dark.citywarn-app {
  --citywarn-ink: #f9fafb;
}
.sky-app-page--dark.citywarn-app :deep(.sky-pill-navigation .sky-segmented) {
  background: rgb(23 25 29 / 98%);
}
.sky-app-page--dark .citywarn-alert-card,
.sky-app-page--dark .citywarn-archive-card,
.sky-app-page--dark .citywarn-map-list,
.sky-app-page--dark .citywarn-meta-grid > div {
  border-color: #30343b;
  background: #17191d;
}
.sky-app-page--dark .citywarn-alert-card > p,
.sky-app-page--dark .citywarn-copy p {
  color: #c7cbd1;
}
.sky-app-page--dark .citywarn-overview {
  border-color: #1f4931;
  background: #12251b;
}
.sky-app-page--dark .citywarn-overview--active {
  border-color: #542626;
  background: #2b1818;
}
.sky-app-page--dark .citywarn-instructions {
  border-color: #632c2c;
  background: #2d1717;
}
.sky-app-page--dark .citywarn-instructions p {
  color: #fecaca;
}
.sky-app-page--dark .citywarn-compose,
.sky-app-page--dark .citywarn-manage-sheet {
  color: #f9fafb;
  background: #111317;
}
.sky-app-page--dark .compose-category-grid button,
.sky-app-page--dark .compose-severity-list button,
.sky-app-page--dark .compose-preview {
  border-color: #343840;
  color: #e5e7eb;
  background: #1c1f24;
}
.sky-app-page--dark .compose-category-grid button.active,
.sky-app-page--dark .compose-severity-list button.active {
  border-color: var(--severity, #dc2626);
  background: #321a1a;
}
</style>
