import $ from "@/shared/common/jquery-global";
import React, { useEffect, useRef, useState } from "react";
import { cn } from "@/shared/lib/utils";

// CSS and plugins
import "@/shared/common/css/redmond.calendars.picker.css";
import "@/shared/common/css/ethiopian-date-picker-overrides.css";
import "@/shared/common/js/jquery.plugin.js";
import "@/shared/common/js/jquery.calendars.js";
import "@/shared/common/js/jquery.calendars.plus.js";
import "@/shared/common/js/jquery.calendars.picker.js";
import "@/shared/common/js/jquery.calendars.ethiopian.js";
import "@/shared/common/js/jquery.calendars.ethiopian-am.js";

interface CalendarsDate {
  toString(): string;
}

interface CalendarsDateFactory {
  toJSDate(): Date;
}

interface CalendarsInstance {
  newDate: (year: number, month: number, day: number) => CalendarsDateFactory;
  fromJSDate: (jsDate: Date) => unknown;
  formatDate: (
    format: string,
    date: unknown,
    settings?: { localNumbers?: boolean },
  ) => string;
}

interface CalendarsApi {
  instance: (calendarType: string, language: string) => CalendarsInstance;
}

interface CalendarsPickerOptions {
  calendar: CalendarsInstance;
  dateFormat: string;
  onSelect: (dates: CalendarsDate[]) => void;
}

type CalendarsPickerElement = JQuery<HTMLElement> & {
  calendarsPicker: (...args: unknown[]) => unknown;
};

declare global {
  interface JQueryStatic {
    calendars: CalendarsApi;
  }
}

interface EthiopianDatePickerModalProps {
  value?: string | Date;
  onChange: (value: string) => void;
}

const ETHIOPIAN_UTC_OFFSET_HOURS = 3;

const toEthiopianTimeInput = (date: Date): string => {
  if (!(date instanceof Date) || isNaN(date.getTime())) {
    return "00:00";
  }

  const totalMinutesUtc = date.getUTCHours() * 60 + date.getUTCMinutes();
  const totalMinutesEthiopian =
    (totalMinutesUtc + ETHIOPIAN_UTC_OFFSET_HOURS * 60 + 1440) % 1440;

  const hh = Math.floor(totalMinutesEthiopian / 60)
    .toString()
    .padStart(2, "0");
  const mm = (totalMinutesEthiopian % 60).toString().padStart(2, "0");

  return `${hh}:${mm}`;
};

const getCurrentEthiopianTimeInput = (): string => {
  return toEthiopianTimeInput(new Date());
};

/**
 * Ethiopian 12-hour clock parts. The Ethiopian day starts at 6:00 EAT, so EAT
 * 6:00 AM is clock 12 (day) and EAT 6:00 PM is clock 12 (night).
 *
 * `period` is purely a display concern — the stored value is always derived
 * from the EAT 24-hour time, so the backend payload is unaffected.
 */
type EthiopianClockPeriod = "day" | "night";

interface EthiopianClockParts {
  hour: number; // 1-12
  minute: number; // 0-59
  period: EthiopianClockPeriod;
}

const ETHIOPIAN_HOUR_OPTIONS = Array.from({ length: 12 }, (_, i) => i + 1);

// EAT 24h "HH:mm" -> Ethiopian 12h clock parts (day starts at 6:00 EAT).
const eatTimeToEthiopianClock = (eatTime: string): EthiopianClockParts => {
  const [hhRaw, mmRaw] = (eatTime || "").split(":");
  const parsedHour = Number(hhRaw);
  const parsedMinute = Number(mmRaw);
  const eatHour = Number.isFinite(parsedHour)
    ? ((parsedHour % 24) + 24) % 24
    : 0;
  const minute = Number.isFinite(parsedMinute)
    ? ((parsedMinute % 60) + 60) % 60
    : 0;

  const hour = (eatHour + 6) % 12 || 12;
  const period: EthiopianClockPeriod =
    eatHour >= 6 && eatHour < 18 ? "day" : "night";

  return { hour, minute, period };
};

// Ethiopian 12h clock parts -> EAT 24h "HH:mm".
const ethiopianClockToEatTime = ({
  hour,
  minute,
  period,
}: EthiopianClockParts): string => {
  const base = (hour % 12) + 6; // day window: 6..17
  const eatHour = (period === "night" ? base + 12 : base) % 24;
  const hh = eatHour.toString().padStart(2, "0");
  const mm = (((minute % 60) + 60) % 60).toString().padStart(2, "0");
  return `${hh}:${mm}`;
};

const normalizeDigits = (value: string): string =>
  value
    .replace(/[٠-٩]/g, (digit) => String(digit.charCodeAt(0) - 1632))
    .replace(/[۰-۹]/g, (digit) => String(digit.charCodeAt(0) - 1776));

