import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
  ActionIcon,
  Button,
  Group,
  MantineSize,
  Popover,
  SegmentedControl,
  Select,
  Stack,
  Text,
  TextInput,
} from '@mantine/core';
import { TimeInput } from '@mantine/dates';
import { useDisclosure } from '@mantine/hooks';
import { DayPicker as EthiopicDayPicker } from '@daypicker/ethiopic';
import { DayPicker as GregorianDayPicker, type Matcher } from '@daypicker/react';
import { IconCalendarEvent } from '@tabler/icons-react';
import '@daypicker/react/dist/style.css';
import './AmharicDatePicker.css';
import {
  type EthPeriod,
  ETH_PERIODS,
  ethMonthName,
  ethTimeLabel,
  fromEthTime,
  toAmharicDisplay,
  toEthDateTime,
  toEthTime,
} from '@ema-platform/shared';

export type { EthPeriod };

// react-day-picker calls these with the Gregorian Date it tracks internally;
// override so the caption/dropdown show Amharic month names instead of the
// library's Latin transliteration (triggered by numerals="latn" below).
const ETH_FORMATTERS = {
  formatCaption: (month: Date) =>
    `${ethMonthName(month)} ${toEthDateTime(month).year}`,
  formatMonthDropdown: (month: Date) => ethMonthName(month),
};

type DateWireFormat = 'iso' | 'date';

// yyyy-MM-dd, parsed/formatted as a LOCAL calendar date — no Date-object/UTC
// round-trip at all, so it can't suffer the timezone off-by-one class of bug
// the ISO path below works around. This is the shape filter query params and
// plain `date: string` DTO fields expect.
function parsePlainDate(value: string): Date | null {
  const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
  if (!m) return null;
  return new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]));
}

function formatPlainDate(date: Date): string {
  const y = date.getFullYear();
  const m = String(date.getMonth() + 1).padStart(2, '0');
  const d = String(date.getDate()).padStart(2, '0');
  return `${y}-${m}-${d}`;
}

