import type { KeyboardEvent, ReactNode } from "react"
import { useEffect, useRef } from "react"
import { FOCUS_RING_CLASS } from "../intentStyles.ts"
import { toClassName } from "../toClassName.ts"
import { VisuallyHidden } from "../VisuallyHidden/VisuallyHidden.tsx"
import type { PlainDate } from "./plainDate.ts"
import {
addDays,
addMonths,
clampPlainDate,
comparePlainDates,
formatPlainDate,
getDaysInMonth,
getIsSameDay,
getIsWithinRange,
getWeekday,
getWeekdayNames,
toIsoDate,
} from "./plainDate.ts"
export type DateGridProps = {
firstDayOfWeek: number
focusedDate: PlainDate
/**
* The month this grid draws — any date inside it. `DatePicker`
* renders two of these in range mode, so the month is a prop
* rather than derived from the focus.
*/
gridMonth: PlainDate
/**
* Whether the keyboard is *in* the grid. Only the grid that owns
* focus pulls it onto its focused cell; the other one would rip
* focus back and forth between the two months on every arrow key.
*/
isGridFocused: boolean
isRange: boolean
label: string
locale?: string
maximum?: PlainDate
minimum?: PlainDate
onFocusDate: (date: PlainDate) => void
onHoverDate: (date: null | PlainDate) => void
onSelectDate: (date: PlainDate) => void
/**
* The end of the band being drawn *right now* — the committed end,
* or the hovered/keyboard-focused day while the second click is
* still outstanding. Painting the pending half is the difference
* between a range picker you can aim and one you have to guess at.
*/
previewEnd: null | PlainDate
rangeStart: null | PlainDate
selectedDates: readonly PlainDate[]
today: PlainDate
}
const CELL_BASE_CLASS =
"cursor-pointer rounded-md p-0 text-center"
const DAY_BASE_CLASS =
"mx-auto flex size-8 items-center justify-center rounded-md border border-transparent text-xs transition-colors duration-(--duration-fast) ease-standard cq-xs:size-9 cq-xs:text-sm cq-sm:size-10"
/**
* One month, as a real `
`.
*
* ### Why the roving focus is arithmetic and not `useRovingFocus`
*
* `@charcuterie/logic`'s `RovingFocus` is a **registration** kind:
* members register from an effect and `next`/`previous`/`first`/
* `last` walk that list. `SegmentedControl` and `Tabs` are exactly
* that shape, and a calendar is not, for two reasons that are worth
* stating rather than discovering:
*
* - The movement is two-dimensional and it is *date* arithmetic, not
* list traversal. ArrowDown is "+7 days", which is a different
* thing from "the seventh next registered member" the moment a
* month does not start on the first column.
* - The move can leave the rendered set entirely. ArrowRight on 31
* August lands on 1 September, which is a day this grid has not
* rendered and no member has registered. A registration list
* cannot express a move to something that does not exist yet; a
* day number can, and re-rendering the grid around it is the
* correct response.
*
* So the focused *date* is the state and the DOM follows it. This is
* the same conclusion `Field` reached about `createLinkedIds` — the
* state kind is real and this is not its shape.
*
* ### The cell is the gridcell
*
* `