import {
  Checkbox,
  Grid,
  Input,
  NumberInput,
  Select,
  Textarea,
  TextInput,
} from '@mantine/core';
import {
  conditionHolds,
  useGetActiveDepartmentsQuery,
  useGetRanksQuery,
  useLocalized,
  type Bilingual,
  type Department,
  type FormFieldConfig,
  type FormSectionConfig,
  type Rank,
  type Vessel,
} from '@ema-platform/api';
import { AmharicDatePicker, CountrySelect, PhoneInput } from '@ema-platform/ui';
import { useTranslation } from 'react-i18next';
import { LocationPicker } from '../../location/components/LocationPicker';

interface Props {
  section: FormSectionConfig;
  values: Record<string, unknown>;
  formData: Record<string, Record<string, unknown>>;
  onChange: (key: string, value: unknown) => void;
  disabled?: boolean;
  /** Keyed `${sectionKey}.${fieldKey}` — shown under the offending field. */
  errors?: Record<string, string>;
  /** The applicant's registered vessels, for the vessel-picker field. */
  vessels?: Vessel[];
  /**
   * Fired when the vessel picker's value resolves to a known vessel. Vessel
   * Information and Current Ownership are separate form sections, so this
   * component (one instance per section) can only fill its own fields —
   * the parent uses this to fill the rest via the same `VESSEL_FIELD_FILLERS`.
   */
  onVesselSelected?: (vessel: Vessel) => void;
}

/**
 * Maps a Vessel's own fields onto whichever fields the backend put in the
 * license form — same dual key/label matching the nationality prefill in
 * LicenseApplicationPage uses, so this doesn't depend on exact field-key
 * naming in the seeded formSchema. Exported so the parent page can apply it
 * across every section, not just the one the vessel picker lives in.
 */
export const VESSEL_FIELD_FILLERS: {
  matches: (label: string, key: string) => boolean;
  value: (vessel: Vessel) => unknown;
}[] = [
  { matches: (l, k) => k === 'registrationNumber' || l.includes('registration number'), value: (v) => v.registrationNumber },
  { matches: (l, k) => k === 'vesselName' || l.includes('vessel name'), value: (v) => v.name },
  { matches: (l, k) => k === 'category' || k === 'vesselCategory' || l.includes('vessel category'), value: (v) => v.category },
  { matches: (l, k) => k === 'vesselType' || l.includes('vessel type'), value: (v) => v.vesselType },
  { matches: (l, k) => k === 'imoNumber' || l.includes('imo'), value: (v) => v.imoNumber },
  { matches: (l, k) => k === 'hullNumber' || l.includes('hull number'), value: (v) => v.hullNumber },
  { matches: (l, k) => k === 'flagState' || l.includes('flag state') || l.includes('flag'), value: (v) => v.flagState },
  { matches: (l, k) => k === 'portOfRegistry' || l.includes('port of registry'), value: (v) => v.portOfRegistry },
  { matches: (l, k) => k === 'grossTonnage' || l.includes('gross tonnage'), value: (v) => v.grossTonnage },
  { matches: (l, k) => k === 'passengerCapacity' || l.includes('passenger capacity'), value: (v) => v.passengerCapacity },
  { matches: (l, k) => k === 'lengthMeters' || l.includes('length'), value: (v) => v.lengthMeters },
  { matches: (l, k) => k === 'yearBuilt' || l.includes('year built'), value: (v) => v.yearBuilt },
  { matches: (l, k) => k === 'engineType' || l.includes('engine type'), value: (v) => v.engineType },
  { matches: (l, k) => k === 'enginePowerKw' || l.includes('engine power'), value: (v) => v.enginePowerKw },
  { matches: (l, k) => k === 'numberOfEngines' || l.includes('number of engines'), value: (v) => v.numberOfEngines },
  { matches: (l, k) => k === 'hullMaterial' || l.includes('hull material'), value: (v) => v.hullMaterial },
  // Current Ownership: only the "current" owner, never "new owner" —
  // that's who the vessel is being transferred to, not on file anywhere.
  { matches: (l, k) => k === 'currentOwnerName' || k === 'currentOwner' || l.includes('current owner'), value: (v) => v.ownerName },
];

