import { useEffect, useState } from 'react';
import {
  Alert,
  Button,
  Checkbox,
  Divider,
  Drawer,
  MultiSelect,
  NumberInput,
  SegmentedControl,
  Select,
  Stack,
  Text,
  TextInput,
} from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { useTranslation } from 'react-i18next';
import { BilingualInput, ModalFooter } from '@ema-platform/ui';
import {
  useLocalized,
  type ApplicationKind,
  type DocumentRequirement,
  type FormSchemaPalette,
  type LicenseType,
} from '@ema-platform/api';
import { ConditionBuilder, type ConditionValue } from './ConditionBuilder';
import type { ConditionTarget } from '../config/schema-paths';

/**
 * File types a slot may be opened to.
 *
 * Longer than the three a slot starts with, because what an applicant
 * actually has is not always a scan: a phone photographs an ID as HEIC, a
 * scanner writes multi-page TIFF, and an academic record often arrives as the
 * Word file its institution issued. Widening a slot stays a deliberate choice
 * — the defaults below do not change — but it no longer needs a release.
 */
const MIME_OPTIONS = [
  { value: 'application/pdf', label: 'PDF' },
  { value: 'image/jpeg', label: 'JPEG' },
  { value: 'image/png', label: 'PNG' },
  { value: 'image/webp', label: 'WebP' },
  { value: 'image/heic', label: 'HEIC (iPhone photo)' },
  { value: 'image/heif', label: 'HEIF' },
  { value: 'image/tiff', label: 'TIFF (scan)' },
  { value: 'image/bmp', label: 'BMP' },
  { value: 'application/msword', label: 'Word (.doc)' },
  {
    value: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
    label: 'Word (.docx)',
  },
  { value: 'application/vnd.ms-excel', label: 'Excel (.xls)' },
  {
    value: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    label: 'Excel (.xlsx)',
  },
  { value: 'text/csv', label: 'CSV' },
  { value: 'text/plain', label: 'Plain text (.txt)' },
  // Video is measured in hundreds of megabytes, not the 5 MB a slot starts
  // with: raise "Max file size" on any slot that accepts one.
  { value: 'video/mp4', label: 'Video (.mp4)' },
  { value: 'video/quicktime', label: 'Video (.mov, iPhone)' },
  { value: 'video/webm', label: 'Video (.webm)' },
  { value: 'video/x-msvideo', label: 'Video (.avi)' },
  { value: 'audio/mpeg', label: 'Audio (.mp3)' },
  { value: 'audio/wav', label: 'Audio (.wav)' },
  // Both, because the same .m4a is reported as audio/mp4 by Chrome and
  // audio/x-m4a by Safari; picking one would reject half the recordings.
  { value: 'audio/mp4', label: 'Audio (.m4a)' },
  { value: 'audio/x-m4a', label: 'Audio (.m4a, Safari)' },
  { value: 'audio/ogg', label: 'Audio (.ogg)' },
];

/** What a new slot accepts until someone widens it. */
const DEFAULT_MIME_TYPES = ['application/pdf', 'image/jpeg', 'image/png'];

type DraftRequirement = Omit<DocumentRequirement, 'id' | 'licenseTypeId' | 'isActive'>;

/**
 * Which licences a personal document is asked for.
 *
 * Empty means every licence (stored as a row with no licence type); otherwise
 * one row per chosen type, all sharing the key. The applicant sees one slot
 * either way — the portal collapses the rows by key — and only if they have
 * declared operating as one of the types.
 */
export type PersonalScope = string[];

function emptyDraft(applicationKind: ApplicationKind, personal: boolean): DraftRequirement {
  return {
    key: '',
    name: { en: '', am: '' },
    applicationKind,
    // A personal document is never demanded by one application, so "always
    // required" would be a promise nothing here can keep.
    mode: personal ? 'OPTIONAL' : 'ALWAYS',
    allowedMimeTypes: [...DEFAULT_MIME_TYPES],
    maxSizeMb: 5,
    requiresValidityDates: false,
    allowMultiple: false,
    isPersonal: personal,
    maxFiles: personal ? 1 : null,
    sortOrder: 0,
  };
}

