import { CalendarDays } from "lucide-react";
import type { SVGProps } from "react";
import { useMemo } from "react";
import { enUS, ru } from "date-fns/locale";
import { DayPicker, type DateRange, type Matcher } from "react-day-picker";
import { cn } from "../lib/cn";
import { formatDate } from "../lib/format";
import { NodeDcDropdown } from "./Dropdown";
import "./calendar.css";

export type NodeDcDateInput = Date | string | null | undefined;
export type NodeDcWeekStartsOn = 0 | 1 | 2 | 3 | 4 | 5 | 6;

export interface NodeDcCalendarRangePreview {
  from?: NodeDcDateInput;
  to?: NodeDcDateInput;
}

export interface NodeDcCalendarProps {
  value: string | null;
  rangeStart?: string | null;
  rangeEnd?: string | null;
  rangePreview?: NodeDcCalendarRangePreview;
  minDate?: NodeDcDateInput;
  maxDate?: NodeDcDateInput;
  weekStartsOn?: NodeDcWeekStartsOn;
  clearable?: boolean;
  resetLabel?: string;
  onChange: (value: string | null) => void;
}

export interface NodeDcDateFieldProps {
  value: string | null;
  rangeStart?: string | null;
  rangeEnd?: string | null;
  rangePreview?: NodeDcCalendarRangePreview;
  minDate?: NodeDcDateInput;
  maxDate?: NodeDcDateInput;
  weekStartsOn?: NodeDcWeekStartsOn;
  label: string;
  onChange: (value: string | null) => void;
  className?: string;
  triggerClassName?: string;
  placement?: "bottom-start" | "bottom-end" | "top-start" | "top-end";
  emptyLabel?: string;
  clearable?: boolean;
  closeOnSelect?: boolean;
  resetLabel?: string;
  popoverWidth?: number;
  popoverMinWidth?: number;
}

export function NodeDcDateField({
  value,
  rangeStart,
  rangeEnd,
  rangePreview,
  minDate,
  maxDate,
  weekStartsOn,
  label,
  onChange,
  className,
  triggerClassName,
  placement = "bottom-start",
  emptyLabel = "Нет",
  clearable = true,
  closeOnSelect = true,
  resetLabel,
  popoverWidth = 328,
  popoverMinWidth = 328,
}: NodeDcDateFieldProps) {
  return (
    <NodeDcDropdown
      className={cn("nodedc-ui-date-field", className)}
      placement={placement}
      minWidth={popoverMinWidth}
      width={popoverWidth}
      surfaceClassName="nodedc-ui-calendar-popover"
      trigger={({ open, toggle, setTriggerRef }) => (
        <button
          ref={setTriggerRef}
          className={cn("nodedc-ui-date-trigger", triggerClassName)}
          type="button"
          aria-label={label}
          aria-expanded={open}
          onClick={toggle}
        >
          <CalendarDays size={14} strokeWidth={1.75} />
          <span>{value ? formatDate(value) : emptyLabel}</span>
        </button>
      )}
    >
      {({ close }) => (
        <NodeDcCalendar
          value={value}
          rangeStart={rangeStart}
          rangeEnd={rangeEnd}
          rangePreview={rangePreview}
          minDate={minDate}
          maxDate={maxDate}
          weekStartsOn={weekStartsOn}
          clearable={clearable}
          resetLabel={resetLabel}
          onChange={(nextValue) => {
            onChange(nextValue);
            if (closeOnSelect) close();
          }}
        />
      )}
    </NodeDcDropdown>
  );
}