export function fillFromVessel(
  vessel: Vessel,
  fields: FormFieldConfig[],
  onChange: (key: string, value: unknown) => void,
) {
  for (const f of fields) {
    // English-pinned: matched against the English strings in VESSEL_FIELD_FILLERS.
    const label = (f.label.en ?? '').toLowerCase();
    const filler = VESSEL_FIELD_FILLERS.find((m) => m.matches(label, f.key));
    if (filler) onChange(f.key, filler.value(vessel) ?? '');
  }
}

/**
 * Options for a SELECT field.
 *
 * A department/rank field's seed `options` are a label cache that goes stale
 * the moment a backoffice admin adds a department or rank — the field's
 * value is already resolved server-side (profile department, or
 * `eligibility.nextRank`), correct either way, but a value missing from a
 * stale cache renders as a blank Select. Live-fetched departments/ranks take
 * over the labels for these two `source`s; the seed's own `options` still
 * cover every other SELECT unchanged.
 */
function selectOptions(
  field: FormFieldConfig,
  currentValue: string | undefined,
  departments: Department[] | undefined,
  ranks: Rank[],
  localized: (v?: Bilingual) => string,
): { value: string; label: string }[] {
  if (field.source === 'profile.seafarerDepartment' && departments) {
    return departments.map((d) => ({ value: d.code, label: localized(d.name) }));
  }
  if (field.source === 'eligibility.nextRank') {
    const options = ranks.map((r) => ({ value: r.key, label: localized(r.name) }));
    // The resolved rank might not be on THIS field's ladder (rank/rankEngine,
    // proficiencyDeck/Engine share one `eligibility.nextRank` source but only
    // one is ever populated) — still show it rather than a blank Select.
    if (currentValue && !options.some((o) => o.value === currentValue)) {
      const known = ranks.find((r) => r.key === currentValue);
      options.push({ value: currentValue, label: known ? localized(known.name) : currentValue });
    }
    return options;
  }
  return (field.options ?? []).map((o) => ({ value: o.value, label: localized(o.label) }));
}

/**
 * Renders one form section from the license type's configuration.
 *
 * Nothing here is Freight-Forwarder specific — adding a license type or moving
 * a field is a backend config change, which is the whole point of the
 * config-driven design.
 */