// ISO wire format is a full ISO-8601 instant string (e.g.
// "2026-07-31T00:00:00.000Z"), what most backend date fields expect. When
// `withTime` is off, the picker only selects a calendar day, so the time is
// pinned to UTC midnight — reading back with getUTC*() (not local get*())
// keeps the calendar day stable regardless of the runtime's timezone,
// avoiding the same off-by-one class of bug the UTC-noon workaround above
// exists for.
function parseWireValue(
  value: string | null | undefined,
  format: DateWireFormat,
  withTime: boolean,
): Date | null {
  if (!value) return null;
  if (format === 'date') return parsePlainDate(value);
  const d = new Date(value);
  if (isNaN(d.getTime())) return null;
  return withTime
    ? d
    : new Date(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
}

function formatWireValue(
  date: Date,
  format: DateWireFormat,
  withTime: boolean,
): string {
  if (format === 'date') return formatPlainDate(date);
  return withTime
    ? date.toISOString()
    : new Date(
        Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
      ).toISOString();
}

// Combines a calendar day with a time-of-day, keeping whichever half isn't
// changing. `base` is the currently selected Date (may be null if nothing
// picked yet); `day`/`time` override only the half that's provided.
function mergeDateTime(
  base: Date | null,
  day?: Date,
  time?: { hours: number; minutes: number },
): Date {
  const result = day ? new Date(day) : new Date(base ?? new Date());
  if (time) {
    result.setHours(time.hours, time.minutes, 0, 0);
  } else if (day && base) {
    result.setHours(base.getHours(), base.getMinutes(), base.getSeconds(), 0);
  }
  return result;
}

const YEAR_DROPDOWN_START = new Date(new Date().getFullYear() - 100, 0, 1);
const YEAR_DROPDOWN_END = new Date(new Date().getFullYear() + 50, 11, 31);

export interface AmharicDatePickerProps {
  label?: React.ReactNode;
  value?: string | null;
  onChange?: (value: string) => void;
  required?: boolean;
  placeholder?: string;
  error?: React.ReactNode;
  disabled?: boolean;
  size?: MantineSize;
  name?: string;
  onBlur?: () => void;
  /** Width of the input, same as Mantine's `w` on TextInput/Select — needed
   * to line this field up with siblings in a filter bar. */
  w?: number | string;
  /** Show a time-of-day field alongside the calendar. Off by default —
   * most callers only need a calendar day. */
  withTime?: boolean;
  /** Earliest/latest selectable day. Accepts a Date or a value in the same
   * wire format as `value`. Days outside the range are disabled in both
   * calendars. */
  minDate?: Date | string;
  maxDate?: Date | string;
  /** Wire format for `value`/`onChange`: a full ISO-8601 instant (default,
   * what most backend date fields expect) or a bare `yyyy-mm-dd` calendar
   * date (what filter query params and plain `date: string` DTO fields
   * expect). */
  dateFormat?: DateWireFormat;
}

export function AmharicDatePicker({
  label,
  value,
  onChange,
  required,
  placeholder,
  error,
  disabled,
  size,
  name,
  onBlur,
  w,
  withTime = false,
  minDate,
  maxDate,
  dateFormat = 'iso',
}: AmharicDatePickerProps) {
  const { t, i18n } = useTranslation();
  const [calendarType, setCalendarType] = useState<'EN' | 'AMH'>(() =>
    i18n.language?.startsWith('am') ? 'AMH' : 'EN',
  );
  const [opened, { close, toggle }] = useDisclosure(false);

  const selected = parseWireValue(value, dateFormat, withTime);

  const asDate = (limit: Date | string | undefined) =>
    limit instanceof Date ? limit : parseWireValue(limit, dateFormat, withTime);
  const min = asDate(minDate);
  const max = asDate(maxDate);
  const outOfRange: Matcher[] = [
    ...(min ? [{ before: min }] : []),
    ...(max ? [{ after: max }] : []),
  ];
  // Compared as calendar days — the limits carry a midnight time-of-day, so
  // an instant comparison would call today "after" a max of today.
  const todayKey = formatPlainDate(new Date());
  const todayOutOfRange =
    (!!min && todayKey < formatPlainDate(min)) ||
    (!!max && todayKey > formatPlainDate(max));

  const dateLabel = selected
    ? calendarType === 'EN'
      ? selected.toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'short',
          day: 'numeric',
        })
      : toAmharicDisplay(selected)
    : '';
  const timeLabel =
    withTime && selected
      ? calendarType === 'AMH'
        ? ` ${ethTimeLabel(selected)}`
        : ` ${selected.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}`
      : '';
  const displayValue = dateLabel + timeLabel;

  return (
    <Popover
      opened={opened}
      onChange={close}
      position="bottom-start"
      width="auto"
      trapFocus
      withArrow
      withinPortal
      shadow="md"
      radius="md"
      // The field lives inside a Modal's scrollable body while the dropdown is
      // portaled to <body> — a different scroll container than its reference.
      // The default `absolute` strategy sums offsets across that scroll chain
      // and can get it wrong (dropdown flipped off-screen, or not following
      // the field as the modal scrolls). `fixed` positions purely off the
      // reference's viewport rect, sidestepping that.
      floatingStrategy="fixed"
      // Prevents the dropdown from re-flipping position mid-interaction —
      // switching months resizes the grid (Pagume has far fewer days), which
      // otherwise nudges the floating box right as a nav click lands, making
      // the click miss.
      preventPositionChangeWhenVisible
    >
      <Popover.Target>
        <TextInput
          label={label}
          required={required}
          value={displayValue}
          readOnly
          disabled={disabled}
          size={size}
          name={name}
          w={w}
          error={error}
          placeholder={placeholder}
          onClick={() => !disabled && toggle()}
          onBlur={onBlur}
          styles={{
            input: {
              cursor: disabled ? 'not-allowed' : 'pointer',
            },
          }}
          leftSection={
            <Button
              type="button"
              variant="subtle"
              size="compact-xs"
              tabIndex={-1}
              disabled={disabled}
              onClick={(e) => {
                e.stopPropagation();
                setCalendarType((prev) => (prev === 'EN' ? 'AMH' : 'EN'));
              }}
              aria-label={t('common.switchCalendar')}
              styles={{
                root: {
                  paddingLeft: 6,
                  paddingRight: 6,
                  fontWeight: 600,
                  fontSize: '0.75rem',
                },
              }}
            >
              {calendarType}
            </Button>
          }
          leftSectionWidth="3.25rem"
          rightSection={
            <ActionIcon size="sm" variant="transparent" disabled={disabled} onClick={() => toggle()}>
              <IconCalendarEvent size={18} />
            </ActionIcon>
          }
        />
      </Popover.Target>

      {/* Inside a Modal the dropdown is portaled out of the modal body so it positions nicely. */}
      <Popover.Dropdown className="amharic-daypicker-dropdown" p="sm" style={{ overflow: 'hidden' }}>
        {calendarType === 'AMH' ? (
          <EthiopicDayPicker
            className="amharic-daypicker"
            mode="single"
            selected={selected ?? undefined}
            defaultMonth={selected ?? undefined}
            startMonth={YEAR_DROPDOWN_START}
            endMonth={YEAR_DROPDOWN_END}
            numerals="latn"
            captionLayout="dropdown"
            disabled={outOfRange}
            formatters={ETH_FORMATTERS}
            onSelect={(date: Date | undefined) => {
              onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : '');
              if (!withTime) close();
            }}
          />
        ) : (
          <GregorianDayPicker
            className="amharic-daypicker"
            mode="single"
            selected={selected ?? undefined}
            defaultMonth={selected ?? undefined}
            startMonth={YEAR_DROPDOWN_START}
            endMonth={YEAR_DROPDOWN_END}
            captionLayout="dropdown"
            disabled={outOfRange}
            onSelect={(date: Date | undefined) => {
              onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : '');
              if (!withTime) close();
            }}
          />
        )}

        {withTime && calendarType === 'AMH' && (() => {
          const { period, hour } = selected ? toEthTime(selected.getHours()) : { period: 'tewat' as EthPeriod, hour: 12 };
          const minute = selected ? selected.getMinutes() : 0;
          const periodHours = ETH_PERIODS.find((p) => p.value === period)?.hours ?? ETH_PERIODS[0].hours;
          const setTime = (h24: number, m: number) =>
            onChange?.(
              formatWireValue(mergeDateTime(selected, undefined, { hours: h24, minutes: m }), dateFormat, true),
            );
          return (
            <Stack gap={4} mt="sm">
              <Text size="sm" fw={500}>
                {t('common.time')}
              </Text>
              {/* Full-width 4-way toggle reads better than a dropdown for 4
                  short Amharic words, and doesn't get squeezed by the
                  calendar's fixed (auto) popover width the way three
                  side-by-side Selects did. */}
              <SegmentedControl
                fullWidth
                size="xs"
                disabled={!selected}
                value={period}
                data={ETH_PERIODS.map((p) => ({ value: p.value, label: p.label }))}
                onChange={(next) => {
                  // Keep the same slot index within the new period so the
                  // hour never lands outside its 6-hour range.
                  const slot = Math.max(periodHours.indexOf(hour), 0);
                  const nextHours = ETH_PERIODS.find((p) => p.value === next)?.hours ?? periodHours;
                  setTime(fromEthTime(next as EthPeriod, nextHours[slot]), minute);
                }}
              />
              <Group gap={4} justify="center" wrap="nowrap">
                <Select
                  disabled={!selected}
                  value={String(hour)}
                  data={periodHours.map((h) => ({ value: String(h), label: String(h) }))}
                  allowDeselect={false}
                  w={72}
                  comboboxProps={{ withinPortal: true }}
                  onChange={(next) => {
                    if (!next) return;
                    setTime(fromEthTime(period, Number(next)), minute);
                  }}
                />
                <Text size="sm" c="dimmed">
                  :
                </Text>
                <Select
                  disabled={!selected}
                  value={String(minute).padStart(2, '0')}
                  data={Array.from({ length: 60 }, (_, m) => ({
                    value: String(m).padStart(2, '0'),
                    label: String(m).padStart(2, '0'),
                  }))}
                  allowDeselect={false}
                  w={72}
                  comboboxProps={{ withinPortal: true }}
                  onChange={(next) => {
                    if (next == null) return;
                    setTime(fromEthTime(period, hour), Number(next));
                  }}
                />
              </Group>
            </Stack>
          );
        })()}

        {withTime && calendarType === 'EN' && (
          <TimeInput
            label={t('common.time')}
            mt="sm"
            disabled={!selected}
            value={
              selected
                ? `${String(selected.getHours()).padStart(2, '0')}:${String(selected.getMinutes()).padStart(2, '0')}`
                : ''
            }
            onChange={(e) => {
              const [h, m] = e.currentTarget.value.split(':').map(Number);
              if (Number.isNaN(h) || Number.isNaN(m)) return;
              onChange?.(
                formatWireValue(mergeDateTime(selected, undefined, { hours: h, minutes: m }), dateFormat, true),
              );
            }}
          />
        )}

        <Group justify="space-between" mt="sm">
          <Button
            variant="subtle"
            size="xs"
            onClick={() => {
              onChange?.('');
              close();
            }}
          >
            {calendarType === 'AMH' ? 'አጽዳ' : 'Clear'}
          </Button>
          <Button
            variant="light"
            size="xs"
            disabled={todayOutOfRange}
            onClick={() => {
              onChange?.(formatWireValue(new Date(), dateFormat, withTime));
              close();
            }}
          >
            {calendarType === 'AMH' ? 'ዛሬ' : 'Today'}
          </Button>
        </Group>
      </Popover.Dropdown>
    </Popover>
  );
}
