"use client"

import * as React from "react"
import type { DayProps } from "react-day-picker"

import { useLocale } from "@/components/locale-provider"
import { Calendar } from "@/components/ui/calendar"
import {
  Tooltip,
  TooltipContent,
  TooltipProvider,
  TooltipTrigger,
} from "@/components/ui/tooltip"
import type { Holiday } from "@/lib/holiday-csv"
import { fullDay } from "@/lib/i18n/dates"
import { cn } from "@/lib/utils"

/**
 * The year, twelve months at once, with the shut days marked and named.
 *
 * A calendar and not a list, because the question somebody brings here is "when
 * are we shut" and a list of eight dates does not answer it at a glance: you
 * cannot see from it that Songkran is three days in a row, or that two of them
 * fall on a Sunday and cost nothing.
 *
 * The name is in a tooltip, which appears on hover **and on keyboard focus**,
 * so the marked days are given a tab stop. That is what makes the colour more
 * than decoration: a mark whose meaning is only reachable with a mouse is a
 * mark half the room cannot read.
 *
 * No selection and no navigation. Nothing here is picked and there is one year
 * on the page, so a day that highlighted under the cursor would be promising
 * something that does not happen.
 */

/**
 * The captions are formatted here rather than by a date-fns locale.
 *
 * date-fns's Thai does **not** use the Buddhist era: it renders August 2026 as
 * สิงหาคม 2026, where every document in the building says 2569. `Intl` with an
 * explicit calendar does, and this is the only place the calendar's own chrome
 * would otherwise disagree with the rest of the interface.
 */
function tag(locale: "en" | "th"): string {
  return locale === "th" ? "th-TH-u-ca-buddhist" : "en-GB"
}

/** A `YYYY-MM-DD` day as a Date, in UTC, so nothing shifts it. */
function asDate(date: string): Date {
  const [y, m, d] = date.split("-").map(Number)
  return new Date(Date.UTC(y!, m! - 1, d!))
}

/** The same, back again, without going through a timezone. */
function asKey(date: Date): string {
  return [
    date.getUTCFullYear(),
    String(date.getUTCMonth() + 1).padStart(2, "0"),
    String(date.getUTCDate()).padStart(2, "0"),
  ].join("-")
}

export function YearCalendar({
  year,
  holidays,
}: {
  year: number
  holidays: Holiday[]
}) {
  const locale = useLocale()
  const named = React.useMemo(
    () => new Map(holidays.map((h) => [h.date, h.name])),
    [holidays],
  )
  const days = React.useMemo(
    () => holidays.map((h) => asDate(h.date)),
    [holidays],
  )

  /**
   * A custom cell, because without a selection mode this calendar renders the
   * day as a text node straight into the `td`: there is nothing to hang a
   * tooltip on and nothing to focus.
   *
   * Only the shut days get one. Giving every day of a year a tab stop would put
   * 365 of them between somebody and the next control on the page.
   */
  const Day = React.useCallback(
    ({ day, modifiers, className, children, ...rest }: DayProps) => {
      const key = asKey(day.date)
      const name = named.get(key)

      if (!name || modifiers.outside) {
        return (
          <td className={className} {...rest}>
            {children}
          </td>
        )
      }

      return (
        <td className={className} {...rest}>
          <Tooltip>
            <TooltipTrigger
              // A span and not a button: nothing happens when it is pressed,
              // and a button that does nothing is a promise this page does not
              // keep. The tab stop is what the tooltip needs, and that is all.
              render={
                <span
                  tabIndex={0}
                  className="flex size-full cursor-help items-center justify-center rounded-(--cell-radius) focus-visible:ring-3 focus-visible:ring-ring/50 focus-visible:outline-none"
                />
              }
            >
              {children}
            </TooltipTrigger>
            <TooltipContent>
              <p className="font-medium">{name}</p>
              <p className="opacity-80">{fullDay(key, locale)}</p>
            </TooltipContent>
          </Tooltip>
        </td>
      )
    },
    [named, locale],
  )

  return (
    <TooltipProvider>
      <Calendar
        month={new Date(Date.UTC(year, 0, 1))}
        numberOfMonths={12}
        hideNavigation
        showOutsideDays={false}
        timeZone="UTC"
        components={{ Day }}
        formatters={{
          formatCaption: (date) =>
            date.toLocaleDateString(tag(locale), {
              timeZone: "UTC",
              month: "long",
              year: "numeric",
            }),
          formatWeekdayName: (date) =>
            date.toLocaleDateString(tag(locale), {
              timeZone: "UTC",
              weekday: "narrow",
            }),
        }}
        modifiers={{ holiday: days }}
        /*
          The mark goes on the cell, and the cell has to be told to centre. The
          `td` is `aspect-square` with `text-center`, which centres across and
          not down, so the number sat at the top of a tall coloured box.

          `empty:` because a month grid still renders the days belonging to its
          neighbours, hidden by `showOutsideDays={false}` but present as empty
          cells. Three of them carry a holiday's date, and without this they
          were three coloured squares with no number in them.
        */
        modifiersClassNames={{
          holiday: cn(
            "flex items-center justify-center rounded-(--cell-radius)",
            "bg-primary font-semibold text-primary-foreground",
            "empty:bg-transparent",
          ),
        }}
        classNames={{
          /*
            Each month is only as wide as it needs to be and sits in the middle
            of its column. Left to stretch, it filled the column, the day cells
            widened with it, and `aspect-square` made every highlighted day a
            tall rectangle with the number stranded at the top.
          */
          months:
            "grid grid-cols-1 justify-items-center gap-x-8 gap-y-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4",
          month: "flex w-fit flex-col gap-2",
          /*
            A minimum width on every cell. The month is only as wide as it needs
            to be, so without this each column took the width of its widest
            number and the two-digit days ran into each other: 25262728 with no
            gap anywhere.
          */
          day: "aspect-square min-w-(--cell-size) rounded-(--cell-radius) p-0 text-center select-none flex items-center justify-center",
          weekday:
            "min-w-(--cell-size) text-[0.75rem] font-normal text-muted-foreground select-none",
          // Above each month and centred over it, rather than in the navigation
          // row, which is where it belongs when there is nothing to navigate.
          month_caption: "flex h-8 items-center justify-center px-1",
        }}
        // The border and radius came from a second `className` on this same
        // element, which is not something JSX allows. Merged rather than dropped.
        className="w-full rounded-lg border p-0 [--cell-size:--spacing(8)]"
      />
    </TooltipProvider>
  )
}