const toEthiopianDateInput = (date: Date): string => {
  if (
    !(date instanceof Date) ||
    Number.isNaN(date.getTime()) ||
    !$ ||
    !$.calendars
  ) {
    return "";
  }

  try {
    const calendar = $.calendars.instance("ethiopian", "am");
    const ethiopianDate = calendar.fromJSDate(
      new Date(date.getTime() + ETHIOPIAN_UTC_OFFSET_HOURS * 60 * 60 * 1000),
    );

    return calendar.formatDate("yyyy-mm-dd", ethiopianDate, {
      localNumbers: false,
    });
  } catch {
    return "";
  }
};

const EthiopianDatePickerModal: React.FC<EthiopianDatePickerModalProps> = ({
  value,
  onChange,
}) => {
  const datePickerRef = useRef<HTMLDivElement>(null);
  const pickerElementRef = useRef<CalendarsPickerElement | null>(null);
  const isSyncingRef = useRef(false);
  const lastSyncedDateRef = useRef<string>("");
  const onChangeRef = useRef(onChange);
  const initialValueRef = useRef(value);
  const [selectedDate, setSelectedDate] = useState<string>("");
  const [selectedTime, setSelectedTime] = useState<string>(
    getCurrentEthiopianTimeInput,
  );
  const selectedTimeRef = useRef(selectedTime);

  useEffect(() => {
    onChangeRef.current = onChange;
  }, [onChange]);

  useEffect(() => {
    selectedTimeRef.current = selectedTime;
  }, [selectedTime]);

  useEffect(() => {
    if (!(value instanceof Date) || Number.isNaN(value.getTime())) {
      return;
    }

    const nextDate = toEthiopianDateInput(value);
    // selectedTime is stored as EAT 24h "HH:mm"; the Ethiopian clock shown in
    // the picker is derived from it via eatTimeToEthiopianClock. Seed it with
    // EAT 24h here so the picker (and the trigger placeholder) agree.
    const nextTime = toEthiopianTimeInput(value);

    setSelectedDate(nextDate);
    setSelectedTime(nextTime);

    if (
      !pickerElementRef.current ||
      !$ ||
      !$.calendars ||
      !nextDate ||
      lastSyncedDateRef.current === nextDate
    ) {
      return;
    }

    try {
      const calendar = $.calendars.instance("ethiopian", "am");
      const [year, month, day] = nextDate.split("-").map(Number);
      if (
        Number.isFinite(year) &&
        Number.isFinite(month) &&
        Number.isFinite(day)
      ) {
        const ethDate = calendar.newDate(year, month, day);
        isSyncingRef.current = true;
        try {
          pickerElementRef.current.calendarsPicker("setDate", ethDate);
          lastSyncedDateRef.current = nextDate;
        } finally {
          isSyncingRef.current = false;
        }
      }
    } catch {
      // ignore third-party plugin errors when syncing selection
    }
  }, [value]);

  useEffect(() => {
    if (!$ || !$.calendars || !datePickerRef.current) {
      return;
    }

    const pickerHost = datePickerRef.current;
    const calendar = $.calendars.instance("ethiopian", "am");
    const picker = $(pickerHost) as unknown as CalendarsPickerElement;
    pickerElementRef.current = picker;

    picker.calendarsPicker({
      calendar,
      dateFormat: "yyyy-mm-dd",
      onSelect: (dates: CalendarsDate[]) => {
        if (isSyncingRef.current) return;
        if (!dates?.length) return;

        const date = dates[0].toString();
        lastSyncedDateRef.current = date;
        setSelectedDate(date);
        onChangeRef.current(`${date} ${selectedTimeRef.current}`);
      },
    } as CalendarsPickerOptions);

    const initialValue = initialValueRef.current;
    if (initialValue instanceof Date && !Number.isNaN(initialValue.getTime())) {
      try {
        const initialEth = toEthiopianDateInput(initialValue);
        if (initialEth) {
          const [y, m, d] = initialEth.split("-").map(Number);
          if (Number.isFinite(y) && Number.isFinite(m) && Number.isFinite(d)) {
            isSyncingRef.current = true;
            try {
              picker.calendarsPicker("setDate", calendar.newDate(y, m, d));
              lastSyncedDateRef.current = initialEth;
            } finally {
              isSyncingRef.current = false;
            }
          }
        }
      } catch {
        // ignore third-party plugin errors when seeding initial date
      }
    }

    return () => {
      try {
        picker.calendarsPicker("destroy");
      } catch {
        // ignore cleanup errors from third-party plugin
      }
      pickerElementRef.current = null;
      lastSyncedDateRef.current = "";
    };
  }, []);

  // selectedTime is always EAT 24h "HH:mm"; the Ethiopian clock is a display-only
  // projection so the stored value (and backend payload) stays unchanged.
  const ethiopianClock = eatTimeToEthiopianClock(selectedTime);

  const applyEthiopianClock = (next: EthiopianClockParts) => {
    const eatTime = ethiopianClockToEatTime(next);
    setSelectedTime(eatTime);

    if (selectedDate) {
      onChange(`${selectedDate} ${eatTime}`);
    }
  };

  const handleHourChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
    const hour = Number(e.target.value);
    applyEthiopianClock({
      ...ethiopianClock,
      hour: Number.isFinite(hour) ? hour : ethiopianClock.hour,
    });
  };

  const handleMinuteChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const parsed = Number(e.target.value);
    const minute = Number.isFinite(parsed)
      ? Math.max(0, Math.min(59, Math.trunc(parsed)))
      : 0;
    applyEthiopianClock({ ...ethiopianClock, minute });
  };

  const handlePeriodChange = (period: EthiopianClockPeriod) => {
    applyEthiopianClock({ ...ethiopianClock, period });
  };

  return (
    <div className="ethiopian-picker-surface w-full space-y-3">
      <div
        ref={datePickerRef}
        className="ethiopian-picker-calendar w-full overflow-hidden rounded-md border border-gray-200 dark:border-gray-700"
      />
      <div className="space-y-1">
        <p className="text-xs font-medium text-gray-600 dark:text-gray-300">
          ሰዓት
        </p>
        <div className="flex items-center gap-2">
          <select
            aria-label="ሰዓት"
            className="h-10 flex-1 rounded-md border border-gray-300 bg-white px-2 text-sm text-gray-800 shadow-sm transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
            value={ethiopianClock.hour}
            onChange={handleHourChange}>
            {ETHIOPIAN_HOUR_OPTIONS.map((h) => (
              <option key={h} value={h}>
                {h}
              </option>
            ))}
          </select>
          <span className="text-sm font-medium text-gray-600 dark:text-gray-300">
            :
          </span>
          <input
            type="number"
            min={0}
            max={59}
            aria-label="ደቂቃ"
            className="h-10 w-16 rounded-md border border-gray-300 bg-white px-2 text-center text-sm text-gray-800 shadow-sm transition-colors focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/20 dark:border-gray-600 dark:bg-gray-800 dark:text-gray-100"
            value={ethiopianClock.minute.toString().padStart(2, "0")}
            onChange={handleMinuteChange}
          />
          <div className="flex overflow-hidden rounded-md border border-gray-300 dark:border-gray-600">
            <button
              type="button"
              onClick={() => handlePeriodChange("day")}
              className={cn(
                "px-3 py-2 text-sm transition-colors",
                ethiopianClock.period === "day"
                  ? "bg-primary text-white"
                  : "bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700",
              )}>
              ቀን
            </button>
            <button
              type="button"
              onClick={() => handlePeriodChange("night")}
              className={cn(
                "px-3 py-2 text-sm transition-colors",
                ethiopianClock.period === "night"
                  ? "bg-primary text-white"
                  : "bg-white text-gray-700 hover:bg-gray-50 dark:bg-gray-800 dark:text-gray-200 dark:hover:bg-gray-700",
              )}>
              ሌሊት
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export function convertEthiopianToGregorian(ethiopianDateTime: string): Date {
  if (!ethiopianDateTime || typeof ethiopianDateTime !== "string") {
    return new Date(NaN);
  }

  const normalizedDateTime = normalizeDigits(ethiopianDateTime.trim());
  const [datePart, timePart = "00:00"] = normalizedDateTime.split(" ");

  if (!datePart || !datePart.match(/^\d{4}-\d{2}-\d{2}$/)) {
    return new Date(NaN);
  }

  const cal = $.calendars.instance("ethiopian", "am");
  const [year, month, day] = datePart.split("-").map(Number);

  if (isNaN(year) || isNaN(month) || isNaN(day)) {
    return new Date(NaN);
  }

  try {
    const ethDate = cal.newDate(year, month, day);
    const gregDate = ethDate.toJSDate();

    const [hours, minutes] = timePart.split(":").map(Number);

    if (
      isNaN(hours) ||
      isNaN(minutes) ||
      hours < 0 ||
      hours > 23 ||
      minutes < 0 ||
      minutes > 59
    ) {
      return new Date(NaN);
    }

    const utcDate = new Date(
      Date.UTC(
        gregDate.getFullYear(),
        gregDate.getMonth(),
        gregDate.getDate(),
        hours - ETHIOPIAN_UTC_OFFSET_HOURS,
        minutes,
        0,
        0,
      ),
    );

    return utcDate;
  } catch (error) {
    console.error(
      "[convertEthiopianToGregorian] Error during conversion:",
      error,
    );
    return new Date(NaN);
  }
}

export function formatEthiopianDate(ethiopianDate: string | Date): string {
  if (!ethiopianDate) return "";

  if (ethiopianDate instanceof Date) {
    const day = ethiopianDate.getDate().toString().padStart(2, "0");
    const month = (ethiopianDate.getMonth() + 1).toString().padStart(2, "0");
    const year = ethiopianDate.getFullYear();
    return `${day}/${month}/${year}`;
  }

  const parts = ethiopianDate.split("-");
  if (parts.length !== 3) return ethiopianDate;

  const [year, month, day] = parts;
  return `${day.padStart(2, "0")}/${month.padStart(2, "0")}/${year}`;
}

export default EthiopianDatePickerModal;
