import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useParams } from 'react-router-dom';
import {
  ActionIcon,
  Alert,
  Badge,
  Card,
  Checkbox,
  Container,
  Grid,
  Group,
  Modal,
  NumberInput,
  Paper,
  SegmentedControl,
  Skeleton,
  Stack,
  Table,
  Tabs,
  Text,
  Textarea,
  TextInput,
  ThemeIcon,
  Timeline,
  Title,
  Tooltip,
} from '@mantine/core';
import {
  IconAlertTriangle,
  IconCheck,
  IconLayoutSidebarRightCollapse,
  IconLayoutSidebarRightExpand,
  IconQuestionMark,
  IconX,
} from '@tabler/icons-react';
import { notifications } from '@mantine/notifications';
import { useTranslation } from 'react-i18next';
import {
  STATUS_COLORS,
  STATUS_LABELS,
  extractErrorMessage,
  useApproveDocumentsMutation,
  useAssignApplicationMutation,
  useCompleteReviewMutation,
  useConfirmPaymentMutation,
  useEscalateApplicationMutation,
  useFinalApproveMutation,
  useGetApplicationForReviewQuery,
  useGetAttachmentsQuery,
  useGetInspectionsQuery,
  useGetAssignableOfficersQuery,
  useGetLicenseTypeRequirementsQuery,
  useHoldApplicationMutation,
  useRecordInspectionResultMutation,
  useRejectApplicationMutation,
  useRequestAdjustmentMutation,
  useResumeApplicationMutation,
  useScheduleInspectionMutation,
  type RemarkTargetType,
} from '@ema-platform/api';
import { ErrorState, ModalFooter, AmharicDatePicker } from '@ema-platform/ui';
import { useDateDisplayer } from '@ema-platform/shared';
import { usePermissions } from '@ema-platform/auth';
import { useAppSelector } from '../../../store/hooks';
import { DecisionBar } from '../components/DecisionBar';
import {
  DecisionConfirmModal,
  type DecisionSubmission,
} from '../components/DecisionConfirmModal';
import { ActivityRail } from '../components/ActivityRail';
import { DocumentsTab } from '../components/DocumentsTab';
import { computeSla } from '../sla';
import { evaluateEligibility, presentationFor } from '../config/license-types';
import { resolveActions, type ActionId, type ResolvedAction } from '../config/actions';

type FlagMap = Record<string, { targetType: RemarkTargetType; remark: string }>;

/**
 * Standard site-visit checklist (US-LOG-016 / US-VES-007). The inspection
 * entity has carried a checklist column since day one; this is the first UI
 * that fills it in.
 */
const INSPECTION_CHECKLIST_ITEMS = [
  { key: 'office_premises', label: 'Office premises' },
  { key: 'storage_facilities', label: 'Warehouse / storage facilities' },
  { key: 'vehicles_equipment', label: 'Vehicles / equipment' },
  { key: 'safety_compliance', label: 'Safety & regulatory compliance' },
] as const;

function buildChecklist(
  outcomes: Record<string, 'PASS' | 'FAIL' | 'NEEDS_CORRECTION'>,
) {
  return INSPECTION_CHECKLIST_ITEMS.map((item) => ({
    key: item.key,
    label: item.label,
    // Untouched rows default to PASS — the segmented control shows exactly
    // that, so what the officer saw is what gets recorded.
    outcome: outcomes[item.key] ?? 'PASS',
  }));
}

/**
 * The officer's review workspace.
 *
 * Three zones: a sticky left rail carrying the summary and eligibility, a
 * centre column of tabs driven by the licence type's sections, and a
 * collapsible activity trail on the right. Every decision goes through the
 * Decision Bar pinned to the bottom, so an officer can act from any scroll
 * position instead of scrolling back to a column of buttons.
 */
