import { useId, useLayoutEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
import { CalendarLegend } from "./CalendarLegend";
import { Field } from "./Field";
import { Button } from "./primitives";
import { CalendarDays, Keyboard } from "lucide-react";
import {
  describeDay,
  calendarTimeline,
  progressShade,
  MONTH_VIEWS,
  visibleCalendarDays,
  type CalendarDay,
  type MonthView,
} from "./calendar-model";

export function CalendarHeatmap({
  days: allDays,
  unit,
  label,
  color = "#111111",
  emptyColor = "#eeeeee",
  compact = false,
  selectedDate: controlledDate,
  onSelectDate,
  historyLabel,
  legend,
}: {
  days: CalendarDay[];
  unit: string;
  label: string;
  color?: string;
  emptyColor?: string;
  compact?: boolean;
  selectedDate?: string;
  onSelectDate?: (date: string) => void;
  historyLabel?: string;
  legend?: ReactNode;
}) {
  const [months, setMonths] = useState<MonthView | "custom">(12);
  const [customRange, setCustomRange] = useState<{ from: string; to: string } | null>(null);
  const [rangeError, setRangeError] = useState("");
  const latestDate =
    allDays.findLast((day) => day.state !== "future")?.date ??
    allDays.at(-1)?.date;
  // A date selected in the editor must stay visible even outside the current window.
  const anchor =
    controlledDate && allDays.some((day) => day.date === controlledDate)
      ? controlledDate
      : latestDate;
  const [localSelectedDate, setLocalSelectedDate] = useState(anchor);
  const selectedDate = controlledDate ?? localSelectedDate;
  function setSelectedDate(date: string) {
    if (onSelectDate) onSelectDate(date);
    else setLocalSelectedDate(date);
  }
  const days = months === "custom" && customRange
    ? allDays.filter((day) => day.date >= customRange.from && day.date <= customRange.to)
    : anchor ? visibleCalendarDays(allDays, months === "custom" ? 12 : months, anchor) : [];
  const selected =
    days.find((day) => day.date === selectedDate) ??
    days.find((day) => day.date === anchor) ??
    days[0];
  // Date keys and the shared timeline remain stable when the chart resizes.
  const timeline = calendarTimeline(days);
  const buttons = useRef<(HTMLButtonElement | null)[]>([]);
  const inspectorId = useId();
  const instructionsId = useId();
  const rangeErrorId = useId();
  const pendingFocusDate = useRef<string | null>(null);
  const [focusRequest, setFocusRequest] = useState(0);
  const selectedIndex = selected ? days.indexOf(selected) : -1;
  // Controlled editors may reject a selection to preserve an unsaved draft.
  // Only move focus after that date is actually selected, using the new window.
  useLayoutEffect(() => {
    if (pendingFocusDate.current !== null)
      buttons.current[selectedIndex]?.focus();
    pendingFocusDate.current = null;
  }, [focusRequest, selectedIndex, selected?.date]);
  function selectAndFocus(date: string) {
    pendingFocusDate.current = date;
    // A rejected controlled selection still needs a render to restore click
    // focus and consume the request, so a later update cannot reuse it.
    setFocusRequest((request) => request + 1);
    setSelectedDate(date);
  }
  if (!selected) return <p>No calendar dates to display.</p>;
  return (
    <div
      className={`ds-calendar${compact ? " ds-calendar--compact" : ""}`}
      data-months={months}
      style={{ "--calendar-weeks": timeline.weeks, "--calendar-color": color } as CSSProperties}
    >
      <div className="ds-calendar-range-toolbar">
        <span className="ds-calendar-range-label" aria-live="polite">
          {new Date(`${days[0]!.date}T12:00:00Z`).toLocaleDateString("en", {
            month: "short",
            day: months === "custom" ? "numeric" : undefined,
            year: "numeric",
            timeZone: "UTC",
          })}{" "}
          –{" "}
          {new Date(`${days.at(-1)!.date}T12:00:00Z`).toLocaleDateString("en", {
            month: "short",
            day: months === "custom" ? "numeric" : undefined,
            year: "numeric",
            timeZone: "UTC",
          })}
        </span>
        <div
          className="ds-month-views"
          role="group"
          aria-label={`${label} time range`}
        >
          {MONTH_VIEWS.map((value) => (
            <button
              key={value}
              type="button"
              aria-label={`${value} months`}
              aria-pressed={months === value}
              onClick={() => setMonths(value)}
            >
              {value}m
            </button>
          ))}
          <button
            type="button"
            aria-pressed={months === "custom"}
            onClick={() => {
              if (!customRange) setCustomRange({ from: days[0]!.date, to: days.at(-1)!.date });
              setRangeError("");
              setMonths("custom");
            }}
          >
            Custom
          </button>
        </div>
      </div>
      {months === "custom" && customRange && (
        <form
          className="ds-calendar-custom-range"
          aria-label={`${label} custom timeframe`}
          noValidate
          onSubmit={(event) => {
            event.preventDefault();
            const form = event.currentTarget;
            const start = form.elements.namedItem("from") as HTMLInputElement;
            const end = form.elements.namedItem("to") as HTMLInputElement;
            const isAvailable = (input: HTMLInputElement) =>
              input.validity.valid && input.value !== "" &&
              input.value >= allDays[0]!.date && input.value <= allDays.at(-1)!.date;
            if (!isAvailable(start) || !isAvailable(end)) {
              setRangeError("Choose both dates within the available history.");
              (!isAvailable(start) ? start : end).focus();
              return;
            }
            if (start.value > end.value) {
              setRangeError("The end date must be on or after the start date.");
              end.focus();
              return;
            }
            if (!allDays.some((day) => day.date >= start.value && day.date <= end.value)) {
              setRangeError("There are no calendar dates in this range.");
              return;
            }
            setCustomRange({ from: start.value, to: end.value });
            setRangeError("");
          }}
        >
          {(["from", "to"] as const).map((name) => (
            <Field key={name} label={name === "from" ? "Start date" : "End date"}>
              {(id) => (
                <input
                  id={id}
                  name={name}
                  type="date"
                  required
                  min={allDays[0]!.date}
                  max={allDays.at(-1)!.date}
                  defaultValue={customRange[name]}
                  aria-invalid={rangeError ? true : undefined}
                  aria-describedby={rangeError ? rangeErrorId : undefined}
                  onChange={() => setRangeError("")}
                />
              )}
            </Field>
          ))}
          <Button type="submit" variant="secondary">Apply range</Button>
          {rangeError && <p id={rangeErrorId} className="ds-calendar-range-error" role="alert">{rangeError}</p>}
        </form>
      )}
      <div
        className="ds-calendar-viewport"
        role="group"
        aria-label={label}
        aria-describedby={instructionsId}
      >
        <div className="ds-calendar-timeline">
          <div className="ds-calendar-months" aria-hidden="true">
            {timeline.months.map(({ month, column }, index) => (
              <div className="ds-calendar-month-label" key={month} style={{
                gridColumn: `${column} / ${timeline.months[index + 1]?.column ?? timeline.weeks + 1}`,
              }}>
                {new Date(`${month}-01T12:00:00Z`).toLocaleDateString("en", {
                  month: "short", timeZone: "UTC",
                })}
              </div>
            ))}
          </div>
          <div className="ds-calendar-weekdays" aria-hidden="true">
            <span>Mon</span>
            <span>Wed</span>
            <span>Fri</span>
          </div>
          <div className="ds-calendar-grid">
            {timeline.entries.map(({ day, index, column, row }) => (
              <button
                key={day.date}
                ref={(element) => {
                  buttons.current[index] = element;
                }}
                type="button"
                className={`ds-day ds-day--${day.state}`}
                style={{
                  gridColumn: column,
                  gridRow: row,
                  backgroundColor:
                    day.state === "not-due"
                      ? "transparent"
                      : day.state === "future"
                        ? emptyColor
                        : progressShade(day, color),
                }}
                aria-label={`${day.date}: ${describeDay(day, unit)}`}
                title={`${day.date}: ${describeDay(day, unit)}`}
                aria-pressed={day.date === selected.date}
                aria-describedby={instructionsId}
                tabIndex={day.date === selected.date ? 0 : -1}
                onClick={() => selectAndFocus(day.date)}
                onKeyDown={(event) => {
                  const offset = {
                    ArrowDown: 1,
                    ArrowUp: -1,
                    ArrowRight: 7,
                    ArrowLeft: -7,
                  }[event.key];
                  const next =
                    event.key === "Home"
                      ? 0
                      : event.key === "End"
                        ? days.length - 1
                        : offset !== undefined
                          ? Math.max(
                              0,
                              Math.min(days.length - 1, index + offset),
                            )
                          : undefined;
                  if (next !== undefined) {
                    event.preventDefault();
                    selectAndFocus(days[next]!.date);
                  }
                }}
              />
            ))}
          </div>
        </div>
      </div>
      {legend ?? <CalendarLegend days={days} color={color} label={label} />}
      <div className="ds-calendar-caption">
        <span>
          {months === "custom" ? `${days.length} ${days.length === 1 ? "day" : "days"}` : `${months} months`}{" "}
          <span className="ds-muted">
            /{" "}
            {historyLabel ??
              (compact ? "Demo history" : "Illustrative history")}
          </span>
        </span>
        <details className="ds-calendar-help">
          <summary><Keyboard size={16} aria-hidden="true" /> Keyboard shortcuts</summary>
          <p id={instructionsId}>
            Select a day. Up/down: one day. Left/right: one week. Home/end: first/last date.
          </p>
        </details>
      </div>
      <div id={inspectorId} className="ds-date-inspector" aria-live="polite">
        <div className="ds-date-inspector-heading">
          <CalendarDays size={22} aria-hidden="true" />
          <div>
            <span className="ds-eyebrow">SELECTED DAY</span>
            <time dateTime={selected.date}>
              {new Date(`${selected.date}T12:00:00Z`).toLocaleDateString("en", {
                month: "long",
                day: "numeric",
                year: "numeric",
                timeZone: "UTC",
              })}
            </time>
            <span className="ds-date-inspector-status">{describeDay(selected, unit)}</span>
          </div>
        </div>
      </div>
    </div>
  );
}