export function NodeDcCalendar({
  value,
  rangeStart,
  rangeEnd,
  rangePreview,
  minDate,
  maxDate,
  weekStartsOn = 0,
  clearable = true,
  resetLabel = "Сбросить дату",
  onChange,
}: NodeDcCalendarProps) {
  const selectedDate = value ? parseDate(value) : null;
  const previewRange = useMemo(
    () => normalizeRange(rangePreview?.from ?? rangeStart, rangePreview?.to ?? rangeEnd),
    [rangeEnd, rangePreview?.from, rangePreview?.to, rangeStart]
  );
  const disabledDays = useMemo(() => buildDisabledDays(minDate, maxDate), [maxDate, minDate]);
  const currentYear = new Date().getFullYear();
  const startMonth = useMemo(() => new Date(currentYear - 30, 0, 1), [currentYear]);
  const endMonth = useMemo(() => new Date(currentYear + 30, 11, 31), [currentYear]);
  const locale = getCalendarLocale();

  const sharedCalendarProps = {
    captionLayout: "dropdown" as const,
    className: "nodedc-ui-calendar nodedc-calendar-shell",
    components: { Chevron: CalendarChevron },
    defaultMonth: selectedDate ?? previewRange?.to ?? previewRange?.from ?? undefined,
    endMonth,
    fixedWeeks: true,
    initialFocus: true,
    locale,
    showOutsideDays: true,
    startMonth,
    weekStartsOn,
    disabled: disabledDays,
  };

  return (
    <div className="nodedc-ui-calendar-frame" data-nodedc-calendar="true">
      {previewRange ? (
        <DayPicker
          {...sharedCalendarProps}
          mode="range"
          selected={previewRange}
          onDayClick={(date, modifiers) => {
            if (modifiers.disabled) return;
            onChange(toLocalIso(date));
          }}
        />
      ) : (
        <DayPicker
          {...sharedCalendarProps}
          mode="single"
          selected={selectedDate ?? undefined}
          onSelect={(date) => onChange(date ? toLocalIso(date) : null)}
        />
      )}
      {clearable && value ? (
        <button className="nodedc-ui-calendar__reset" type="button" onClick={() => onChange(null)}>
          {resetLabel}
        </button>
      ) : null}
    </div>
  );
}

function CalendarChevron({ className, orientation, ...props }: SVGProps<SVGSVGElement> & { orientation?: "up" | "down" | "left" | "right" }) {
  return (
    <svg
      width="16"
      height="16"
      viewBox="0 0 16 16"
      fill="none"
      xmlns="http://www.w3.org/2000/svg"
      className={cn(
        "nodedc-ui-calendar__chevron",
        orientation === "right" && "nodedc-ui-calendar__chevron--right",
        orientation === "down" && "nodedc-ui-calendar__chevron--down",
        className
      )}
      {...props}
    >
      <path
        d="M9.55757 3.55708C9.80165 3.313 10.1983 3.313 10.4423 3.55708C10.6864 3.80116 10.6864 4.19777 10.4423 4.44185L6.88472 7.99946L10.4423 11.5571C10.6864 11.8012 10.6864 12.1978 10.4423 12.4418C10.1983 12.6859 9.80165 12.6859 9.55757 12.4418L5.55757 8.44185C5.31349 8.19777 5.31349 7.80116 5.55757 7.55708L9.55757 3.55708Z"
        fill="currentColor"
      />
    </svg>
  );
}

function getCalendarLocale() {
  const localeCode =
    typeof document !== "undefined" && document.documentElement.lang
      ? document.documentElement.lang
      : typeof navigator !== "undefined"
        ? navigator.language
        : "ru-RU";

  return localeCode.toLowerCase().startsWith("ru") ? ru : enUS;
}

function normalizeRange(startValue?: NodeDcDateInput, endValue?: NodeDcDateInput): DateRange | undefined {
  if (!startValue || !endValue) return undefined;

  const start = parseDate(startValue);
  const end = parseDate(endValue);

  if (!start || !end) return undefined;

  if (start <= end) return { from: start, to: end };
  return { from: end, to: start };
}

function buildDisabledDays(minDate?: NodeDcDateInput, maxDate?: NodeDcDateInput): Matcher[] {
  const disabledDays: Matcher[] = [];
  const min = minDate ? parseDate(minDate) : null;
  const max = maxDate ? parseDate(maxDate) : null;

  if (min) disabledDays.push({ before: min });
  if (max) disabledDays.push({ after: max });

  return disabledDays;
}

function parseDate(value: Date | string): Date | null {
  if (value instanceof Date) return Number.isNaN(value.getTime()) ? null : value;

  const date = new Date(value);
  if (!Number.isNaN(date.getTime())) return date;

  const fallbackDate = new Date(`${value.slice(0, 10)}T00:00:00`);
  return Number.isNaN(fallbackDate.getTime()) ? null : fallbackDate;
}

function toLocalDateInput(date: Date): string {
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, "0");
  const day = String(date.getDate()).padStart(2, "0");
  return `${year}-${month}-${day}`;
}

function toLocalIso(date: Date): string {
  const day = toLocalDateInput(date);
  return new Date(`${day}T00:00:00`).toISOString();
}