export function LicenseReviewPage() {
  const { t, i18n } = useTranslation();
  const showDate = useDateDisplayer();
  const { id = '' } = useParams();
  const { can } = usePermissions();
  const currentUserId = useAppSelector((state) => state.auth.user?.id) ?? '';

  const { data, isLoading, isError, error, refetch } = useGetApplicationForReviewQuery(id, {
    skip: !id,
  });
  const { data: inspections = [], refetch: refetchInspections } = useGetInspectionsQuery(id, {
    skip: !id,
  });
  const { data: requirements } = useGetLicenseTypeRequirementsQuery(
    { idOrKey: data?.application.licenseTypeId ?? '', kind: data?.application.kind ?? 'NEW' },
    { skip: !data?.application.licenseTypeId },
  );

  const [completeReview] = useCompleteReviewMutation();
  const [requestAdjustment] = useRequestAdjustmentMutation();
  const [approveDocuments] = useApproveDocumentsMutation();
  const [finalApprove] = useFinalApproveMutation();
  const [rejectApplication] = useRejectApplicationMutation();
  const [scheduleInspection] = useScheduleInspectionMutation();
  const [recordResult] = useRecordInspectionResultMutation();
  const [confirmPayment] = useConfirmPaymentMutation();
  const [holdApplication] = useHoldApplicationMutation();
  const [resumeApplication] = useResumeApplicationMutation();
  const [escalateApplication] = useEscalateApplicationMutation();
  const [assignApplication] = useAssignApplicationMutation();
  // Real officer list, so Assign and Escalate name a person instead of
  // silently reassigning to whoever already held the application.
  const { data: officers = [] } = useGetAssignableOfficersQuery();

  const [flags, setFlags] = useState<FlagMap>({});
  const [capital, setCapital] = useState<number | undefined>();
  const [pendingAction, setPendingAction] = useState<ResolvedAction | null>(null);
  const [busyAction, setBusyAction] = useState<ActionId | null>(null);
  const [railOpen, setRailOpen] = useState(true);
  const [inspectionOpen, setInspectionOpen] = useState(false);
  const [inspectionDate, setInspectionDate] = useState('');
  const [resultOpen, setResultOpen] = useState(false);
  const [findings, setFindings] = useState('');
  const [checklist, setChecklist] = useState<
    Record<string, 'PASS' | 'FAIL' | 'NEEDS_CORRECTION'>
  >({});

  // Prefill from whatever is recorded, else the declared figure, so the officer
  // confirms a number rather than retyping it. Runs before the early return
  // below because hooks must be called unconditionally.
  const seeded = useRef(false);
  const loadedApp = data?.application;
  useEffect(() => {
    if (seeded.current || !loadedApp) return;
    const existing = loadedApp.capitalAmountVerified ?? loadedApp.capitalAmountDeclared;
    if (existing != null) setCapital(Number(existing));
    seeded.current = true;
  }, [loadedApp]);

  const toggleFlag = useCallback((targetType: RemarkTargetType, key: string) => {
    setFlags((prev) => {
      const next = { ...prev };
      if (next[key]) delete next[key];
      else next[key] = { targetType, remark: '' };
      return next;
    });
  }, []);

  const documentFlags = useMemo(() => {
    const map: Record<string, string> = {};
    for (const [key, flag] of Object.entries(flags)) {
      if (flag.targetType === 'DOCUMENT') map[key] = flag.remark;
    }
    return map;
  }, [flags]);

  const pendingInspection = inspections.find((i) => i.status === 'SCHEDULED');
  const flagged = Object.entries(flags);

  /**
   * The flagged items as the officer should see them in the confirmation.
   *
   * Only a document key reads acceptably on its own. A form section is
   * camelCase, and a staff flag is keyed by the ApplicationStaff id — showing
   * a uuid in the checklist would make the list impossible to tick with
   * confidence.
   */
  const flaggedItems = useMemo(
    () =>
      flagged.map(([key, flag]) => {
        if (flag.targetType === 'STAFF') {
          const member = data?.staff.find((s) => s.id === key);
          return {
            key,
            label: member
              ? `${member.roleKey} — ${member.fullName}`
              : t('review.staffMember', 'Staff member'),
          };
        }
        if (flag.targetType === 'FORM_SECTION') {
          return { key, label: key.replace(/([A-Z])/g, ' $1').trim() };
        }
        return { key, label: key };
      }),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [flags, data?.staff, t],
  );

  const actions = useMemo(() => {
    if (!data) return [];
    return resolveActions({
      detail: data,
      currentUserId,
      can,
      flaggedCount: flagged.length,
      hasPendingInspection: Boolean(pendingInspection),
      reasons: {
        wrongStatus: t('review.disabled.wrongStatus', 'Not available at this stage'),
        notAssigned: t('review.disabled.notAssigned', 'Assigned to another officer'),
        noPermission: t('review.disabled.noPermission', 'You do not have permission'),
        needsFlags: t('review.disabled.needsFlags', 'Flag at least one item to request a correction'),
        needsCapital: t('review.disabled.needsCapital', 'Record the verified capital first'),
        needsInspection: t('review.disabled.needsInspection', 'Requires an inspection result'),
      },
    });
  }, [data, currentUserId, can, flagged.length, pendingInspection, t]);

  if (isLoading) {
    // Skeleton mirrors the real three-zone layout so nothing jumps on load.
    return (
      <Container size="xl" py="md">
        <Skeleton height={36} width={320} mb="lg" />
        <Grid>
          <Grid.Col span={{ base: 12, md: 3 }}>
            <Skeleton height={280} radius="md" />
          </Grid.Col>
          <Grid.Col span={{ base: 12, md: 6 }}>
            <Skeleton height={420} radius="md" />
          </Grid.Col>
          <Grid.Col span={{ base: 12, md: 3 }}>
            <Skeleton height={280} radius="md" />
          </Grid.Col>
        </Grid>
      </Container>
    );
  }

  if (isError || !data) {
    return (
      <Container size="xl" py="md">
        <ErrorState
          title={t('review.errorTitle', 'Could not load this application')}
          description={extractErrorMessage(error)}
          onRetry={() => refetch()}
        />
      </Container>
    );
  }

  const app = data.application;
  const status = app.status;
  const presentation = presentationFor(app.licenseType?.key);
  const sla = computeSla(app, undefined, i18n.language);
  const eligibility = evaluateEligibility(app, app.licenseType, i18n.language);

  const rawThreshold = app.licenseType?.capitalThreshold;
  const threshold =
    rawThreshold === null || rawThreshold === undefined ? undefined : Number(rawThreshold);
  /** Stages where the officer can still record the verified capital. */
  const needsCapital =
    Boolean(threshold) &&
    ['UNDER_REVIEW', 'UNDER_EVALUATION', 'INSPECTION_PENDING', 'INSPECTION_COMPLETED'].includes(
      status,
    );

  async function run(action: () => Promise<unknown>, success: string) {
    try {
      await action();
      notifications.show({ color: 'teal', title: success, message: '' });
      refetch();
      refetchInspections();
    } catch (err) {
      notifications.show({
        color: 'red',
        title: t('review.actionFailed', 'Action failed'),
        message: extractErrorMessage(err),
      });
    }
  }

  /** Actions with their own dedicated form open that; the rest confirm. */
  function handleAction(action: ResolvedAction) {
    switch (action.id) {
      case 'schedule-inspection':
        setInspectionOpen(true);
        return;
      case 'record-inspection':
        setResultOpen(true);
        return;
      case 'copy-link':
        navigator.clipboard.writeText(window.location.href);
        notifications.show({
          color: 'teal',
          title: t('review.linkCopied', 'Link copied'),
          message: '',
        });
        return;
      case 'print':
        window.print();
        return;
      case 'audit-trail':
        setRailOpen(true);
        return;
      case 'download-documents':
        for (const attachment of data?.attachments ?? []) {
          const url = attachment.files?.[0]?.url;
          if (url) window.open(url, '_blank', 'noopener');
        }
        return;
      default:
        setPendingAction(action);
    }
  }

  async function submitDecision(submission: DecisionSubmission) {
    const action = pendingAction;
    if (!action) return;
    setBusyAction(action.id);
    try {
      switch (action.id) {
        case 'claim':
          // Claim is fired from the queue in practice; kept here for the case
          // where an officer opens an unclaimed application directly.
          break;
        case 'complete-review':
          await run(
            () => completeReview({ id, capitalAmountVerified: capital }).unwrap(),
            t('review.done.completeReview', 'Review completed'),
          );
          break;
        case 'approve-documents':
          await run(
            () => approveDocuments({ id }).unwrap(),
            t('review.done.approveDocuments', 'Documents approved'),
          );
          break;
        case 'final-approve':
          await run(
            () => finalApprove({ id, capitalAmountVerified: capital }).unwrap(),
            t('review.done.finalApprove', 'Approved'),
          );
          break;
        case 'request-adjustment': {
          const items = flagged
            // Only the ticked deficiencies are sent, so the applicant can
            // edit exactly the list they were shown.
            .filter(([key]) =>
              submission.deficiencies.length
                ? submission.deficiencies.includes(key)
                : true,
            )
            .map(([key, flag]) => ({
              targetType: flag.targetType,
              targetKey: key,
              remark: flag.remark.trim(),
            }));

          // Each item is an instruction the applicant has to act on, so the
          // API requires wording for every one. Catching it here names the
          // items that need it; letting it through produced a bare 400 saying
          // "items.0.remark should not be empty", which tells an officer
          // nothing about which box to go and fill in.
          const unexplained = items.filter((item) => !item.remark);
          if (items.length === 0 || unexplained.length > 0) {
            notifications.show({
              color: 'orange',
              title:
                items.length === 0
                  ? t(
                      'review.adjustment.nothingFlagged',
                      'Nothing has been flagged',
                    )
                  : t(
                      'review.adjustment.reasonsMissing',
                      'Every flagged item needs a reason',
                    ),
              message:
                items.length === 0
                  ? t(
                      'review.adjustment.nothingFlaggedHint',
                      'Tick what the applicant must correct before requesting an adjustment.',
                    )
                  : `${t(
                      'review.adjustment.reasonsMissingHint',
                      'Say what must be corrected for:',
                    )} ${unexplained.map((item) => item.targetKey).join(', ')}`,
            });
            // `finally` closes the modal and clears the busy flag.
            return;
          }

          await run(async () => {
            await requestAdjustment({
              id,
              generalRemark: submission.reason,
              notificationBody: submission.notificationBody,
              items,
            }).unwrap();
            setFlags({});
          }, t('review.done.requestAdjustment', 'Adjustment requested'));
          break;
        }
        case 'reject':
          await run(
            () =>
              rejectApplication({
                id,
                reason: submission.reason,
                // The preview the officer edited is what actually gets sent.
                notificationBody: submission.notificationBody,
              }).unwrap(),
            t('review.done.reject', 'Application rejected'),
          );
          break;
        case 'confirm-payment':
          await run(
            () => confirmPayment(id).unwrap(),
            t('review.done.confirmPayment', 'Payment confirmed'),
          );
          break;
        case 'hold':
          await run(
            () => holdApplication({ id, reason: submission.reason }).unwrap(),
            t('review.done.hold', 'Application placed on hold'),
          );
          break;
        case 'resume':
          await run(
            () => resumeApplication({ id, remark: submission.reason }).unwrap(),
            t('review.done.resume', 'Application resumed'),
          );
          break;
        case 'escalate':
          if (!submission.officerId) return;
          await run(
            () =>
              escalateApplication({
                id,
                supervisorId: submission.officerId as string,
                reason: submission.reason,
              }).unwrap(),
            t('review.done.escalate', 'Escalated'),
          );
          break;
        case 'assign':
          if (!submission.officerId) return;
          await run(
            () =>
              assignApplication({
                id,
                officerId: submission.officerId as string,
                remark: submission.reason,
              }).unwrap(),
            t('review.done.assign', 'Reassigned'),
          );
          break;
        default:
          break;
      }
    } finally {
      setBusyAction(null);
      setPendingAction(null);
    }
  }

  const sections = presentation.detailSections;
  const formSections = Object.entries(app.formData ?? {});

  return (
    <Container size="xl" py="md">
      <Group justify="space-between" mb="md">
        <div>
          <Title order={3}>{app.companyName ?? app.applicationNumber}</Title>
          <Group gap="xs">
            <Text size="sm" c="dimmed">
              {app.applicationNumber}
            </Text>
            <Badge color={STATUS_COLORS[status]} variant="light">
              {STATUS_LABELS[status]}
            </Badge>
            {app.adjustmentRound > 0 && (
              <Badge color="orange" variant="light" size="sm">
                {t('review.round', { count: app.adjustmentRound, defaultValue: 'round {{count}}' })}
              </Badge>
            )}
          </Group>
        </div>
        <Group gap="xs">
          <Tooltip
            label={
              railOpen
                ? t('review.hideActivity', 'Hide activity')
                : t('review.showActivity', 'Show activity')
            }
          >
            <ActionIcon variant="default" size="lg" onClick={() => setRailOpen((o) => !o)}>
              {railOpen ? (
                <IconLayoutSidebarRightCollapse size={18} />
              ) : (
                <IconLayoutSidebarRightExpand size={18} />
              )}
            </ActionIcon>
          </Tooltip>
        </Group>
      </Group>

      <Grid>
        {/* Zone 1 — sticky summary rail. */}
        <Grid.Col span={{ base: 12, md: 3 }}>
          <Stack style={{ position: 'sticky', top: 16 }}>
            <Paper withBorder p="md">
              <Text fw={600} size="sm" mb="sm">
                {t('review.summary', 'Summary')}
              </Text>
              <Stack gap={6}>
                <SummaryRow label={t('review.type', 'Type')} value={app.licenseType?.name?.en} />
                <SummaryRow label={t('review.tin', 'TIN')} value={app.tinNumber} />
                <SummaryRow label={t('review.kind', 'Kind')} value={app.kind} />
                <SummaryRow
                  label={t('review.submitted', 'Submitted')}
                  value={showDate(app.submittedAt)}
                />
                <SummaryRow label={t('review.slaLabel', 'SLA')} value={sla.label} />
              </Stack>
            </Paper>

            {/* Eligibility, checked and shown — not applied invisibly. */}
            {eligibility.length > 0 && (
              <Paper withBorder p="md">
                <Text fw={600} size="sm" mb="sm">
                  {t('review.eligibility', 'Eligibility')}
                </Text>
                <Stack gap="xs">
                  {eligibility.map((rule) => (
                    <Group key={rule.id} gap="xs" wrap="nowrap" align="flex-start">
                      <ThemeIcon
                        size={18}
                        radius="xl"
                        variant="light"
                        color={
                          rule.status === 'pass'
                            ? 'teal'
                            : rule.status === 'fail'
                              ? 'red'
                              : 'gray'
                        }
                      >
                        {rule.status === 'pass' ? (
                          <IconCheck size={11} />
                        ) : rule.status === 'fail' ? (
                          <IconX size={11} />
                        ) : (
                          <IconQuestionMark size={11} />
                        )}
                      </ThemeIcon>
                      <div style={{ minWidth: 0 }}>
                        <Text size="xs" fw={500}>
                          {rule.label}
                        </Text>
                        <Text size="xs" c="dimmed">
                          {rule.actual}
                        </Text>
                      </div>
                    </Group>
                  ))}
                </Stack>
              </Paper>
            )}

            <Paper withBorder p="md">
              <Text fw={600} size="sm" mb="sm">
                {t('review.statusTimeline', 'Progress')}
              </Text>
              <Timeline bulletSize={12} lineWidth={2} active={data.history.length}>
                {data.history.slice(-5).map((entry) => (
                  <Timeline.Item
                    key={entry.id}
                    title={
                      <Text size="xs" fw={600}>
                        {STATUS_LABELS[entry.toStatus] ?? entry.toStatus}
                      </Text>
                    }
                  >
                    <Text size="xs" c="dimmed">
                      {showDate(entry.createdAt)}
                    </Text>
                  </Timeline.Item>
                ))}
              </Timeline>
            </Paper>
          </Stack>
        </Grid.Col>

        {/* Zone 2 — the application itself. */}
        <Grid.Col span={{ base: 12, md: railOpen ? 6 : 9 }}>
          <Tabs defaultValue={sections[0]}>
            <Tabs.List mb="md">
              {/* Tabs with nothing behind them are not rendered at all. */}
              {sections.includes('overview') && formSections.length > 0 && (
                <Tabs.Tab value="overview">{t('review.tabs.overview', 'Overview')}</Tabs.Tab>
              )}
              {sections.includes('financials') && (
                <Tabs.Tab value="financials">{t('review.tabs.financials', 'Financials')}</Tabs.Tab>
              )}
              {sections.includes('documents') && (
                <Tabs.Tab value="documents">
                  {t('review.tabs.documents', 'Documents')} ({data.attachments.length})
                </Tabs.Tab>
              )}
              {sections.includes('staff') && data.staff.length > 0 && (
                <Tabs.Tab value="staff">
                  {t('review.tabs.staff', 'Staff')} ({data.staff.length})
                </Tabs.Tab>
              )}
              {sections.includes('inspection') && app.licenseType?.inspectionRequired && (
                <Tabs.Tab value="inspection">{t('review.tabs.inspection', 'Inspection')}</Tabs.Tab>
              )}
            </Tabs.List>

            <Tabs.Panel value="overview">
              <Stack>
                {formSections.map(([sectionKey, values]) => (
                  <Card withBorder key={sectionKey} padding="md">
                    <Group justify="space-between" mb="xs">
                      <Text fw={600} size="sm" tt="capitalize">
                        {sectionKey.replace(/([A-Z])/g, ' $1')}
                      </Text>
                      <Checkbox
                        size="xs"
                        label={t('review.needsCorrection', 'Needs correction')}
                        checked={Boolean(flags[sectionKey])}
                        onChange={() => toggleFlag('FORM_SECTION', sectionKey)}
                      />
                    </Group>
                    <Table withTableBorder>
                      <Table.Tbody>
                        {Object.entries(values ?? {}).map(([k, v]) => (
                          <Table.Tr key={k}>
                            <Table.Td w="40%">
                              <Text size="xs" c="dimmed">
                                {k}
                              </Text>
                            </Table.Td>
                            <Table.Td>
                              <Text size="sm">{v === null ? '—' : String(v)}</Text>
                            </Table.Td>
                          </Table.Tr>
                        ))}
                      </Table.Tbody>
                    </Table>
                    {flags[sectionKey] && (
                      <TextInput
                        mt="xs"
                        size="xs"
                        withAsterisk
                        placeholder={t(
                          'review.correctionPlaceholder',
                          'What must the applicant correct?',
                        )}
                        // Flagging without saying why is what the applicant
                        // would receive: "fix this section", and nothing else.
                        error={
                          flags[sectionKey].remark.trim()
                            ? null
                            : t(
                                'review.correctionRequired',
                                'Say what must be corrected',
                              )
                        }
                        value={flags[sectionKey].remark}
                        onChange={(e) => {
                          // Read here, not inside the updater: React nulls
                          // `currentTarget` when the handler returns, and the
                          // updater runs afterwards during the re-render —
                          // which crashed the page on the first keystroke.
                          const remark = e.currentTarget.value;
                          setFlags((p) => ({
                            ...p,
                            [sectionKey]: { ...p[sectionKey], remark },
                          }));
                        }}
                      />
                    )}
                  </Card>
                ))}
              </Stack>
            </Tabs.Panel>

            <Tabs.Panel value="financials">
              <Paper withBorder p="md">
                {needsCapital ? (
                  <>
                    <NumberInput
                      label={t('review.verifiedCapital', 'Verified capital (ETB)')}
                      description={
                        threshold
                          ? t('review.capitalHint', {
                              min: threshold.toLocaleString(i18n.language),
                              defaultValue:
                                'Minimum {{min}} — check against the bank letter',
                            })
                          : t('review.capitalHintNoMin', 'Checked against the bank letter')
                      }
                      value={capital ?? ''}
                      onChange={(v) => setCapital(Number(v) || undefined)}
                      thousandSeparator=","
                      error={
                        capital !== undefined && threshold && capital < threshold
                          ? t('review.belowMinimum', {
                              min: threshold.toLocaleString(i18n.language),
                              defaultValue: 'Below the {{min}} minimum',
                            })
                          : undefined
                      }
                    />
                    <Text size="xs" c="dimmed" mt="xs">
                      {t('review.declared', 'Applicant declared')}{' '}
                      {app.capitalAmountDeclared
                        ? Number(app.capitalAmountDeclared).toLocaleString(i18n.language)
                        : '—'}
                    </Text>
                  </>
                ) : (
                  <Text size="sm" c="dimmed">
                    {t('review.capitalLocked', 'Capital can no longer be edited at this stage.')}
                  </Text>
                )}
              </Paper>
            </Tabs.Panel>

            <Tabs.Panel value="documents">
              <DocumentsTab
                applicationId={id}
                attachments={data.attachments}
                requirements={requirements?.documentRequirements ?? []}
                flags={documentFlags}
                onToggleFlag={(key) => toggleFlag('DOCUMENT', key)}
                onFlagRemark={(key, remark) =>
                  setFlags((p) => ({ ...p, [key]: { ...p[key], remark } }))
                }
              />
            </Tabs.Panel>

            <Tabs.Panel value="staff">
              <Card withBorder padding="md">
                <Table withTableBorder>
                  <Table.Thead>
                    <Table.Tr>
                      <Table.Th>{t('review.role', 'Role')}</Table.Th>
                      <Table.Th>{t('review.name', 'Name')}</Table.Th>
                      <Table.Th>{t('review.evidence', 'Evidence')}</Table.Th>
                      <Table.Th w={260}>
                        {t('review.correction', 'Correction')}
                      </Table.Th>
                    </Table.Tr>
                  </Table.Thead>
                  <Table.Tbody>
                    {data.staff.map((member) => (
                      <Table.Tr key={member.id}>
                        <Table.Td>
                          <Text size="xs">{member.roleKey}</Text>
                        </Table.Td>
                        <Table.Td>
                          <Text size="sm">{member.fullName}</Text>
                        </Table.Td>
                        <Table.Td>
                          <StaffEvidenceCell staffId={member.id} fallback={member.documents} />
                        </Table.Td>
                        {/* A person's papers are as returnable as a document
                            or a form section: an ERB certificate for the wrong
                            person is a defect the applicant has to fix, and
                            until now the officer had to describe it under some
                            unrelated document. */}
                        <Table.Td>
                          <Checkbox
                            size="xs"
                            label={t('review.needsCorrection', 'Needs correction')}
                            checked={Boolean(flags[member.id])}
                            onChange={() => toggleFlag('STAFF', member.id)}
                          />
                          {flags[member.id] && (
                            <TextInput
                              mt={6}
                              size="xs"
                              withAsterisk
                              placeholder={t(
                                'review.correctionPlaceholder',
                                'What must the applicant correct?',
                              )}
                              error={
                                flags[member.id].remark.trim()
                                  ? null
                                  : t(
                                      'review.correctionRequired',
                                      'Say what must be corrected',
                                    )
                              }
                              value={flags[member.id].remark}
                              onChange={(e) => {
                                const remark = e.currentTarget.value;
                                setFlags((p) => ({
                                  ...p,
                                  [member.id]: { ...p[member.id], remark },
                                }));
                              }}
                            />
                          )}
                        </Table.Td>
                      </Table.Tr>
                    ))}
                  </Table.Tbody>
                </Table>
              </Card>
            </Tabs.Panel>

            <Tabs.Panel value="inspection">
              <Paper withBorder p="md">
                {inspections.length === 0 ? (
                  <Text size="sm" c="dimmed">
                    {t('review.noInspections', 'No inspection has been scheduled yet.')}
                  </Text>
                ) : (
                  <Stack gap="xs">
                    {inspections.map((inspection) => (
                      <Group key={inspection.id} justify="space-between">
                        <div>
                          <Text size="sm">
                            {inspection.scheduledDate
                              ? showDate(inspection.scheduledDate)
                              : t('review.unscheduled', 'Not scheduled')}
                          </Text>
                          {inspection.findings && (
                            <Text size="xs" c="dimmed">
                              {inspection.findings}
                            </Text>
                          )}
                        </div>
                        <Badge
                          variant="light"
                          color={inspection.result === 'FAILED' ? 'red' : 'teal'}
                        >
                          {inspection.result ?? inspection.status}
                        </Badge>
                      </Group>
                    ))}
                  </Stack>
                )}
              </Paper>
            </Tabs.Panel>
          </Tabs>

          {status === 'PAYMENT_PENDING' && (
            <Alert mt="md" color="yellow" icon={<IconAlertTriangle size={16} />}>
              {t('review.awaitingPayment', {
                amount: app.feeAmount,
                currency: app.feeCurrency,
                defaultValue: 'Waiting for the applicant to pay {{amount}} {{currency}}.',
              })}
            </Alert>
          )}
        </Grid.Col>

        {/* Zone 3 — activity and audit trail. */}
        {railOpen && (
          <Grid.Col span={{ base: 12, md: 3 }}>
            <ActivityRail detail={data} />
          </Grid.Col>
        )}
      </Grid>

      <DecisionBar
        status={status}
        assigneeName={app.assignedOfficerId ? t('review.assigned', 'Assigned') : null}
        sla={sla}
        actions={actions}
        busyAction={busyAction}
        onAction={handleAction}
      />

      <DecisionConfirmModal
        action={pendingAction}
        applicantName={app.companyName ?? t('review.theApplicant', 'the applicant')}
        applicationNumber={app.applicationNumber}
        flaggedItems={flaggedItems}
        officers={officers}
        submitting={Boolean(busyAction)}
        onClose={() => setPendingAction(null)}
        onConfirm={submitDecision}
      />

      <Modal
        opened={inspectionOpen}
        onClose={() => setInspectionOpen(false)}
        title={t('review.actions.scheduleInspection', 'Schedule inspection')}
      >
        <Stack>
          <AmharicDatePicker
            label={t('review.dateTime', 'Date and time')}
            value={inspectionDate}
            onChange={setInspectionDate}
            withTime
          />
          <ModalFooter>
            <Tooltip
              label={t('review.pickDate', 'Pick a date and time first')}
              disabled={Boolean(inspectionDate)}
            >
              <span>
                <button
                  type="button"
                  hidden
                  aria-hidden
                />
              </span>
            </Tooltip>
            <ActionIcon
              variant="filled"
              size="lg"
              disabled={!inspectionDate}
              aria-label={t('review.schedule', 'Schedule')}
              onClick={() =>
                run(async () => {
                  await scheduleInspection({
                    applicationId: id,
                    scheduledDate: inspectionDate,
                  }).unwrap();
                  setInspectionOpen(false);
                }, t('review.done.scheduled', 'Inspection scheduled'))
              }
            >
              <IconCheck size={18} />
            </ActionIcon>
          </ModalFooter>
        </Stack>
      </Modal>

      <Modal
        opened={resultOpen}
        onClose={() => setResultOpen(false)}
        title={t('review.inspectionResult', 'Inspection result')}
      >
        <Stack>
          {/* Structured per-area outcomes; persisted to the inspection's
              checklist column, which was previously never populated. */}
          <Stack gap={6}>
            {INSPECTION_CHECKLIST_ITEMS.map((item) => (
              <Group key={item.key} justify="space-between" wrap="nowrap">
                <Text size="sm">{item.label}</Text>
                <SegmentedControl
                  size="xs"
                  value={checklist[item.key] ?? 'PASS'}
                  onChange={(value) =>
                    setChecklist((prev) => ({
                      ...prev,
                      [item.key]: value as
                        | 'PASS'
                        | 'FAIL'
                        | 'NEEDS_CORRECTION',
                    }))
                  }
                  data={[
                    { value: 'PASS', label: t('review.checkPass', 'Pass') },
                    {
                      value: 'NEEDS_CORRECTION',
                      label: t('review.checkFix', 'Fix'),
                    },
                    { value: 'FAIL', label: t('review.checkFail', 'Fail') },
                  ]}
                />
              </Group>
            ))}
          </Stack>
          <Textarea
            label={t('review.findings', 'Findings')}
            withAsterisk
            value={findings}
            onChange={(e) => setFindings(e.currentTarget.value)}
            autosize
            minRows={3}
          />
          <ModalFooter grow>
            <ActionIcon
              variant="light"
              color="teal"
              size="lg"
              disabled={!findings.trim() || !pendingInspection}
              aria-label={t('review.passed', 'Passed')}
              onClick={() =>
                run(async () => {
                  // Guarded by the disabled state above; narrowing it here
                  // keeps that guarantee in the type system too.
                  if (!pendingInspection) return;
                  await recordResult({
                    inspectionId: pendingInspection.id,
                    applicationId: id,
                    result: 'PASSED',
                    findings,
                    checklist: buildChecklist(checklist),
                  }).unwrap();
                  setResultOpen(false);
                }, t('review.done.inspectionPassed', 'Inspection passed'))
              }
            >
              <IconCheck size={18} />
            </ActionIcon>
            <ActionIcon
              variant="light"
              color="red"
              size="lg"
              disabled={!findings.trim() || !pendingInspection}
              aria-label={t('review.failed', 'Failed')}
              onClick={() =>
                run(async () => {
                  // Guarded by the disabled state above; narrowing it here
                  // keeps that guarantee in the type system too.
                  if (!pendingInspection) return;
                  await recordResult({
                    inspectionId: pendingInspection.id,
                    applicationId: id,
                    result: 'FAILED',
                    findings,
                    checklist: buildChecklist(checklist),
                  }).unwrap();
                  setResultOpen(false);
                }, t('review.done.inspectionFailed', 'Inspection failed'))
              }
            >
              <IconX size={18} />
            </ActionIcon>
          </ModalFooter>
        </Stack>
      </Modal>
    </Container>
  );
}

