import { useEffect, useMemo, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import {
  ActionIcon,
  Alert,
  Badge,
  Button,
  Card,
  Center,
  Container,
  Divider,
  Group,
  Loader,
  Modal,
  NumberInput,
  Paper,
  Stack,
  Stepper,
  Table,
  Text,
  TextInput,
  Title,
} from "@mantine/core";
import {
  IconAlertTriangle,
  IconCheck,
  IconInfoCircle,
  IconPlus,
  IconTrash,
} from "@tabler/icons-react";
import { notifications } from "@mantine/notifications";
import { useTranslation } from "react-i18next";
import {
  buildWizardSteps,
  conditionHolds,
  extractErrorMessage,
  extractValidationIssues,
  useLocalized,
  validateSections,
  STATUS_COLORS,
  STATUS_LABELS,
  useAddStaffMutation,
  useCreateApplicationMutation,
  useGetApplicationQuery,
  useGetAttachmentsQuery,
  useGetLicenseTypeRequirementsQuery,
  useGetMyVesselsQuery,
  usePatchSectionMutation,
  useRemoveStaffMutation,
  useResolveRemarkMutation,
  useResubmitApplicationMutation,
  useSubmitApplicationMutation,
  type FieldErrors,
  type FormFieldConfig,
  type ValidationIssue,
  type Vessel,
} from "@ema-platform/api";
import { getCountryCode, getCountryName, ModalFooter } from "@ema-platform/ui";
import {
  LICENSE_PERMISSIONS,
  PORTAL_PERMISSIONS,
  RequirePermission,
  useCurrentProfile,
} from "@ema-platform/auth";
import { ApplicationSummary } from "../components/ApplicationSummary";
import {
  ConfigDrivenSection,
  fillFromVessel,
} from "../components/ConfigDrivenSection";
import { DocumentSlots } from "../components/DocumentSlots";
import { StaffEvidence } from "../components/StaffEvidence";

/** Resolves a dot path (e.g. "profile.address.nationality") against a plain object. */
function readSourcePath(context: Record<string, unknown>, path: string): unknown {
  return path
    .split(".")
    .reduce<unknown>(
      (acc, key) =>
        acc && typeof acc === "object"
          ? (acc as Record<string, unknown>)[key]
          : undefined,
      context,
    );
}

/**
 * The applicant wizard, rendered entirely from the license type's
 * configuration. The same page serves every license type — the route's
 * `typeCode` decides which configuration is loaded.
 */
export function LicenseApplicationPage() {
  const { typeCode = "FREIGHT_FORWARDER", applicationId } = useParams();
  const navigate = useNavigate();
  const { i18n } = useTranslation();
  const localized = useLocalized();

  const { data: config, isLoading: loadingConfig } =
    useGetLicenseTypeRequirementsQuery({ idOrKey: typeCode });
  const { profile } = useCurrentProfile();
  // Only the vessel-select field (ConfigDrivenSection) reads this — fetched
  // here rather than deeper down since it's the shared source of draft state.
  const { data: vessels } = useGetMyVesselsQuery();
  const [createApplication] = useCreateApplicationMutation();
  const [appId, setAppId] = useState<string | undefined>(applicationId);

  // Create (or resume) the draft up front, so uploads have a real owner to
  // attach to and nothing is lost if the browser is closed mid-wizard.
  useEffect(() => {
    if (appId || !config) return;
    createApplication({ licenseType: typeCode })
      .unwrap()
      .then((app) => setAppId(app.id))
      .catch((err) =>
        notifications.show({
          color: "red",
          title: "Could not start application",
          message: extractErrorMessage(err),
        }),
      );
  }, [appId, config, createApplication, typeCode]);

  const { data: detail, refetch } = useGetApplicationQuery(appId as string, {
    skip: !appId,
  });
  const { data: attachments = [], refetch: refetchAttachments } =
    useGetAttachmentsQuery(
      { ownerType: "APPLICATION", ownerId: appId as string },
      { skip: !appId },
    );

  const [patchSection] = usePatchSectionMutation();
  const [submitApplication, { isLoading: submitting }] =
    useSubmitApplicationMutation();
  const [resubmitApplication, { isLoading: resubmitting }] =
    useResubmitApplicationMutation();
  const [resolveRemark] = useResolveRemarkMutation();
  const [addStaff] = useAddStaffMutation();
  const [removeStaff] = useRemoveStaffMutation();

  const [active, setActive] = useState(0);
  // A submitted (or otherwise non-draft) application opens straight to a
  // read-only summary — status up top, everything the applicant filled below
  // — instead of the entry step of a stepper there is nothing left to step
  // through. RESUBMIT_REQUIRED is still summary-first, but "Edit details"
  // drops into the ordinary wizard on the flagged sections.
  const [viewingSummary, setViewingSummary] = useState(true);
  const [draft, setDraft] = useState<Record<string, Record<string, unknown>>>(
    {},
  );
  const [issues, setIssues] = useState<ValidationIssue[]>([]);
  const [fieldErrors, setFieldErrors] = useState<FieldErrors>({});
  const [staffModal, setStaffModal] = useState<string | null>(null);
  const [newStaff, setNewStaff] = useState({
    fullName: "",
    position: "",
    yearsOfExperience: 0,
  });

  // Seed local edits from the server copy once it arrives.
  useEffect(() => {
    if (detail?.application?.formData) setDraft(detail.application.formData);
  }, [detail?.application?.id, detail?.application?.adjustmentRound]);

  // Nationality and National ID (Fayda) number are already on file from the
  // profile's Address tab — carry them into whichever section the form
  // config puts those fields in, rather than asking again. Only fills a
  // blank; a value already on the draft (the applicant's own edit, or one
  // the server saved) is left alone.
  useEffect(() => {
    const address = profile?.address;
    if (!address || !config) return;

    setDraft((prev) => {
      let next = prev;
      const fill = (
        matchField: (label: string, key: string) => boolean,
        toFieldValue: (field: FormFieldConfig) => unknown,
      ) => {
        for (const section of config.licenseType.formSchema.sections) {
          // English-pinned: matched against English substrings below ('nationality', 'fayda').
          const field = section.fields.find((f) =>
            matchField((f.label.en ?? "").toLowerCase(), f.key),
          );
          if (!field) continue;
          if (next[section.key]?.[field.key]) return; // already set — leave it
          next = {
            ...next,
            [section.key]: {
              ...next[section.key],
              [field.key]: toFieldValue(field),
            },
          };
          return;
        }
      };

      if (address.nationality) {
        // Profile stores the full country name; the field always renders as
        // a CountrySelect (see ConfigDrivenSection), which takes alpha-2
        // codes regardless of the backend's configured field type.
        fill(
          (label, key) =>
            key === "nationality" || label.includes("nationality"),
          () => getCountryCode(address.nationality) ?? address.nationality,
        );
      }
      if (address.idType === "NID" && address.idNumber) {
        fill(
          (label, key) =>
            key === "idNumber" ||
            key === "nationalId" ||
            key === "faydaNumber" ||
            label.includes("fayda") ||
            label.includes("national id"),
          () => address.idNumber,
        );
      }
      return next;
    });
    // Also re-run after the server seed effect (above) replaces `draft`
    // wholesale — that effect can resolve after this one, wiping the
    // prefill back out since the server's own draft has none of this yet.
  }, [
    profile?.address,
    config,
    detail?.application?.id,
    detail?.application?.formData,
  ]);

  // Generic fill for every field the config marks `readOnly` with a
  // `source` — e.g. seafarer registration's read-only Identity Details step,
  // which shows what's already on the profile instead of asking again.
  // `readOnly` fields are never sent by the applicant and the server skips
  // them at validation, so this is display-only; the profile itself is what
  // an edit has to go through.
  useEffect(() => {
    if (!profile || !config) return;
    const context = { user: profile.user, profile };

    setDraft((prev) => {
      let changed = false;
      const next = { ...prev };
      for (const section of config.licenseType.formSchema.sections) {
        for (const field of section.fields) {
          if (!field.readOnly || !field.source) continue;
          const value = readSourcePath(context, field.source);
          if (value === undefined || value === null || value === "") continue;
          if (next[section.key]?.[field.key] === value) continue;
          next[section.key] = { ...next[section.key], [field.key]: value };
          changed = true;
        }
      }
      return changed ? next : prev;
    });
  }, [profile, config, detail?.application?.id, detail?.application?.formData]);

  const application = detail?.application;
  const isAdjusting = application?.status === "RESUBMIT_REQUIRED";
  const openRemarks = detail?.openRemarks ?? [];

  const flaggedSections = useMemo(
    () =>
      Object.fromEntries(
        openRemarks
          .filter((r) => r.targetType === "FORM_SECTION")
          .map((r) => [r.targetKey, r.remark]),
      ),
    [openRemarks],
  );
  const flaggedDocuments = useMemo(
    () =>
      Object.fromEntries(
        openRemarks
          .filter((r) => r.targetType === "DOCUMENT")
          .map((r) => [r.targetKey, r.remark]),
      ),
    [openRemarks],
  );

  // Sections that share a group collapse onto one step, so the stepper stays
  // short instead of showing a page per section.
  const steps = useMemo(
    () =>
      buildWizardSteps(config?.licenseType?.formSchema?.sections ?? [], draft, {
        hasStaff: (config?.staffRoleRequirements?.length ?? 0) > 0,
        language: i18n.language,
      }),
    [config, draft, i18n.language],
  );
  const sections = useMemo(
    () => steps.flatMap((step) => step.sections),
    [steps],
  );

  if (loadingConfig || !config || !appId || !application) {
    return (
      <Center h={400}>
        <Loader />
      </Center>
    );
  }

  const readOnly = !["DRAFT", "RESUBMIT_REQUIRED"].includes(application.status);
  // A DRAFT has nothing worth summarising yet, so it always opens straight
  // into the wizard; every later status (including RESUBMIT_REQUIRED) opens
  // to the summary first.
  const showSummary = application.status !== "DRAFT" && viewingSummary;

  // Vessel Information and Current Ownership are separate form sections, so
  // ConfigDrivenSection (one instance per section) can't fill both itself —
  // it reports the pick up here and this fans it out across every section.
  function handleVesselSelected(vessel: Vessel) {
    for (const section of config?.licenseType.formSchema.sections ?? []) {
      fillFromVessel(vessel, section.fields, (key, value) => {
        setDraft((prev) => ({
          ...prev,
          [section.key]: { ...(prev[section.key] ?? {}), [key]: value },
        }));
      });
    }
  }

  async function saveSection(sectionKey: string) {
    // During an adjustment round only flagged sections are editable, so don't
    // even attempt a write the server would reject.
    if (isAdjusting && !flaggedSections[sectionKey]) return;
    const values = { ...(draft[sectionKey] ?? {}) };
    // The picker works in alpha-2 codes (CountrySelect); the backend, like
    // the profile Address endpoint, stores the full country name.
    const nationalityField = config?.licenseType.formSchema.sections
      .find((s) => s.key === sectionKey)
      // English-pinned: same reasoning as the fill() matcher above.
      ?.fields.find(
        (f) =>
          f.key === "nationality" ||
          (f.label.en ?? "").toLowerCase().includes("nationality"),
      );
    if (nationalityField && values[nationalityField.key]) {
      values[nationalityField.key] =
        getCountryName(values[nationalityField.key] as string) ||
        values[nationalityField.key];
    }
    try {
      await patchSection({
        id: appId as string,
        sectionKey,
        values,
      }).unwrap();
    } catch (err) {
      notifications.show({
        color: "red",
        title: "Could not save",
        message: extractErrorMessage(err),
      });
    }
  }

  async function handleSubmit() {
    setIssues([]);
    if (!readOnly && currentStep?.sections?.length) {
      const errors = validateSections(
        currentStep.sections,
        draft,
        i18n.language,
      );
      setFieldErrors(errors);
      if (Object.keys(errors).length) {
        notifications.show({
          color: "red",
          title: "Incomplete",
          message: "Complete the highlighted fields before submitting.",
        });
        return;
      }
    }
    for (const section of sections) await saveSection(section.key);
    try {
      if (isAdjusting) {
        for (const remark of openRemarks) {
          await resolveRemark({
            id: appId as string,
            remarkId: remark.id,
          }).unwrap();
        }
        await resubmitApplication(appId as string).unwrap();
        notifications.show({
          color: "teal",
          title: "Resubmitted",
          message: "Your corrections were sent back to the reviewing officer.",
        });
      } else {
        await submitApplication(appId as string).unwrap();
        notifications.show({
          color: "teal",
          title: "Application submitted",
          message: "You will be notified as it progresses.",
        });
      }
      navigate("/licensing/applications");
    } catch (err) {
      const found = extractValidationIssues(err);
      setIssues(found);
      notifications.show({
        color: "red",
        title: "Application incomplete",
        message: found.length
          ? `${found.length} item(s) still need attention.`
          : extractErrorMessage(err),
      });
    }
  }

  const currentStep = steps[active];

  /**
   * Checks the current step before moving on.
   *
   * The server rejects an incomplete application anyway, but only at submit —
   * by then the applicant has walked through every step and has to hunt for
   * what was missing. Validating per step points at the field directly.
   */
  async function validateCurrentStep(): Promise<boolean> {
    // The wizard does not render until the configuration has loaded, but this
    // is declared above that guard, so narrow it here too.
    if (!currentStep || !config) return true;

    if (currentStep.kind === "sections") {
      const errors = validateSections(
        currentStep.sections,
        draft,
        i18n.language,
      );
      setFieldErrors(errors);
      const count = Object.keys(errors).length;
      if (count > 0) {
        notifications.show({
          color: "red",
          title: "Incomplete",
          message: `Complete ${count} required field${count > 1 ? "s" : ""} to continue.`,
        });
        return false;
      }
      return true;
    }

    if (currentStep.kind === "staff") {
      const missing = config.staffRoleRequirements
        .filter(
          (role) =>
            (detail?.staff ?? []).filter((m) => m.roleKey === role.roleKey)
              .length < role.minCount,
        )
        .map((role) => `${localized(role.name)} (${role.minCount} required)`);
      if (missing.length) {
        notifications.show({
          color: "red",
          title: "Staff incomplete",
          message: `Still needed: ${missing.join(", ")}.`,
        });
        return false;
      }
      return true;
    }

    if (currentStep.kind === "documents") {
      const supplied = new Set(
        attachments.filter((a) => a.files?.length).map((a) => a.documentKey),
      );
      const missing = config.documentRequirements
        .filter(
          (req) =>
            req.mode === "ALWAYS" ||
            (req.mode === "CONDITIONAL" &&
              conditionHolds(req.conditionExpression, draft)),
        )
        .filter((req) => !supplied.has(req.key))
        .map((req) => localized(req.name));
      if (missing.length) {
        notifications.show({
          color: "red",
          title: "Documents missing",
          message: `Upload: ${missing.slice(0, 3).join(", ")}${missing.length > 3 ? ` and ${missing.length - 3} more` : ""}.`,
        });
        return false;
      }
      return true;
    }

    return true;
  }

  async function handleContinue() {
    // A locked step during an adjustment round has nothing to validate.
    if (!readOnly && !(await validateCurrentStep())) return;
    if (currentStep?.kind === "sections") {
      for (const section of currentStep.sections)
        await saveSection(section.key);
    }
    setFieldErrors({});
    setActive((s) => Math.min(steps.length - 1, s + 1));
  }

  /** Going back is always allowed; going forward validates each step passed. */
  async function goToStep(target: number) {
    if (target <= active) {
      setActive(target);
      return;
    }
    if (!readOnly && !(await validateCurrentStep())) return;
    if (currentStep?.kind === "sections") {
      for (const section of currentStep.sections)
        await saveSection(section.key);
    }
    setFieldErrors({});
    setActive(active + 1);
  }

  return (
    <Container size="lg" py="md">
      <Group justify="space-between" mb="xs" align="flex-start">
        <div>
          <Title order={3}>{localized(config.licenseType.name)}</Title>
          <Group gap="xs" mt={4}>
            <Text size="sm" c="dimmed">
              {application.applicationNumber}
            </Text>
            <Badge
              size="sm"
              variant="light"
              color={STATUS_COLORS[application.status]}
            >
              {STATUS_LABELS[application.status]}
            </Badge>
          </Group>
        </div>
        <Group gap="md" align="center">
          <Text size="sm" c="dimmed">
            Fee: {config.fee ?? "—"} {config.feeCurrency}
          </Text>
          {showSummary && isAdjusting && (
            <Button
              size="xs"
              variant="default"
              onClick={() => setViewingSummary(false)}
            >
              Edit details
            </Button>
          )}
        </Group>
      </Group>

      {isAdjusting && (
        <Alert
          color="orange"
          icon={<IconAlertTriangle size={16} />}
          title="Corrections requested"
          mb="md"
        >
          <Stack gap={4}>
            {openRemarks.map((remark) => (
              <Text size="sm" key={remark.id}>
                <b>{remark.targetKey}</b>: {remark.remark}
              </Text>
            ))}
            <Text size="xs" c="dimmed" mt={4}>
              Only the items listed above can be changed.
            </Text>
          </Stack>
        </Alert>
      )}

      {issues.length > 0 && (
        <Alert
          color="red"
          icon={<IconAlertTriangle size={16} />}
          title="Still missing"
          mb="md"
        >
          <Stack gap={2}>
            {issues.map((issue, i) => (
              <Text size="sm" key={i}>
                • {issue.message}
              </Text>
            ))}
          </Stack>
        </Alert>
      )}

      {showSummary && (
        <ApplicationSummary
          sections={sections}
          formData={application.formData}
          localized={localized}
          config={config}
          attachments={attachments}
          applicationId={appId as string}
        />
      )}

      {!showSummary && (
        <Paper withBorder p="lg" radius="md">
          <Stepper active={active} onStepClick={goToStep} size="sm" mb="lg">
            {steps.map((step) => (
              <Stepper.Step key={step.key} label={step.label} />
            ))}
          </Stepper>

          {currentStep?.kind === "sections" && (
            <Stack gap="lg">
              {currentStep.sections.map((section, index) => {
                const locked = isAdjusting && !flaggedSections[section.key];
                return (
                  <div key={section.key}>
                    {index > 0 && <Divider mb="lg" />}
                    <Text fw={600} fz="sm" tt="uppercase" c="gray.6" mb="sm">
                      {localized(section.title)}
                    </Text>
                    {locked && (
                      <Alert
                        color="gray"
                        icon={<IconInfoCircle size={16} />}
                        mb="md"
                      >
                        This section was accepted and is locked for this round.
                      </Alert>
                    )}
                    <ConfigDrivenSection
                      section={section}
                      values={draft[section.key] ?? {}}
                      formData={draft}
                      onVesselSelected={handleVesselSelected}
                      errors={fieldErrors}
                      vessels={vessels}
                      disabled={readOnly || locked}
                      onChange={(key, value) => {
                        setDraft((prev) => ({
                          ...prev,
                          [section.key]: {
                            ...(prev[section.key] ?? {}),
                            [key]: value,
                          },
                        }));
                        // Clear the error as soon as the applicant addresses it.
                        setFieldErrors((prev) => {
                          const next = { ...prev };
                          delete next[`${section.key}.${key}`];
                          return next;
                        });
                      }}
                    />
                  </div>
                );
              })}
            </Stack>
          )}

          {currentStep?.kind === "staff" && (
            <Stack>
              {config.staffRoleRequirements.map((role) => {
                const members = (detail?.staff ?? []).filter(
                  (s) => s.roleKey === role.roleKey,
                );
                return (
                  <Card withBorder key={role.roleKey} padding="md">
                    <Group justify="space-between" mb="xs">
                      <div>
                        <Text fw={600} size="sm">
                          {localized(role.name)}
                        </Text>
                        <Text size="xs" c="dimmed">
                          {members.length} of {role.minCount} required
                          {role.requiredEvidence.length > 0 &&
                            ` · each needs ${role.requiredEvidence
                              .filter((e) => e.mandatory)
                              .map((e) => localized(e.label))
                              .join(", ")}`}
                        </Text>
                      </div>
                      <Group gap="xs">
                        {members.length >= role.minCount && (
                          <Badge
                            color="teal"
                            size="sm"
                            leftSection={<IconCheck size={10} />}
                          >
                            complete
                          </Badge>
                        )}
                        {!readOnly && (
                          <Button
                            size="xs"
                            variant="light"
                            leftSection={<IconPlus size={14} />}
                            onClick={() => setStaffModal(role.roleKey)}
                          >
                            Add
                          </Button>
                        )}
                      </Group>
                    </Group>

                    <Stack gap="xs">
                      {members.map((member) => (
                        <Card
                          withBorder
                          key={member.id}
                          padding="sm"
                          radius="sm"
                        >
                          <Group
                            justify="space-between"
                            mb={member.id ? "xs" : 0}
                          >
                            <div>
                              <Text size="sm" fw={500}>
                                {member.fullName}
                              </Text>
                              <Text size="xs" c="dimmed">
                                {member.position ?? "—"}
                                {member.yearsOfExperience
                                  ? ` · ${member.yearsOfExperience} yrs`
                                  : ""}
                              </Text>
                            </div>
                            {!readOnly && (
                              <ActionIcon
                                variant="subtle"
                                color="red"
                                onClick={async () => {
                                  await removeStaff({
                                    id: appId,
                                    staffId: member.id,
                                  });
                                  refetch();
                                }}
                              >
                                <IconTrash size={16} />
                              </ActionIcon>
                            )}
                          </Group>
                          <StaffEvidence
                            staffId={member.id}
                            evidence={role.requiredEvidence}
                            readOnly={readOnly}
                            onUploaded={refetch}
                          />
                        </Card>
                      ))}
                    </Stack>
                  </Card>
                );
              })}
            </Stack>
          )}

          {currentStep?.kind === "documents" && (
            <DocumentSlots
              requirements={config.documentRequirements}
              attachments={attachments}
              formData={draft}
              ownerType="APPLICATION"
              ownerId={appId}
              flagged={flaggedDocuments}
              restrictToFlagged={isAdjusting}
              readOnly={readOnly}
              onUploaded={() => {
                refetchAttachments();
                refetch();
              }}
            />
          )}

          {currentStep?.kind === "review" && (
            <Stack>
              {currentStep.sections.map((section) => (
                <div key={section.key}>
                  <Text fw={600} fz="sm" tt="uppercase" c="gray.6" mb="sm">
                    {localized(section.title)}
                  </Text>
                  <ConfigDrivenSection
                    section={section}
                    onVesselSelected={handleVesselSelected}
                    values={draft[section.key] ?? {}}
                    formData={draft}
                    errors={fieldErrors}
                    vessels={vessels}
                    disabled={readOnly}
                    onChange={(key, value) => {
                      setDraft((prev) => ({
                        ...prev,
                        [section.key]: {
                          ...(prev[section.key] ?? {}),
                          [key]: value,
                        },
                      }));
                      setFieldErrors((prev) => {
                        const next = { ...prev };
                        delete next[`${section.key}.${key}`];
                        return next;
                      });
                    }}
                  />
                  <Divider my="lg" />
                </div>
              ))}
              <Title order={5}>Review</Title>
              {sections.map((section) => (
                <div key={section.key}>
                  <Text fw={600} size="sm" mb={4}>
                    {localized(section.title)}
                  </Text>
                  <Table withTableBorder withColumnBorders>
                    <Table.Tbody>
                      {(section.fields ?? [])
                        .filter((f) => conditionHolds(f.showWhen, draft))
                        .map((field) => (
                          <Table.Tr key={field.key}>
                            <Table.Td w="45%">
                              <Text size="xs" c="dimmed">
                                {localized(field.label)}
                              </Text>
                            </Table.Td>
                            <Table.Td>
                              <Text size="sm">
                                {String(draft[section.key]?.[field.key] ?? "—")}
                              </Text>
                            </Table.Td>
                          </Table.Tr>
                        ))}
                    </Table.Tbody>
                  </Table>
                  <Divider my="sm" />
                </div>
              ))}
            </Stack>
          )}

          <Group justify="space-between" mt="xl">
            <Button
              variant="default"
              onClick={() => setActive((s) => Math.max(0, s - 1))}
              disabled={active === 0}
            >
              Back
            </Button>
            {active < steps.length - 1 ? (
              <Button onClick={handleContinue}>Continue</Button>
            ) : (
              <RequirePermission
                anyOf={
                  isAdjusting
                    ? [PORTAL_PERMISSIONS.RESUBMIT_APPLICATION]
                    : [LICENSE_PERMISSIONS.SUBMIT_APPLICATION]
                }
                hideOnly
              >
                <Button
                  color="teal"
                  loading={submitting || resubmitting}
                  disabled={readOnly}
                  onClick={handleSubmit}
                >
                  {isAdjusting ? "Resubmit corrections" : "Submit application"}
                </Button>
              </RequirePermission>
            )}
          </Group>
        </Paper>
      )}

      <Modal
        opened={Boolean(staffModal)}
        onClose={() => setStaffModal(null)}
        title="Add staff member"
      >
        <Stack>
          <TextInput
            label="Full name"
            withAsterisk
            value={newStaff.fullName}
            onChange={(e) =>
              setNewStaff({ ...newStaff, fullName: e.currentTarget.value })
            }
          />
          <TextInput
            label="Position"
            value={newStaff.position}
            onChange={(e) =>
              setNewStaff({ ...newStaff, position: e.currentTarget.value })
            }
          />
          <NumberInput
            label="Years of experience"
            value={newStaff.yearsOfExperience}
            onChange={(v) =>
              setNewStaff({ ...newStaff, yearsOfExperience: Number(v) || 0 })
            }
            min={0}
          />
          <ModalFooter>
            <Button
              onClick={async () => {
                if (!newStaff.fullName.trim() || !staffModal) return;
                await addStaff({ id: appId, roleKey: staffModal, ...newStaff });
                setNewStaff({
                  fullName: "",
                  position: "",
                  yearsOfExperience: 0,
                });
                setStaffModal(null);
                refetch();
              }}
            >
              Add
            </Button>
          </ModalFooter>
        </Stack>
      </Modal>
    </Container>
  );
}

export default LicenseApplicationPage;
