import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
  ActionIcon,
  Button,
  Group,
  MantineSize,
  Popover,
  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 } from '@daypicker/react';
import { IconCalendarEvent } from '@tabler/icons-react';
import { EthDateTime } from 'ethiopian-calendar-date-converter';
import '@daypicker/react/dist/style.css';
import './AmharicDatePicker.css';

const EC_MONTHS_AM = [
  'መስከረም', 'ጥቅምት', 'ኅዳር', 'ታህሳስ', 'ጥር', 'የካቲት',
  'መጋቢት', 'ሚያዝያ', 'ግንቦት', 'ሰኔ', 'ሐምሌ', 'ነሐሴ', 'ጳጉሜ',
];

// EthDateTime.fromEuropeanDate() computes the day from a raw UTC-epoch
// difference. A local-midnight Date in any positive-UTC-offset timezone
// (e.g. Ethiopia, UTC+3) lands in the previous UTC day and converts to
// yesterday's Ethiopian date. Re-embedding the same Y/M/D at UTC noon fixes
// the day regardless of the runtime's timezone.
function toEthDateTime(date: Date): EthDateTime {
  const utcNoon = new Date(
    Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), 12),
  );
  return EthDateTime.fromEuropeanDate(utcNoon);
}

function ethMonthName(date: Date): string {
  try {
    return EC_MONTHS_AM[toEthDateTime(date).month - 1] ?? '';
  } catch {
    return '';
  }
}

function toAmharicDisplay(date: Date): string {
  try {
    const eth = toEthDateTime(date);
    return `${ethMonthName(date)} ${eth.date}/${eth.year}`;
  } catch {
    return date.toLocaleDateString('en-US');
  }
}

// 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;
}

// Accepts either wire shape for display-only formatting — tries the plain
// yyyy-MM-dd shape first, falls back to ISO — so these keep working
// regardless of which `dateFormat` produced the stored string.
function parseAnyDateString(value: string): Date | null {
  return parsePlainDate(value) ?? parseWireValue(value, 'iso', false);
}

export function toEthiopicDateLabel(date: Date | string): string {
  const d = typeof date === 'string' ? parseAnyDateString(date) : date;
  if (!d) return '';
  try {
    const eth = toEthDateTime(d);
    return `EC ${eth.year}-${String(eth.month).padStart(2, '0')}-${String(eth.date).padStart(2, '0')}`;
  } catch {
    return d.toLocaleDateString('en-US');
  }
}

export function toGregorianDateLabel(date: Date | string): string {
  const d = typeof date === 'string' ? parseAnyDateString(date) : date;
  if (!d) return '';
  return d.toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' });
}

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;
  /** 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,
  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 dateLabel = selected
    ? calendarType === 'EN'
      ? selected.toLocaleDateString('en-US', {
          year: 'numeric',
          month: 'long',
          day: 'numeric',
        })
      : toAmharicDisplay(selected)
    : '';
  const timeLabel =
    withTime && 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
      // Inside a Modal, the target sits in a scrollable container — floating-ui's
      // default flip middleware measures room against that scroll container, not
      // the viewport, and flips the dropdown above the field where it runs off
      // the top of the screen. Keeping it pinned below and letting shift nudge it
      // sideways instead avoids that.
      middlewares={{ flip: false, shift: true }}
    >
      <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}
          leftSection={
            <Button
              type="button"
              variant="light"
              size="compact-xs"
              tabIndex={0}
              disabled={disabled}
              onClick={(e) => {
                e.stopPropagation();
                setCalendarType((prev) => (prev === 'EN' ? 'AMH' : 'EN'));
              }}
              aria-label={t('common.switchCalendar')}
            >
              {calendarType}
            </Button>
          }
          leftSectionWidth="calc(4.375rem * var(--mantine-scale))"
          rightSection={
            <ActionIcon size="md" variant="transparent" disabled={disabled} onClick={() => toggle()}>
              <IconCalendarEvent size={20} />
            </ActionIcon>
          }
        />
      </Popover.Target>

      <Popover.Dropdown p="md">
        {calendarType === 'AMH' ? (
          <EthiopicDayPicker
            className="amharic-daypicker"
            mode="single"
            selected={selected ?? undefined}
            defaultMonth={selected ?? undefined}
            endMonth={YEAR_DROPDOWN_END}
            numerals="latn"
            captionLayout="dropdown"
            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}
            endMonth={YEAR_DROPDOWN_END}
            captionLayout="dropdown"
            onSelect={(date: Date | undefined) => {
              onChange?.(date ? formatWireValue(mergeDateTime(selected, date), dateFormat, withTime) : '');
              if (!withTime) close();
            }}
          />
        )}

        {withTime && (
          <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"
            onClick={() => {
              onChange?.(formatWireValue(new Date(), dateFormat, withTime));
              close();
            }}
          >
            {calendarType === 'AMH' ? 'ዛሬ' : 'Today'}
          </Button>
        </Group>
      </Popover.Dropdown>
    </Popover>
  );
}