/**
 * Evidence badges for one staff member.
 *
 * `application-for-review` nests a `documents` array per staff member, but it
 * doesn't always carry the uploaded file (the portal's own upload widget
 * hits the attachments endpoint directly for the same reason). Query
 * attachments by owner here too, so the officer gets a working link instead
 * of a badge with nowhere to go.
 */
function StaffEvidenceCell({
  staffId,
  fallback,
}: {
  staffId: string;
  fallback?: { id: string; documentKey: string; files: { url?: string }[] }[];
}) {
  const { data: attachments } = useGetAttachmentsQuery({
    ownerType: 'APPLICATION_STAFF',
    ownerId: staffId,
  });
  const docs = attachments?.length ? attachments : (fallback ?? []);

  return (
    <Group gap={4}>
      {docs.map((doc) => {
        const url = doc.files?.[0]?.url;
        return (
          <Badge
            key={doc.id}
            size="xs"
            variant="light"
            component={url ? 'a' : undefined}
            href={url}
            target={url ? '_blank' : undefined}
            rel={url ? 'noreferrer' : undefined}
            style={url ? { cursor: 'pointer' } : undefined}
          >
            {doc.documentKey}
          </Badge>
        );
      })}
    </Group>
  );
}

function SummaryRow({ label, value }: { label: string; value?: string | null }) {
  return (
    <Group justify="space-between" gap="xs" wrap="nowrap">
      <Text size="xs" c="dimmed">
        {label}
      </Text>
      <Text size="xs" fw={500} ta="right" truncate>
        {value || '—'}
      </Text>
    </Group>
  );
}

export default LicenseReviewPage;