/**
 * Adds/edits one document requirement slot.
 *
 * Two shapes of the same row: a slot on one licence type's application form,
 * and — with `personal` — a document every applicant keeps in their own vault
 * whatever they apply for. The vault has no application to condition on and no
 * renewal of its own, so those fields are hidden rather than left to mean
 * nothing.
 */
export function DocumentRequirementEditorDrawer({
  opened,
  onClose,
  requirement,
  defaultApplicationKind,
  onSave,
  palette,
  conditionTargets,
  saving,
  personal = false,
  licenseTypes = [],
  scope = [],
}: {
  opened: boolean;
  onClose: () => void;
  /** Null = adding a new requirement. */
  requirement: DocumentRequirement | null;
  defaultApplicationKind: ApplicationKind;
  onSave: (draft: DraftRequirement, scope: PersonalScope) => void;
  palette: FormSchemaPalette | undefined;
  conditionTargets: ConditionTarget[];
  saving: boolean;
  /** Editing a personal document — one kept in the applicant's own vault. */
  personal?: boolean;
  /** Licence types offered as scope; only read when `personal`. */
  licenseTypes?: LicenseType[];
  /** The licence types this document is already scoped to. */
  scope?: PersonalScope;
}) {
  const { t } = useTranslation();
  const localized = useLocalized();
  const [draft, setDraft] = useState<DraftRequirement>(
    emptyDraft(defaultApplicationKind, personal),
  );
  const [scopeIds, setScopeIds] = useState<PersonalScope>(scope);
  const [appliesToAll, setAppliesToAll] = useState(scope.length === 0);
  const [keyError, setKeyError] = useState<string | null>(null);
  const isNew = !requirement;

  useEffect(() => {
    if (opened) {
      setDraft(
        requirement
          ? {
              key: requirement.key,
              name: { ...requirement.name },
              description: requirement.description ? { ...requirement.description } : undefined,
              applicationKind: requirement.applicationKind,
              mode: requirement.mode,
              conditionExpression: requirement.conditionExpression,
              allowedMimeTypes: requirement.allowedMimeTypes,
              maxSizeMb: requirement.maxSizeMb,
              requiresValidityDates: requirement.requiresValidityDates,
              allowMultiple: requirement.allowMultiple,
              isPersonal: requirement.isPersonal ?? personal,
              maxFiles: requirement.maxFiles ?? null,
              sortOrder: requirement.sortOrder,
            }
          : emptyDraft(defaultApplicationKind, personal),
      );
      setScopeIds(scope);
      setAppliesToAll(scope.length === 0);
      setKeyError(null);
    }
    // `scope` is a fresh array each render; the opened flag is what gates this.
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [opened, requirement, defaultApplicationKind, personal]);

  function save() {
    if (!draft.key.trim()) {
      setKeyError(t('certReq.doc.keyRequired', 'Key is required'));
      return;
    }
    if (!draft.name.en?.trim()) return;
    // A condition is either a single-field check or an anyOf list — the CoP
    // watch_rating_certificate requirement is seeded with anyOf and no field.
    const hasCondition =
      Boolean(draft.conditionExpression?.field) ||
      Boolean(draft.conditionExpression?.anyOf?.length);
    if (
      draft.mode === 'CONDITIONAL' &&
      !hasCondition &&
      !draft.conditionExpression?.anyOf?.length
    ) {
      setKeyError(t('certReq.doc.conditionRequired', 'A conditional requirement needs a condition'));
      return;
    }
    if (personal && !appliesToAll && scopeIds.length === 0) {
      setKeyError(t('certReq.doc.scopeRequired', 'Choose at least one licence type'));
      return;
    }
    onSave(
      {
        ...draft,
        key: draft.key.trim(),
        conditionExpression: draft.mode === 'CONDITIONAL' ? draft.conditionExpression : undefined,
        isPersonal: personal,
        // `allowMultiple` predates `maxFiles` and nothing reads it any more;
        // kept in step so the two columns never contradict each other.
        allowMultiple: draft.maxFiles !== 1,
      },
      appliesToAll ? [] : scopeIds,
    );
  }

  return (
    <Drawer
      opened={opened}
      onClose={onClose}
      position="right"
      size="md"
      title={<Text fw={700}>{isNew ? t('certReq.doc.add', 'Add document requirement') : t('certReq.doc.edit', 'Edit document requirement')}</Text>}
    >
      <Stack gap="md">
        {personal && (
          <Alert
            variant="light"
            color="blue"
            icon={<IconInfoCircle size={16} />}
            title={t('certReq.doc.keyMatchTitle', 'Keys are what link this to a licence')}
          >
            {t(
              'certReq.doc.keyMatchBody',
              'An applicant who holds this document has it copied onto any licence application asking for a requirement with the same key. A key that matches nothing is still collected — it simply never fills a form by itself.',
            )}
          </Alert>
        )}

        <TextInput
          label={t('certReq.doc.key', 'Key')}
          placeholder="bank_letter"
          required
          value={draft.key}
          error={keyError}
          disabled={!isNew}
          description={
            !isNew
              ? t('certReq.doc.keyLocked', 'Key cannot change once created')
              : personal
                ? t(
                    'certReq.doc.keyHelpPersonal',
                    'Use the same key as the licence document this should answer — an application is filled from this document only when the two keys match exactly.',
                  )
                : t('certReq.doc.keyHelp', 'Stable slug identifying this document slot')
          }
          // The value is read out of the event first: a functional updater
          // runs after React has released the event, so `currentTarget` is
          // null by the time it would be read inside one.
          onChange={(e) => {
            const { value } = e.currentTarget;
            setDraft((d) => ({ ...d, key: value }));
          }}
        />

        <BilingualInput
          label={t('certReq.doc.name', 'Name')}
          required
          value={{ en: draft.name.en ?? '', am: draft.name.am ?? '' }}
          onChange={(v) => setDraft((d) => ({ ...d, name: v }))}
        />

        <BilingualInput
          label={t('certReq.doc.description', 'Description')}
          value={{ en: draft.description?.en ?? '', am: draft.description?.am ?? '' }}
          onChange={(v) => setDraft((d) => ({ ...d, description: v }))}
        />

        {personal && (
          <Stack gap="xs">
            <Text fz="sm" fw={500}>
              {t('certReq.doc.scope', 'Applies to')}
            </Text>
            <SegmentedControl
              fullWidth
              value={appliesToAll ? 'all' : 'selected'}
              onChange={(v) => setAppliesToAll(v === 'all')}
              data={[
                { value: 'all', label: t('certReq.doc.scopeAll', 'All licences') },
                {
                  value: 'selected',
                  label: t('certReq.doc.scopeSelected', 'Selected licence types'),
                },
              ]}
            />
            {!appliesToAll && (
              <MultiSelect
                data={licenseTypes
                  .slice()
                  .sort((a, b) => a.sortOrder - b.sortOrder)
                  .map((lt) => ({ value: lt.id, label: localized(lt.name) || lt.key }))}
                value={scopeIds}
                onChange={setScopeIds}
                searchable
                placeholder={t('certReq.doc.scopePlaceholder', 'Choose licence types')}
                description={t(
                  'certReq.doc.scopeHelp',
                  'Only applicants who declared one of these as a mode of operation are asked for it.',
                )}
              />
            )}
          </Stack>
        )}

        {!personal && (
          <Select
            label={t('certReq.doc.applicationKind', 'Application kind')}
            data={[
              { value: 'NEW', label: t('certReq.doc.kindNew', 'New application') },
              { value: 'RENEWAL', label: t('certReq.doc.kindRenewal', 'Renewal') },
            ]}
            value={draft.applicationKind}
            onChange={(v) => v && setDraft((d) => ({ ...d, applicationKind: v as ApplicationKind }))}
            allowDeselect={false}
            disabled={!isNew}
            description={!isNew ? t('certReq.doc.kindLocked', 'Application kind cannot change once created') : undefined}
          />
        )}

        {!personal && (
          <Select
            label={t('certReq.doc.mode', 'Mode')}
            data={[
              { value: 'ALWAYS', label: t('certReq.doc.modeAlways', 'Always required') },
              { value: 'CONDITIONAL', label: t('certReq.doc.modeConditional', 'Required when condition holds') },
              { value: 'OPTIONAL', label: t('certReq.doc.modeOptional', 'Optional upload') },
            ]}
            value={draft.mode}
            onChange={(v) => v && setDraft((d) => ({ ...d, mode: v as DraftRequirement['mode'] }))}
            allowDeselect={false}
          />
        )}

        {!personal && draft.mode === 'CONDITIONAL' && (
          <>
            <Divider label={t('certReq.condition.title', 'Condition')} labelPosition="left" />
            <ConditionBuilder
              value={(draft.conditionExpression ?? { field: '', equals: '' }) as ConditionValue}
              onChange={(v) => setDraft((d) => ({ ...d, conditionExpression: v ?? undefined }))}
              targets={conditionTargets}
              palette={palette}
              allowClear={false}
            />
          </>
        )}

        <MultiSelect
          label={t('certReq.doc.allowedTypes', 'Allowed file types')}
          description={t(
            'certReq.doc.allowedTypesHelp',
            'The applicant can only upload these. PDF, JPEG and PNG are selected by default.',
          )}
          searchable
          data={MIME_OPTIONS}
          value={draft.allowedMimeTypes}
          onChange={(v) => setDraft((d) => ({ ...d, allowedMimeTypes: v }))}
        />

        <NumberInput
          label={t('certReq.doc.maxSize', 'Max file size (MB)')}
          min={1}
          value={draft.maxSizeMb}
          onChange={(v) => setDraft((d) => ({ ...d, maxSizeMb: typeof v === 'number' ? v : d.maxSizeMb }))}
        />

        {!personal && (
          <Checkbox
            label={t('certReq.doc.requiresValidity', 'Requires validity dates')}
            checked={draft.requiresValidityDates}
            onChange={(e) => {
              const { checked } = e.currentTarget;
              setDraft((d) => ({ ...d, requiresValidityDates: checked }));
            }}
          />
        )}

        <NumberInput
          label={t('certReq.doc.maxFiles', 'Files accepted')}
          description={t(
            'certReq.doc.maxFilesHelp',
            'Leave empty for no limit. Use 2 for a document with a front and a back.',
          )}
          placeholder={t('certReq.doc.maxFilesUnlimited', 'No limit')}
          min={1}
          value={draft.maxFiles ?? ''}
          onChange={(v) =>
            setDraft((d) => ({ ...d, maxFiles: typeof v === 'number' ? v : null }))
          }
        />

        <NumberInput
          label={t('certReq.doc.sortOrder', 'Sort order')}
          value={draft.sortOrder}
          onChange={(v) => setDraft((d) => ({ ...d, sortOrder: typeof v === 'number' ? v : d.sortOrder }))}
        />

        <ModalFooter>
          <Button variant="default" onClick={onClose}>{t('certReq.cancel', 'Cancel')}</Button>
          <Button color="teal" loading={saving} onClick={save}>
            {isNew ? t('certReq.doc.add', 'Add document requirement') : t('certReq.saveChanges', 'Save changes')}
          </Button>
        </ModalFooter>
      </Stack>
    </Drawer>
  );
}
