<script lang="ts" setup>
import { computed, onMounted, onUnmounted, reactive, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import dayjs from 'dayjs'
import { CircleAlert, CircleCheck, Download, Eye, Plus } from '@lucide/vue'
import {
  AlertRoot,
  AlertTitle,
  AlertDescription,
  AlertCloseTrigger,
} from '@/components/ui/alert'
import { Badge } from '@/components/ui/badge'
import { Box } from '@/components/ui/box'
import { Button } from '@/components/ui/button'
import { Field, FieldError, FieldLabel } from '@/components/ui/field'
import { Input } from '@/components/ui/input'
import {
  SelectRoot,
  SelectControl,
  SelectTrigger,
  SelectValueText,
  SelectContent,
  SelectItemGroup,
  SelectItemGroupLabel,
  SelectItem,
  SelectItemText,
} from '@/components/ui/select'
import { TabsRoot, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'
import { Textarea } from '@/components/ui/textarea'
import { usePermissions } from '@/composables/usePermissions'
import { getApiErrorMessage, getApiValidationErrors } from '@/core/utils/getApiErrorMessage'
import {
  downloadMembershipApplicationDocument,
  fetchMembershipApplicationDocument,
  getMembershipApplication,
  lookupMemberByIcNumber,
  updateMembershipApplication,
  uploadMembershipApplicationDocument,
} from '../services/membership-application.service'
import type {
  DocumentUploadType,
  MembershipApplicationDetail,
  MembershipApplicationDocumentDetail,
  MembershipApplicationFormState,
  MembershipApplicationReferenceDetail,
  MembershipApplicationReviewDetail,
  MembershipApplicationStatus,
} from '../types/membership-application.types'
import {
  DOCUMENT_TYPE_LABELS,
  DOCUMENT_UPLOAD_TYPES,
  GENDER_OPTIONS,
  MARRIAGE_STATUS_OPTIONS,
  RELATIONSHIP_OPTIONS,
  apiValueToLabel,
  buildUpdatePayload,
  createEmptyHeir,
  createEmptyFormState,
  createSelectCollection,
  detailToFormState,
  labelToApiValue,
} from '../utils/membership-application-form.utils'

const MAX_FILE_SIZE_MB = 10
const MAX_FILE_SIZE_BYTES = MAX_FILE_SIZE_MB * 1024 * 1024

const WORKFLOW_STEPS = [
  { id: 1, label: 'Dihantar' },
  { id: 2, label: 'Semakan Pentadbiran' },
  { id: 3, label: 'Semakan Lembaga' },
  { id: 4, label: 'Makluman Keputusan' },
  { id: 5, label: 'Selesai' },
] as const

const router = useRouter()
const route = useRoute()
const { hasPermission } = usePermissions()

const applicationId = computed(() => String(route.params.id ?? ''))
const loading = ref(false)
const saving = ref(false)
const error = ref<string | null>(null)
const successMessage = ref<string | null>(null)
const application = ref<MembershipApplicationDetail | null>(null)
const form = reactive<MembershipApplicationFormState>(createEmptyFormState())
const fieldErrors = reactive<Record<string, string>>({})
const downloadingDocumentId = ref<string | null>(null)
const uploadingDocumentType = ref<DocumentUploadType | null>(null)
const previewOpen = ref(false)
const previewLoading = ref(false)
const previewUrl = ref<string | null>(null)
const previewDocument = ref<MembershipApplicationDocumentDetail | null>(null)

const referenceLookupLoading = reactive({
  proposer: false,
  supporter: false,
})

const genderCollection = createSelectCollection(GENDER_OPTIONS)
const marriageStatusCollection = createSelectCollection(MARRIAGE_STATUS_OPTIONS)
const relationshipCollection = createSelectCollection(RELATIONSHIP_OPTIONS)

const canEdit = computed(
  () =>
    hasPermission('kemaskini permohonan keahlian') &&
    application.value?.status !== 'COMPLETED',
)

const genderInitial = computed(() => apiValueToLabel(GENDER_OPTIONS, form.applicant.gender))
const marriageStatusInitial = computed(() =>
  apiValueToLabel(MARRIAGE_STATUS_OPTIONS, form.applicant.marriage_status),
)

const workflowProgress = computed(() =>
  application.value ? getWorkflowProgress(application.value.status) : null,
)

const sortedReviews = computed(() => {
  if (!application.value?.reviews.length) return []

  return [...application.value.reviews].sort((left, right) => {
    const leftTime = left.reviewed_at ? new Date(left.reviewed_at).getTime() : 0
    const rightTime = right.reviewed_at ? new Date(right.reviewed_at).getTime() : 0
    return leftTime - rightTime
  })
})

const documentsByType = computed(() => {
  const map: Partial<Record<DocumentUploadType, MembershipApplicationDocumentDetail>> = {}

  application.value?.documents.forEach((document) => {
    if (DOCUMENT_UPLOAD_TYPES.includes(document.type as DocumentUploadType)) {
      map[document.type as DocumentUploadType] = document
    }
  })

  return map
})

function getWorkflowProgress(status: MembershipApplicationStatus) {
  switch (status) {
    case 'SUBMITTED':
      return { currentStep: 2, failed: false, failedStep: null as number | null }
    case 'MANAGEMENT_REJECTED':
      return { currentStep: 2, failed: true, failedStep: 2 }
    case 'PENDING_BOARD':
      return { currentStep: 3, failed: false, failedStep: null }
    case 'PENDING_NOTIFICATION':
      return { currentStep: 4, failed: false, failedStep: null }
    case 'COMPLETED':
      return { currentStep: 6, failed: false, failedStep: null }
    default:
      return { currentStep: 1, failed: false, failedStep: null }
  }
}

function statusLabel(status: MembershipApplicationStatus): string {
  const labels: Record<MembershipApplicationStatus, string> = {
    SUBMITTED: 'Dihantar',
    PENDING_BOARD: 'Menunggu Lembaga',
    MANAGEMENT_REJECTED: 'Ditolak Pentadbiran',
    PENDING_NOTIFICATION: 'Menunggu Makluman',
    COMPLETED: 'Selesai',
  }

  return labels[status] ?? status
}

function statusBadgeVariant(status: MembershipApplicationStatus) {
  if (status === 'COMPLETED') return 'success'
  if (status === 'MANAGEMENT_REJECTED') return 'danger'
  if (status === 'PENDING_BOARD' || status === 'PENDING_NOTIFICATION') return 'pending'
  return 'outline'
}

function workflowStepButtonClass(stepId: number) {
  const progress = workflowProgress.value
  if (!progress) return 'mx-2 size-12 rounded-full shadow-none bg-background border border-foreground/15'

  if (progress.failed && stepId === progress.failedStep) {
    return 'mx-2 size-12 rounded-full shadow-none bg-danger text-danger-foreground'
  }

  if (stepId < progress.currentStep) {
    return 'mx-2 size-12 rounded-full shadow-none bg-primary text-primary-foreground'
  }

  if (stepId === progress.currentStep && application.value?.status !== 'COMPLETED') {
    return 'mx-2 size-12 rounded-full shadow-none'
  }

  if (application.value?.status === 'COMPLETED' || stepId < progress.currentStep) {
    return 'mx-2 size-12 rounded-full shadow-none bg-primary text-primary-foreground'
  }

  return 'bg-background border border-foreground/15 shadow-none mx-2 size-12 rounded-full'
}

function workflowStepLabelClass(stepId: number) {
  const progress = workflowProgress.value
  const isCurrent =
    progress &&
    stepId === progress.currentStep &&
    application.value?.status !== 'COMPLETED' &&
    !progress.failed

  return isCurrent
    ? 'text-primary ml-3 font-medium opacity-100 lg:mx-auto lg:mt-3 lg:w-32'
    : 'ml-3 opacity-70 lg:mx-auto lg:mt-3 lg:w-32'
}

function displayValue(value: string | number | null | undefined): string {
  if (value === null || value === undefined || value === '') return '-'
  return String(value)
}

function formatDateTime(value: string | null | undefined): string {
  if (!value) return '-'
  return dayjs(value).format('DD/MM/YYYY HH:mm')
}

function formatFileSize(bytes: number | null | undefined): string {
  if (!bytes) return '-'
  if (bytes < 1024) return `${bytes} B`
  if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
  return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
}

function documentLabel(type: string, name: string): string {
  return DOCUMENT_TYPE_LABELS[type] ?? name
}

function documentAccept(type: DocumentUploadType): string {
  if (type === 'photo') return '.jpg,.jpeg,.png'
  return '.pdf,.jpg,.jpeg,.png'
}

function isImageMimeType(mimeType: string | null | undefined): boolean {
  return !!mimeType?.startsWith('image/')
}

function isPdfDocument(document: MembershipApplicationDocumentDetail): boolean {
  return document.mime_type === 'application/pdf' || document.name.toLowerCase().endsWith('.pdf')
}

const isPreviewImage = computed(() => isImageMimeType(previewDocument.value?.mime_type))
const isPreviewPdf = computed(() => (previewDocument.value ? isPdfDocument(previewDocument.value) : false))

function reviewStageLabel(stage: string): string {
  if (stage === 'MANAGEMENT') return 'Semakan Pentadbiran'
  if (stage === 'BOARD') return 'Semakan Lembaga'
  return stage
}

function reviewDecisionLabel(decision: string | null, stage: string): string {
  if (!decision) return '-'

  if (stage === 'MANAGEMENT') {
    if (decision === 'APPROVED') return 'Diluluskan'
    if (decision === 'REJECTED') return 'Ditolak'
  }

  if (stage === 'BOARD') {
    if (decision === 'PASS') return 'Lulus'
    if (decision === 'FAIL') return 'Gagal'
  }

  return decision
}

function reviewDecisionBadgeVariant(decision: string | null, stage: string) {
  if (stage === 'MANAGEMENT') {
    if (decision === 'APPROVED') return 'success'
    if (decision === 'REJECTED') return 'danger'
  }

  if (stage === 'BOARD') {
    if (decision === 'PASS') return 'success'
    if (decision === 'FAIL') return 'danger'
  }

  return 'outline'
}

function reviewTimelineDotClass(review: MembershipApplicationReviewDetail): string {
  const variant = reviewDecisionBadgeVariant(review.decision, review.stage)

  if (variant === 'success') return 'border-success bg-success'
  if (variant === 'danger') return 'border-danger bg-danger'
  return 'border-primary bg-primary'
}

function clearErrors() {
  Object.keys(fieldErrors).forEach((key) => delete fieldErrors[key])
}

function setFieldErrors(errors: Record<string, string[]>) {
  clearErrors()
  Object.entries(errors).forEach(([key, messages]) => {
    if (messages[0]) fieldErrors[key] = messages[0]
  })
}

function setGenderValue(details: { value: string[] }) {
  form.applicant.gender = labelToApiValue(GENDER_OPTIONS, details.value[0])
  delete fieldErrors['applicant.gender']
}

function setMarriageStatusValue(details: { value: string[] }) {
  form.applicant.marriage_status = labelToApiValue(MARRIAGE_STATUS_OPTIONS, details.value[0])
  delete fieldErrors['applicant.marriage_status']
}

function setHeirRelationshipValue(index: number, details: { value: string[] }) {
  const heir = form.heirs[index]
  if (!heir) return
  heir.relationship = labelToApiValue(RELATIONSHIP_OPTIONS, details.value[0])
  delete fieldErrors[`heirs.${index}.relationship`]
}

function clearReference(role: 'proposer' | 'supporter') {
  form.references[role].user_id = ''
  form.references[role].name = ''
  delete fieldErrors[`references.${role}_ic_number`]
}

function handleReferenceIcInput(role: 'proposer' | 'supporter') {
  clearReference(role)
}

async function lookupReference(role: 'proposer' | 'supporter') {
  const reference = form.references[role]
  const icNumber = reference.ic_number.trim()
  const fieldKey = role === 'proposer' ? 'references.proposer_ic_number' : 'references.supporter_ic_number'

  delete fieldErrors[fieldKey]

  if (!icNumber) {
    clearReference(role)
    return
  }

  if (icNumber === form.applicant.ic_number.trim()) {
    clearReference(role)
    fieldErrors[fieldKey] = 'Pencadang/penyokong tidak boleh sama dengan pemohon.'
    return
  }

  const otherRole = role === 'proposer' ? 'supporter' : 'proposer'
  if (icNumber === form.references[otherRole].ic_number.trim()) {
    clearReference(role)
    fieldErrors[fieldKey] = 'Pencadang dan penyokong mestilah ahli yang berbeza.'
    return
  }

  referenceLookupLoading[role] = true

  try {
    const response = await lookupMemberByIcNumber(icNumber)

    if (!response.success || !response.data) {
      clearReference(role)
      fieldErrors[fieldKey] = response.message || 'Ahli tidak dijumpai.'
      return
    }

    reference.user_id = response.data.id
    reference.name = response.data.name
    reference.ic_number = response.data.ic_number
  } catch (err) {
    clearReference(role)
    fieldErrors[fieldKey] = getApiErrorMessage(err, 'Gagal mencari ahli.')
  } finally {
    referenceLookupLoading[role] = false
  }
}

function addHeir() {
  form.heirs.push(createEmptyHeir())
}

function removeHeir(index: number) {
  if (form.heirs.length <= 1) return
  form.heirs.splice(index, 1)
}

function applyDetailToForm(detail: MembershipApplicationDetail) {
  const nextForm = detailToFormState(detail)
  Object.assign(form.applicant, nextForm.applicant)
  form.heirs.splice(0, form.heirs.length, ...nextForm.heirs)
  Object.assign(form.references.proposer, nextForm.references.proposer)
  Object.assign(form.references.supporter, nextForm.references.supporter)
}

function revokePreviewUrl() {
  if (previewUrl.value) {
    window.URL.revokeObjectURL(previewUrl.value)
    previewUrl.value = null
  }
}

function handlePreviewOpenChange(open: boolean) {
  previewOpen.value = open

  if (!open) {
    revokePreviewUrl()
    previewDocument.value = null
    previewLoading.value = false
  }
}

async function fetchApplication() {
  loading.value = true
  error.value = null

  try {
    const response = await getMembershipApplication(applicationId.value)
    application.value = response.data
    applyDetailToForm(response.data)
  } catch (err) {
    error.value = getApiErrorMessage(err, 'Gagal memuatkan permohonan.')
    application.value = null
  } finally {
    loading.value = false
  }
}

async function handleSave() {
  if (!application.value || !canEdit.value || saving.value) return

  saving.value = true
  error.value = null
  successMessage.value = null
  clearErrors()

  try {
    const response = await updateMembershipApplication(applicationId.value, buildUpdatePayload(form))
    application.value = response.data
    applyDetailToForm(response.data)
    successMessage.value = response.message || 'Permohonan keahlian berjaya dikemaskini.'
  } catch (err) {
    const validationErrors = getApiValidationErrors(err)
    if (validationErrors) {
      setFieldErrors(validationErrors)
    }
    error.value = getApiErrorMessage(err, 'Gagal mengemaskini permohonan.')
  } finally {
    saving.value = false
  }
}

async function handleDocumentUpload(type: DocumentUploadType, event: Event) {
  if (!application.value || !canEdit.value) return

  const input = event.target as HTMLInputElement
  const file = input.files?.[0]
  input.value = ''

  if (!file) return

  if (file.size > MAX_FILE_SIZE_BYTES) {
    fieldErrors[`documents.${type}`] = `Saiz fail melebihi ${MAX_FILE_SIZE_MB}MB.`
    return
  }

  delete fieldErrors[`documents.${type}`]
  uploadingDocumentType.value = type
  error.value = null
  successMessage.value = null

  try {
    const response = await uploadMembershipApplicationDocument(applicationId.value, type, file)
    application.value = response.data
    successMessage.value = response.message || 'Dokumen berjaya dimuat naik.'
  } catch (err) {
    const validationErrors = getApiValidationErrors(err)
    if (validationErrors) {
      setFieldErrors(validationErrors)
    }
    error.value = getApiErrorMessage(err, 'Gagal memuat naik dokumen.')
  } finally {
    uploadingDocumentType.value = null
  }
}

async function handleDownloadDocument(document: MembershipApplicationDocumentDetail) {
  if (!application.value || downloadingDocumentId.value) return

  downloadingDocumentId.value = document.id

  try {
    await downloadMembershipApplicationDocument(
      application.value.id,
      document.id,
      document.name,
      document.mime_type,
    )
  } catch (err) {
    error.value = getApiErrorMessage(err, 'Gagal memuat turun dokumen.')
  } finally {
    downloadingDocumentId.value = null
  }
}

async function handleViewDocument(document: MembershipApplicationDocumentDetail) {
  if (!application.value) return

  previewDocument.value = document
  previewOpen.value = true
  previewLoading.value = true
  revokePreviewUrl()

  try {
    const blob = await fetchMembershipApplicationDocument(
      application.value.id,
      document.id,
      document.mime_type,
    )
    previewUrl.value = window.URL.createObjectURL(blob)
  } catch (err) {
    handlePreviewOpenChange(false)
    error.value = getApiErrorMessage(err, 'Gagal memuatkan dokumen.')
  } finally {
    previewLoading.value = false
  }
}

onMounted(() => {
  fetchApplication()
})

onUnmounted(() => {
  revokePreviewUrl()
})
</script>

<template>
  <div class="w-full space-y-6">
    <div class="flex flex-wrap items-center gap-3">
      <div class="mr-auto">
        <h2 class="text-lg font-medium">Kemaskini Permohonan Keahlian</h2>
        <p v-if="application" class="mt-1 text-sm opacity-70">
          {{ application.application_number }} · {{ application.applicant?.name ?? '-' }}
        </p>
      </div>
      <Button
        look="outline"
        variant="secondary"
        type="button"
        @click="router.push({ name: 'view-membership-application', params: { id: applicationId } })"
      >
        Lihat
      </Button>
      <Button look="outline" variant="secondary" type="button"
        @click="router.push({ name: 'list-membership-applications' })">
        Kembali
      </Button>
    </div>

    <AlertRoot v-if="application && !canEdit" variant="warning">
      <AlertTitle>Tidak Boleh Dikemaskini</AlertTitle>
      <AlertDescription>
        Permohonan yang telah selesai tidak boleh dikemaskini.
      </AlertDescription>
    </AlertRoot>

    <AlertRoot v-if="error && !application" variant="danger">
      <CircleAlert />
      <AlertTitle>Ralat</AlertTitle>
      <AlertDescription>{{ error }}</AlertDescription>
      <AlertCloseTrigger @click="error = null" />
    </AlertRoot>

    <div v-if="loading" class="opacity-70">Memuatkan permohonan...</div>

    <template v-else-if="application">
      <Box class="p-5 sm:p-6">
        <div class="flex flex-wrap items-center justify-between gap-3">
          <div>
            <div class="text-sm opacity-70">No. Permohonan</div>
            <div class="text-xl font-semibold">{{ application.application_number }}</div>
          </div>
          <div class="flex flex-wrap items-center gap-2">
            <Badge :variant="statusBadgeVariant(application.status)" class="whitespace-nowrap">
              {{ statusLabel(application.status) }}
            </Badge>
            <Badge v-if="application.board_result"
              :variant="application.board_result === 'PASS' ? 'success' : 'danger'">
              Lembaga: {{ application.board_result === 'PASS' ? 'Lulus' : 'Gagal' }}
            </Badge>
          </div>
        </div>

        <div class="mt-4 grid gap-3 text-sm sm:grid-cols-2 lg:grid-cols-4">
          <div>
            <span class="opacity-70">Tarikh Hantar:</span>
            {{ formatDateTime(application.submitted_at) }}
          </div>
          <div>
            <span class="opacity-70">Tarikh Selesai:</span>
            {{ formatDateTime(application.completed_at) }}
          </div>
          <div>
            <span class="opacity-70">Emel Pemohon:</span>
            <span class="lowercase">{{ displayValue(application.applicant?.email) }}</span>
          </div>
          <div>
            <span class="opacity-70">No. IC:</span>
            {{ displayValue(application.applicant?.ic_number) }}
          </div>
        </div>
      </Box>

      <Box class="py-8 sm:py-10">
        <div class="px-5 sm:px-8">
          <div class="text-sm font-medium opacity-70">Status Permohonan</div>
        </div>
        <div
          class="before:bg-foreground/10 relative mt-4 flex flex-col justify-center px-5 before:absolute before:bottom-0 before:top-0 before:mt-6 before:hidden before:h-0.5 before:w-[69%] sm:px-8 lg:flex-row before:lg:block">
          <div v-for="step in WORKFLOW_STEPS" :key="step.id"
            class="z-10 flex flex-1 items-center lg:block lg:text-center">
            <Button type="button" :class="workflowStepButtonClass(step.id)"
              :variant="step.id === workflowProgress?.currentStep && application.status !== 'COMPLETED' && !workflowProgress?.failed ? 'default' : 'ghost'"
              disabled>
              {{ step.id }}
            </Button>
            <div :class="workflowStepLabelClass(step.id)">
              {{ step.label }}
            </div>
          </div>
        </div>
      </Box>

      <TabsRoot defaultValue="personal" class="w-full">
        <Box raised="single" class="w-full p-0">
          <div class="w-full px-5 py-4">
            <TabsList class="mb-0 flex w-full">
              <TabsTrigger class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm" value="personal">
                Maklumat Peribadi
              </TabsTrigger>
              <TabsTrigger class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm" value="contact">
                Hubungan & Alamat
              </TabsTrigger>
              <TabsTrigger class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm" value="employment">
                Pekerjaan & Caruman
              </TabsTrigger>
              <TabsTrigger class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm" value="heirs">
                Waris
              </TabsTrigger>
              <TabsTrigger class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm" value="references">
                Pencadang & Penyokong
              </TabsTrigger>
              <TabsTrigger class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm" value="documents">
                Dokumen
              </TabsTrigger>
              <TabsTrigger
                v-if="application.reviews.length"
                class="inline-flex min-w-0 flex-1 items-center justify-center whitespace-nowrap px-2 text-xs sm:px-3 sm:text-sm"
                value="reviews"
              >
                Sejarah Semakan
              </TabsTrigger>
            </TabsList>
          </div>
        </Box>

        <TabsContent value="personal" class="mt-6">
          <div class="grid grid-cols-12 gap-4 gap-y-5">
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="name">Nama Penuh</FieldLabel>
              <Input id="name" v-model="form.applicant.name" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.name']">{{ fieldErrors['applicant.name'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="email">Emel</FieldLabel>
              <Input id="email" v-model="form.applicant.email" type="email" class="lowercase" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.email']">{{ fieldErrors['applicant.email'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="ic_number">No. Kad Pengenalan</FieldLabel>
              <Input id="ic_number" v-model="form.applicant.ic_number" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.ic_number']">{{ fieldErrors['applicant.ic_number'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="birth_date">Tarikh Lahir</FieldLabel>
              <Input id="birth_date" v-model="form.applicant.birth_date" type="date" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.birth_date']">{{ fieldErrors['applicant.birth_date'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="birth_place">Tempat Lahir</FieldLabel>
              <Input id="birth_place" v-model="form.applicant.birth_place" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.birth_place']">{{ fieldErrors['applicant.birth_place'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel>Jantina</FieldLabel>
              <SelectRoot
                :key="`gender-${form.applicant.gender}`"
                class="w-full"
                :collection="genderCollection"
                :default-value="genderInitial"
                :disabled="!canEdit"
                @value-change="setGenderValue"
              >
                <SelectControl>
                  <SelectTrigger :aria-invalid="!!fieldErrors['applicant.gender']">
                    <SelectValueText placeholder="Pilih jantina" />
                  </SelectTrigger>
                </SelectControl>
                <SelectContent>
                  <SelectItemGroup>
                    <SelectItemGroupLabel>Jantina</SelectItemGroupLabel>
                    <SelectItem v-for="item in genderCollection.items" :key="item.label" :item="item">
                      <SelectItemText>{{ item.label }}</SelectItemText>
                    </SelectItem>
                  </SelectItemGroup>
                </SelectContent>
              </SelectRoot>
              <FieldError v-if="fieldErrors['applicant.gender']">{{ fieldErrors['applicant.gender'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel>Status Perkahwinan</FieldLabel>
              <SelectRoot
                :key="`marriage-${form.applicant.marriage_status}`"
                class="w-full"
                :collection="marriageStatusCollection"
                :default-value="marriageStatusInitial"
                :disabled="!canEdit"
                @value-change="setMarriageStatusValue"
              >
                <SelectControl>
                  <SelectTrigger :aria-invalid="!!fieldErrors['applicant.marriage_status']">
                    <SelectValueText placeholder="Pilih status" />
                  </SelectTrigger>
                </SelectControl>
                <SelectContent>
                  <SelectItemGroup>
                    <SelectItemGroupLabel>Status Perkahwinan</SelectItemGroupLabel>
                    <SelectItem v-for="item in marriageStatusCollection.items" :key="item.label" :item="item">
                      <SelectItemText>{{ item.label }}</SelectItemText>
                    </SelectItem>
                  </SelectItemGroup>
                </SelectContent>
              </SelectRoot>
              <FieldError v-if="fieldErrors['applicant.marriage_status']">{{ fieldErrors['applicant.marriage_status'] }}</FieldError>
            </Field>
          </div>
        </TabsContent>

        <TabsContent value="contact" class="mt-6">
          <div class="grid grid-cols-12 gap-4 gap-y-5">
            <Field class="col-span-12">
              <FieldLabel for="address">Alamat</FieldLabel>
              <Textarea id="address" v-model="form.applicant.address" rows="3" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.address']">{{ fieldErrors['applicant.address'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-4">
              <FieldLabel for="phone_number">No. Telefon</FieldLabel>
              <Input id="phone_number" v-model="form.applicant.phone_number" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.phone_number']">{{ fieldErrors['applicant.phone_number'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-4">
              <FieldLabel for="office_number">No. Pejabat</FieldLabel>
              <Input id="office_number" v-model="form.applicant.office_number" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.office_number']">{{ fieldErrors['applicant.office_number'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-4">
              <FieldLabel for="postcode">Poskod</FieldLabel>
              <Input id="postcode" v-model="form.applicant.postcode" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.postcode']">{{ fieldErrors['applicant.postcode'] }}</FieldError>
            </Field>
          </div>
        </TabsContent>

        <TabsContent value="employment" class="mt-6">
          <div class="grid grid-cols-12 gap-4 gap-y-5">
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="employer_name">Nama Majikan</FieldLabel>
              <Input id="employer_name" v-model="form.applicant.employer_name" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.employer_name']">{{ fieldErrors['applicant.employer_name'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="current_position">Jawatan Semasa</FieldLabel>
              <Input id="current_position" v-model="form.applicant.current_position" type="text" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.current_position']">{{ fieldErrors['applicant.current_position'] }}</FieldError>
            </Field>
            <Field class="col-span-12">
              <FieldLabel for="employer_address">Alamat Majikan</FieldLabel>
              <Textarea id="employer_address" v-model="form.applicant.employer_address" rows="3" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.employer_address']">{{ fieldErrors['applicant.employer_address'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-4">
              <FieldLabel for="start_work_date">Tarikh Mula Berkhidmat</FieldLabel>
              <Input id="start_work_date" v-model="form.applicant.start_work_date" type="date" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.start_work_date']">{{ fieldErrors['applicant.start_work_date'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-4">
              <FieldLabel for="stock_monthly_contribution">Caruman Saham (RM)</FieldLabel>
              <Input id="stock_monthly_contribution" v-model="form.applicant.stock_monthly_contribution" type="number"
                min="0" step="0.01" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.stock_monthly_contribution']">{{ fieldErrors['applicant.stock_monthly_contribution'] }}</FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-4">
              <FieldLabel for="fee_monthly_contribution">Caruman Yuran (RM)</FieldLabel>
              <Input id="fee_monthly_contribution" v-model="form.applicant.fee_monthly_contribution" type="number"
                min="0" step="0.01" :disabled="!canEdit" />
              <FieldError v-if="fieldErrors['applicant.fee_monthly_contribution']">{{ fieldErrors['applicant.fee_monthly_contribution'] }}</FieldError>
            </Field>
          </div>
        </TabsContent>

        <TabsContent value="heirs" class="mt-6">
          <div class="space-y-4">
            <div v-for="(heir, index) in form.heirs" :key="index"
              class="rounded-lg border border-foreground/10 p-4">
              <div class="mb-4 flex items-center justify-between">
                <div class="font-medium">Waris {{ index + 1 }}</div>
                <Button
                  v-if="canEdit && form.heirs.length > 1"
                  type="button"
                  look="outline"
                  size="sm"
                  @click="removeHeir(index)"
                >
                  Buang
                </Button>
              </div>
              <div class="grid grid-cols-12 gap-4">
                <Field class="col-span-12 sm:col-span-6">
                  <FieldLabel :for="`heir-name-${index}`">Nama</FieldLabel>
                  <Input :id="`heir-name-${index}`" v-model="heir.name" type="text" :disabled="!canEdit" />
                  <FieldError v-if="fieldErrors[`heirs.${index}.name`]">{{ fieldErrors[`heirs.${index}.name`] }}</FieldError>
                </Field>
                <Field class="col-span-12 sm:col-span-6">
                  <FieldLabel :for="`heir-ic-${index}`">No. Kad Pengenalan</FieldLabel>
                  <Input :id="`heir-ic-${index}`" v-model="heir.ic_number" type="text" :disabled="!canEdit" />
                  <FieldError v-if="fieldErrors[`heirs.${index}.ic_number`]">{{ fieldErrors[`heirs.${index}.ic_number`] }}</FieldError>
                </Field>
                <Field class="col-span-12 sm:col-span-6">
                  <FieldLabel>Hubungan</FieldLabel>
                  <SelectRoot
                    :key="`heir-relationship-${index}-${heir.relationship}`"
                    class="w-full"
                    :collection="relationshipCollection"
                    :default-value="apiValueToLabel(RELATIONSHIP_OPTIONS, heir.relationship)"
                    :disabled="!canEdit"
                    @value-change="(details) => setHeirRelationshipValue(index, details)"
                  >
                    <SelectControl>
                      <SelectTrigger :aria-invalid="!!fieldErrors[`heirs.${index}.relationship`]">
                        <SelectValueText placeholder="Pilih hubungan" />
                      </SelectTrigger>
                    </SelectControl>
                    <SelectContent>
                      <SelectItemGroup>
                        <SelectItemGroupLabel>Hubungan</SelectItemGroupLabel>
                        <SelectItem v-for="item in relationshipCollection.items" :key="item.label" :item="item">
                          <SelectItemText>{{ item.label }}</SelectItemText>
                        </SelectItem>
                      </SelectItemGroup>
                    </SelectContent>
                  </SelectRoot>
                  <FieldError v-if="fieldErrors[`heirs.${index}.relationship`]">{{ fieldErrors[`heirs.${index}.relationship`] }}</FieldError>
                </Field>
                <Field class="col-span-12 sm:col-span-6">
                  <FieldLabel :for="`heir-phone-${index}`">No. Telefon</FieldLabel>
                  <Input :id="`heir-phone-${index}`" v-model="heir.phone_number" type="text" :disabled="!canEdit" />
                  <FieldError v-if="fieldErrors[`heirs.${index}.phone_number`]">{{ fieldErrors[`heirs.${index}.phone_number`] }}</FieldError>
                </Field>
              </div>
            </div>
            <Button v-if="canEdit" type="button" look="outline" @click="addHeir">
              <Plus class="mr-2 size-4" />
              Tambah Waris
            </Button>
          </div>
        </TabsContent>

        <TabsContent value="references" class="mt-6">
          <div class="mb-4 rounded-lg border border-foreground/10 bg-foreground/5 p-4 text-sm opacity-80">
            Masukkan no. KP ahli sedia ada. Sistem akan mengesahkan dan mengisi nama secara automatik.
          </div>
          <div class="grid grid-cols-12 gap-4 gap-y-5">
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="proposer_ic_number">No. KP Pencadang</FieldLabel>
              <Input
                id="proposer_ic_number"
                v-model="form.references.proposer.ic_number"
                type="text"
                placeholder="Contoh: 900101011234"
                :disabled="!canEdit || referenceLookupLoading.proposer"
                @input="handleReferenceIcInput('proposer')"
                @blur="lookupReference('proposer')"
              />
              <p v-if="form.references.proposer.name" class="mt-1 text-sm text-success">
                {{ form.references.proposer.name }}
              </p>
              <FieldError v-if="fieldErrors['references.proposer_ic_number']">
                {{ fieldErrors['references.proposer_ic_number'] }}
              </FieldError>
            </Field>
            <Field class="col-span-12 sm:col-span-6">
              <FieldLabel for="supporter_ic_number">No. KP Penyokong</FieldLabel>
              <Input
                id="supporter_ic_number"
                v-model="form.references.supporter.ic_number"
                type="text"
                placeholder="Contoh: 850505055678"
                :disabled="!canEdit || referenceLookupLoading.supporter"
                @input="handleReferenceIcInput('supporter')"
                @blur="lookupReference('supporter')"
              />
              <p v-if="form.references.supporter.name" class="mt-1 text-sm text-success">
                {{ form.references.supporter.name }}
              </p>
              <FieldError v-if="fieldErrors['references.supporter_ic_number']">
                {{ fieldErrors['references.supporter_ic_number'] }}
              </FieldError>
            </Field>
          </div>
        </TabsContent>

        <TabsContent value="documents" class="mt-6">
          <div class="mb-4 rounded-lg border border-foreground/10 bg-foreground/5 p-4 text-sm opacity-80">
            Saiz maksimum setiap fail: <strong>{{ MAX_FILE_SIZE_MB }}MB</strong>.
            Muat naik fail baharu untuk menggantikan dokumen sedia ada.
          </div>
          <div class="space-y-4">
            <div
              v-for="type in DOCUMENT_UPLOAD_TYPES"
              :key="type"
              class="rounded-lg border border-foreground/10 p-4"
            >
              <div class="flex flex-wrap items-start justify-between gap-3">
                <div>
                  <div class="font-medium">{{ DOCUMENT_TYPE_LABELS[type] }}</div>
                  <div v-if="documentsByType[type]" class="mt-1 text-sm opacity-70">
                    {{ documentsByType[type]?.name }} · {{ formatFileSize(documentsByType[type]?.file_size) }}
                  </div>
                  <div v-else class="mt-1 text-sm opacity-70">Tiada dokumen dimuat naik.</div>
                </div>
                <div v-if="documentsByType[type]" class="flex flex-wrap items-center gap-2">
                  <Button
                    type="button"
                    look="outline"
                    size="sm"
                    :disabled="previewLoading && previewDocument?.id === documentsByType[type]?.id"
                    @click="documentsByType[type] && handleViewDocument(documentsByType[type]!)"
                  >
                    <Eye class="mr-2 size-4" />
                    Lihat
                  </Button>
                  <Button
                    type="button"
                    look="outline"
                    size="sm"
                    :disabled="downloadingDocumentId === documentsByType[type]?.id"
                    @click="documentsByType[type] && handleDownloadDocument(documentsByType[type]!)"
                  >
                    <Download class="mr-2 size-4" />
                    Muat Turun
                  </Button>
                </div>
              </div>
              <Field v-if="canEdit" class="mt-4">
                <FieldLabel :for="`document-${type}`">
                  {{ documentsByType[type] ? 'Ganti Dokumen' : 'Muat Naik Dokumen' }}
                </FieldLabel>
                <Input
                  :id="`document-${type}`"
                  type="file"
                  :accept="documentAccept(type)"
                  :disabled="uploadingDocumentType === type"
                  @change="handleDocumentUpload(type, $event)"
                />
                <FieldError v-if="fieldErrors[`documents.${type}`]">{{ fieldErrors[`documents.${type}`] }}</FieldError>
                <p v-if="uploadingDocumentType === type" class="mt-1 text-sm opacity-70">Memuat naik...</p>
              </Field>
            </div>
          </div>
        </TabsContent>

        <TabsContent v-if="application.reviews.length" value="reviews" class="mt-6">
          <div class="relative ms-3 ps-8">
            <div
              v-for="(review, index) in sortedReviews"
              :key="review.id"
              class="relative pb-8 last:pb-0"
            >
              <span
                class="absolute -start-8 top-1.5 flex size-3.5 rounded-full border-2 ring-4 ring-background"
                :class="reviewTimelineDotClass(review)"
              />
              <span
                v-if="index < sortedReviews.length - 1"
                class="absolute -start-[1.375rem] top-5 h-[calc(100%-0.25rem)] w-px bg-foreground/15"
              />

              <div class="rounded-lg border border-foreground/10 p-4">
                <div class="flex flex-wrap items-start justify-between gap-2">
                  <div>
                    <div class="font-medium">{{ reviewStageLabel(review.stage) }}</div>
                    <div class="mt-1 text-sm opacity-70">
                      {{ formatDateTime(review.reviewed_at) }}
                    </div>
                  </div>
                  <Badge
                    :variant="reviewDecisionBadgeVariant(review.decision, review.stage)"
                    class="whitespace-nowrap"
                  >
                    {{ reviewDecisionLabel(review.decision, review.stage) }}
                  </Badge>
                </div>

                <div class="mt-3 space-y-2 text-sm">
                  <div>
                    <span class="opacity-70">Disemak oleh:</span>
                    {{ displayValue(review.reviewer?.name) }}
                  </div>
                  <div v-if="review.remarks">
                    <span class="opacity-70">Catatan:</span>
                    {{ review.remarks }}
                  </div>
                </div>
              </div>
            </div>
          </div>
        </TabsContent>
      </TabsRoot>

      <div v-if="canEdit" class="space-y-3 pt-2">
        <AlertRoot v-if="successMessage" :key="successMessage" variant="success">
          <CircleCheck />
          <AlertTitle>Berjaya</AlertTitle>
          <AlertDescription>{{ successMessage }}</AlertDescription>
          <AlertCloseTrigger @click="successMessage = null" />
        </AlertRoot>

        <AlertRoot v-if="error" :key="error" variant="danger">
          <CircleAlert />
          <AlertTitle>Ralat</AlertTitle>
          <AlertDescription>{{ error }}</AlertDescription>
          <AlertCloseTrigger @click="error = null" />
        </AlertRoot>

        <div class="flex items-center justify-end gap-2">
          <Button type="button" :disabled="saving || loading" @click="handleSave">
            {{ saving ? 'Menyimpan...' : 'Simpan' }}
          </Button>
        </div>
      </div>
    </template>

    <Teleport to="body">
      <div v-if="previewOpen" class="fixed inset-0 z-70 flex items-center justify-center p-4 sm:p-6" role="dialog"
        aria-modal="true"
        :aria-label="previewDocument ? documentLabel(previewDocument.type, previewDocument.name) : 'Pratonton dokumen'">
        <button type="button" class="absolute inset-0 bg-black/80" aria-label="Tutup pratonton"
          @click="handlePreviewOpenChange(false)" />

        <div
          class="relative z-10 flex w-full max-w-5xl flex-col overflow-hidden rounded-2xl border border-foreground/10 bg-background shadow-2xl">
          <div class="border-b border-foreground/10 px-5 py-4">
            <div class="text-lg font-medium">
              {{ previewDocument ? documentLabel(previewDocument.type, previewDocument.name) : 'Pratonton Dokumen' }}
            </div>
            <div v-if="previewDocument" class="mt-1 text-sm opacity-70">
              {{ previewDocument.name }} · {{ formatFileSize(previewDocument.file_size) }}
            </div>
          </div>

          <div class="overflow-auto p-5">
            <div v-if="previewLoading" class="py-12 text-center opacity-70">
              Memuatkan dokumen...
            </div>

            <div v-else-if="previewUrl && isPreviewImage" class="flex justify-center">
              <img :src="previewUrl" :alt="previewDocument?.name ?? 'Pratonton dokumen'"
                class="block h-auto max-h-[calc(90vh-12rem)] w-auto max-w-full object-contain" />
            </div>

            <iframe v-else-if="previewUrl && isPreviewPdf" :src="previewUrl"
              class="block w-full rounded-lg border border-foreground/10" style="height: min(70vh, 720px)"
              :title="previewDocument?.name ?? 'Pratonton dokumen'" />

            <div v-else-if="previewUrl" class="py-12 text-center opacity-70">
              Pratonton tidak tersedia untuk jenis fail ini. Sila muat turun dokumen.
            </div>
          </div>

          <div class="flex justify-end gap-2 border-t border-foreground/10 px-5 py-4">
            <Button type="button" look="outline" @click="handlePreviewOpenChange(false)">
              Tutup
            </Button>
            <Button v-if="previewDocument" type="button" look="outline"
              :disabled="downloadingDocumentId === previewDocument.id" @click="handleDownloadDocument(previewDocument)">
              <Download class="mr-2 size-4" />
              Muat Turun
            </Button>
          </div>
        </div>
      </div>
    </Teleport>
  </div>
</template>
