import { createSignal } from "solid-js";
import type { JSX } from "solid-js";
import { createControllableSignal } from "../../primitives/create-controllable-signal";
import { CalendarContextProvider } from "./calendar-context";
import type { CalendarRootProps } from "./calendar.props";

/** Converts an ISO date string to a Date at midnight local time. */
function parseISO(iso: string): Date {
  const [y, m, d] = iso.split("-").map(Number);
  return new Date(y, m - 1, d);
}

/** Formats a Date as YYYY-MM-DD. */
function toISO(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}`;
}

/** Returns all Date objects to fill the calendar grid for a given month, padded to complete weeks. */
function buildMonthGrid(year: number, month: number, weekStartsOn: number): Date[] {
  const firstDay = new Date(year, month, 1);
  const lastDay = new Date(year, month + 1, 0);
  const startOffset = (firstDay.getDay() - weekStartsOn + 7) % 7;
  const endOffset = (6 - lastDay.getDay() + weekStartsOn) % 7;
  const days: Date[] = [];
  for (let i = startOffset; i > 0; i--) {
    days.push(new Date(year, month, 1 - i));
  }
  for (let d = 1; d <= lastDay.getDate(); d++) {
    days.push(new Date(year, month, d));
  }
  for (let i = 1; i <= endOffset; i++) {
    days.push(new Date(year, month + 1, i));
  }
  return days;
}

/** Returns ordered weekday short names using Intl, starting from weekStartsOn. */
function buildWeekDayNames(locale: string, weekStartsOn: number): string[] {
  const names: string[] = [];
  for (let i = 0; i < 7; i++) {
    const day = (weekStartsOn + i) % 7;
    const date = new Date(2017, 0, day + 1);
    names.push(new Intl.DateTimeFormat(locale, { weekday: "short" }).format(date));
  }
  return names;
}

/**
 * Root component for Calendar. Manages selected date and display month/year state.
 * Provides context to all Calendar sub-components. Renders no DOM elements.
 */
export function CalendarRoot(props: CalendarRootProps): JSX.Element {
  const today = new Date();
  const initialDate = props.defaultValue ? parseISO(props.defaultValue) : today;

  const [selectedDate, setSelectedDate] = createControllableSignal<string | undefined>({
    value: () => props.value,
    defaultValue: () => props.defaultValue,
    onChange: props.onValueChange,
  });

  const [displayMonth, setDisplayMonth] = createSignal(props.month ?? initialDate.getMonth());
  const [displayYear, setDisplayYear] = createSignal(props.year ?? initialDate.getFullYear());
  const [focusedDate, setFocusedDate] = createSignal<Date>(initialDate);

  const locale = () => props.locale ?? "en-US";
  const weekStartsOn = () => props.weekStartsOn ?? 0;

  const isDateDisabled = (iso: string): boolean => {
    if (props.disabled) return true;
    if (props.minDate && iso < props.minDate) return true;
    if (props.maxDate && iso > props.maxDate) return true;
    return false;
  };

  const goToPrevMonth = () => {
    const m = displayMonth();
    const y = displayYear();
    const newMonth = m === 0 ? 11 : m - 1;
    const newYear = m === 0 ? y - 1 : y;
    setDisplayMonth(newMonth);
    setDisplayYear(newYear);
    props.onMonthChange?.(newMonth, newYear);
  };

  const goToNextMonth = () => {
    const m = displayMonth();
    const y = displayYear();
    const newMonth = m === 11 ? 0 : m + 1;
    const newYear = m === 11 ? y + 1 : y;
    setDisplayMonth(newMonth);
    setDisplayYear(newYear);
    props.onMonthChange?.(newMonth, newYear);
  };

  const selectDate = (iso: string) => {
    if (!isDateDisabled(iso)) setSelectedDate(iso);
  };

  const ctx = {
    focusedDate,
    selectedDate,
    displayMonth,
    displayYear,
    locale,
    weekStartsOn,
    minDate: () => props.minDate,
    maxDate: () => props.maxDate,
    disabled: () => props.disabled ?? false,
    selectDate,
    focusDate: setFocusedDate,
    goToPrevMonth,
    goToNextMonth,
    isDateDisabled,
    isDateSelected: (iso: string) => selectedDate() === iso,
    isDateToday: (iso: string) => iso === toISO(today),
    getDaysInMonth: () => buildMonthGrid(displayYear(), displayMonth(), weekStartsOn()),
    getWeekDayNames: () => buildWeekDayNames(locale(), weekStartsOn()),
  };

  return (
    <CalendarContextProvider value={ctx}>
      {props.children}
    </CalendarContextProvider>
  );
}