export function ConfigDrivenSection({
  section,
  values,
  formData,
  onChange,
  disabled,
  errors = {},
  vessels = [],
  onVesselSelected,
}: Props) {
  const localized = useLocalized();
  const { t } = useTranslation();
  const fields = [...(section.fields ?? [])].sort(
    (a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0),
  );

  // A department/rank SELECT ships with a hardcoded `options` label list in
  // the seed, so a department or rank added later in the backoffice has no
  // label there and would render as a blank Select even though the field's
  // value (resolved server-side) is correct. Skipped when the section has
  // neither kind of field, so most sections never pay for these two queries.
  const needsDepartmentLabels = fields.some(
    (f) => f.source === 'profile.seafarerDepartment',
  );
  const needsRankLabels = fields.some((f) => f.source === 'eligibility.nextRank');
  const { data: departments } = useGetActiveDepartmentsQuery(undefined, {
    skip: !needsDepartmentLabels,
  });
  const { data: rankRes } = useGetRanksQuery(undefined, { skip: !needsRankLabels });
  const ranks = rankRes?.items ?? [];

  return (
    <Grid>
      {fields.map((field) => {
        if (!conditionHolds(field.showWhen, formData)) return null;

        const label = localized(field.label);
        // English-pinned: the picker overrides below match English labels.
        const labelEn = (field.label.en ?? '').toLowerCase();
        const value = values?.[field.key];
        const error = errors[`${section.key}.${field.key}`];
        const common = {
          label,
          description: localized(field.helpText) || undefined,
          withAsterisk: field.required,
          error,
          // Read-only fields come from the user account and must not be edited.
          disabled: disabled || field.readOnly,
        };
        const span = field.type === 'TEXTAREA' ? 12 : 6;
        // Same field the profile Address tab collects — give it the same
        // searchable, flag-labeled picker instead of a plain option list.
        const isNationality = field.key === 'nationality' || labelEn.includes('nationality');
        // Options here can't be seeded statically — they're the applicant's
        // own vessel register, so this overrides whatever type the backend
        // configured, the same way nationality overrides SELECT above.
        const isVesselPicker = field.key === 'vesselId' || field.key === 'vessel' || /^(select\s+)?vessel$/i.test(labelEn.trim());
        // Stores a location-tree uuid, so it needs the cascading picker the
        // profile's Address tab uses — configured as TEXT because the field
        // types have no LOCATION member, which left a required field asking
        // the applicant to type a uuid by hand.
        const isLocation = field.key === 'locationId' || labelEn.trim() === 'location';

        return (
          <Grid.Col span={{ base: 12, md: span }} key={field.key}>
            {isLocation ? (
              // LocationPicker renders its own cascade of Selects and takes no
              // label/error props, so the wrapper supplies them.
              <Input.Wrapper
                label={label}
                description={localized(field.helpText) || undefined}
                withAsterisk={field.required}
                error={error}
              >
                <LocationPicker
                  value={(value as string) ?? undefined}
                  onChange={(id) => onChange(field.key, id)}
                  required={field.required}
                  maxDepth={3}
                  disabled={common.disabled}
                />
              </Input.Wrapper>
            ) : isNationality ? (
              <CountrySelect
                {...common}
                demonym
                value={(value as string) ?? null}
                onChange={(v) => onChange(field.key, v)}
              />
            ) : isVesselPicker ? (
              <Select
                {...common}
                placeholder={t('licensing.vesselPicker.placeholder')}
                data={vessels.map((v) => ({ value: v.id, label: `${v.name} — ${v.registrationNumber}` }))}
                value={(value as string) ?? null}
                onChange={(v) => {
                  onChange(field.key, v);
                  const vessel = vessels.find((x) => x.id === v);
                  if (vessel) {
                    fillFromVessel(vessel, fields, onChange);
                    onVesselSelected?.(vessel);
                  }
                }}
                searchable
                clearable={!field.required}
              />
            ) : field.type === 'SELECT' ? (
              <Select
                {...common}
                data={selectOptions(field, value as string | undefined, departments, ranks, localized)}
                value={(value as string) ?? null}
                onChange={(v) => onChange(field.key, v)}
                clearable={!field.required}
              />
            ) : field.type === 'BOOLEAN' ? (
              <Checkbox
                label={label}
                error={error}
                disabled={common.disabled}
                checked={Boolean(value)}
                onChange={(e) => onChange(field.key, e.currentTarget.checked)}
                mt="md"
              />
            ) : field.type === 'NUMBER' || field.type === 'MONEY' ? (
              <NumberInput
                {...common}
                value={(value as number) ?? ''}
                onChange={(v) => onChange(field.key, v === '' ? null : Number(v))}
                // Deliberately not clamped with min/max: Mantine would rewrite
                // the entered figure on blur, quietly turning a capital of
                // 900,000 into the 1,500,000 threshold. Validation reports the
                // problem instead, leaving what the applicant typed intact.
                thousandSeparator={field.type === 'MONEY' ? ',' : undefined}
              />
            ) : field.type === 'DATE' ? (
              // AmharicDatePicker has no `description`/`withAsterisk` props (from
              // `common`) — pass `required` explicitly so the asterisk still shows.
              <AmharicDatePicker
                label={label}
                error={error}
                disabled={common.disabled}
                required={field.required}
                dateFormat="date"
                value={(value as string) ?? ''}
                onChange={(v) => onChange(field.key, v)}
              />
            ) : field.type === 'PHONE' ? (
              <PhoneInput
                {...common}
                value={(value as string) ?? ''}
                onChange={(v) => onChange(field.key, v)}
              />
            ) : field.type === 'TEXTAREA' ? (
              <Textarea
                {...common}
                autosize
                minRows={3}
                value={(value as string) ?? ''}
                onChange={(e) => onChange(field.key, e.currentTarget.value)}
              />
            ) : (
              <TextInput
                {...common}
                type={field.type === 'EMAIL' ? 'email' : 'text'}
                value={(value as string) ?? ''}
                onChange={(e) => onChange(field.key, e.currentTarget.value)}
              />
            )}
          </Grid.Col>
        );
      })}
    </Grid>
  );
}
