"use client"

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

import { Button } from "@/components/ui/button"
import { Calendar, CalendarDayButton } from "@/components/ui/calendar"
import {
  Card,
  CardAction,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { Checkbox } from "@/components/ui/checkbox"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import { Input } from "@/components/ui/input"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import {
  IconCheck,
  IconCircleCheck,
  IconCircleDot,
  IconPlus,
  IconX,
} from "@tabler/icons-react"
import { useLocale, useT } from "@/components/locale-provider"
import { isIsoDate, type WorkPattern } from "@/lib/calendar"
import type { Locale, Translate } from "@/lib/i18n"
import { calendarChrome, fullDay, monthName, shortDay } from "@/lib/i18n/dates"
import type { MessageKey } from "@/lib/i18n/messages"
import {
  eachDay,
  explain,
  generatedLine,
  rangeLine,
  read,
  skipLine,
  type Line,
  type PlainLine,
  type Reading,
  type Weekday,
} from "@/lib/recurrence-preview"
import type { Recurrence, Template } from "@/lib/recurrence"

/**
 * The recurrence engine, made arguable.
 *
 * Every answer on this screen comes from `isDueOn`, and every sentence beside
 * it is assembled from what the engine returned on the way there. Nothing here
 * decides a date. If the screen and the function disagree, the screen is the
 * one that is wrong.
 *
 * The two work patterns are shown together rather than switched between,
 * because the interesting cases are exactly the ones where they part company:
 * a Saturday that exists for one holder and not for the other.
 *
 * Every word on it comes from the dictionary. The person this screen exists for
 * is the one who will never open `lib/recurrence.ts`, and that person reads
 * Thai. Nothing it imports reaches below `lib/i18n`, `lib/calendar` or
 * `lib/recurrence*`, all of which are pure, because this is a client component
 * and what it imports it puts in the browser.
 */

const PATTERNS: WorkPattern[] = ["mon_fri", "mon_sat"]

const PATTERN_WORD: Record<WorkPattern, MessageKey> = {
  mon_fri: "recurrence.pattern.monFri",
  mon_sat: "recurrence.pattern.monSat",
}

const RECURRENCE_WORD: Record<Recurrence, MessageKey> = {
  daily: "occurrence.daily",
  weekly: "occurrence.weekly",
  monthly: "occurrence.monthly",
  quarterly: "occurrence.quarterly",
}

/** 1 Monday to 7 Sunday, which is what the database stores. */
const WEEKDAY_WORD: Record<Weekday, MessageKey> = {
  1: "weekday.monday",
  2: "weekday.tuesday",
  3: "weekday.wednesday",
  4: "weekday.thursday",
  5: "weekday.friday",
  6: "weekday.saturday",
  7: "weekday.sunday",
}

const WEEKDAYS: Weekday[] = [1, 2, 3, 4, 5, 6, 7]

type Setup = {
  recurrence: Recurrence
  dayOfWeek: number
  dayOfMonth: number
  active: boolean
  from: string
  to: string
  holidays: string[]
}

/**
 * Each case is one rule that breaks quietly, with the range and the calendar
 * that expose it. They are the cases the engine's own tests pin.
 */
const CASES: { title: MessageKey; rule: MessageKey; setup: Setup }[] = [
  {
    title: "recurrence.case.nextMonth",
    rule: "recurrence.case.nextMonthRule",
    setup: {
      recurrence: "monthly",
      dayOfWeek: 4,
      dayOfMonth: 31,
      active: true,
      from: "2026-05-28",
      to: "2026-06-03",
      holidays: [],
    },
  },
  {
    title: "recurrence.case.shortMonth",
    rule: "recurrence.case.shortMonthRule",
    setup: {
      recurrence: "monthly",
      dayOfWeek: 4,
      dayOfMonth: 31,
      active: true,
      from: "2026-04-27",
      to: "2026-05-02",
      holidays: [],
    },
  },
  {
    title: "recurrence.case.february",
    rule: "recurrence.case.februaryRule",
    setup: {
      recurrence: "monthly",
      dayOfWeek: 4,
      dayOfMonth: 31,
      active: true,
      from: "2026-02-25",
      to: "2026-03-03",
      holidays: [],
    },
  },
  {
    title: "recurrence.case.saturday",
    rule: "recurrence.case.saturdayRule",
    setup: {
      recurrence: "monthly",
      dayOfWeek: 4,
      dayOfMonth: 15,
      active: true,
      from: "2026-08-13",
      to: "2026-08-18",
      holidays: [],
    },
  },
  {
    title: "recurrence.case.holiday",
    rule: "recurrence.case.holidayRule",
    setup: {
      recurrence: "monthly",
      dayOfWeek: 4,
      dayOfMonth: 15,
      active: true,
      from: "2026-09-14",
      to: "2026-09-18",
      holidays: ["2026-09-15", "2026-09-16"],
    },
  },
  {
    title: "recurrence.case.impossible",
    rule: "recurrence.case.impossibleRule",
    setup: {
      recurrence: "weekly",
      dayOfWeek: 6,
      dayOfMonth: 15,
      active: true,
      from: "2026-08-03",
      to: "2026-08-16",
      holidays: [],
    },
  },
]

/** A wall date from a picker's local midnight, and back again. */
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}`
}

function fromIso(date: string): Date {
  const [y, m, d] = date.split("-").map(Number)
  return new Date(y!, m! - 1, d!)
}

/**
 * The one place a `Line` becomes words.
 *
 * `explain` hands back a key and the data beside it and stops there, because it
 * does not know who is reading. This does: a day goes through `shortDay` and a
 * month through `monthName`, so a Thai reader gets the Buddhist year and the
 * Thai month, and a weekday goes through the dictionary. Neither is ever glued
 * into a template string here.
 *
 * The checks run from the most-filled shape to the least, and the last of them
 * assigns to `PlainLine`: a shape added to `Line` with a value in it and not
 * handled above stops compiling here rather than reaching a reader with `{day}`
 * still in the sentence.
 */
function sentence(line: Line, t: Translate, locale: Locale): string {
  if ("pattern" in line) {
    const pattern = t(PATTERN_WORD[line.pattern])
    if ("day" in line) {
      return t(line.key, { day: shortDay(line.day, locale), pattern })
    }
    return "count" in line
      ? t(line.key, { pattern, count: line.count })
      : t(line.key, { pattern })
  }
  if ("date" in line) return t(line.key, { day: fullDay(line.date, locale) })
  if ("count" in line) {
    return t(line.key, {
      count: line.count,
      from: fullDay(line.from, locale),
      to: fullDay(line.to, locale),
    })
  }
  if ("fromMonth" in line) {
    return t(line.key, {
      from: monthName(line.fromMonth, locale),
      to: monthName(line.toMonth, locale),
    })
  }
  if ("weekday" in line) {
    return t(line.key, { day: t(WEEKDAY_WORD[line.weekday]) })
  }
  if ("dayOfMonth" in line) {
    return "month" in line
      ? t(line.key, {
          dayOfMonth: line.dayOfMonth,
          month: monthName(line.month, locale),
        })
      : t(line.key, { dayOfMonth: line.dayOfMonth })
  }
  if ("month" in line) {
    return "day" in line
      ? t(line.key, {
          month: monthName(line.month, locale),
          day: shortDay(line.day, locale),
        })
      : t(line.key, { month: monthName(line.month, locale) })
  }
  if ("day" in line) return t(line.key, { day: shortDay(line.day, locale) })

  const plain: PlainLine = line
  return t(plain.key)
}

function Holder({
  reading,
  template,
}: {
  reading: Reading
  template: Template
}) {
  const t = useT()
  const locale = useLocale()
  const lines = explain(reading, template)
  return (
    <div
      className={
        reading.due
          ? "flex flex-col gap-2 rounded-lg bg-primary/15 p-3 ring-1 ring-primary/50"
          : "flex flex-col gap-2 rounded-lg bg-muted/50 p-3 ring-1 ring-border"
      }
    >
      <div className="flex items-center gap-2 font-semibold">
        {reading.due ? (
          <IconCircleCheck className="size-5" aria-hidden />
        ) : (
          <IconCircleDot className="size-5 text-foreground/60" aria-hidden />
        )}
        {t(PATTERN_WORD[template.pattern])}
        <span className="ml-auto">
          {t(reading.due ? "recurrence.oneInstance" : "recurrence.nothing")}
        </span>
      </div>
      {lines.map((line) => (
        <p key={line.key} className="max-w-prose text-foreground/90">
          {sentence(line, t, locale)}
        </p>
      ))}
      {reading.holiday && reading.silence?.kind !== "holiday" ? (
        <p className="max-w-prose text-foreground/90">
          {t("recurrence.alsoAHoliday")}
        </p>
      ) : null}
      {reading.skipped?.length ? (
        <ul className="flex flex-col gap-1 border-l-2 border-primary/50 pl-3">
          {reading.skipped.map((skip) => (
            <li key={skip.date} className="text-foreground/80">
              {sentence(skipLine(skip, template.pattern), t, locale)}
            </li>
          ))}
        </ul>
      ) : null}
      {!reading.due &&
      reading.silence?.kind === "moved-away" &&
      reading.silence.skipped.length ? (
        <ul className="flex flex-col gap-1 border-l-2 border-border pl-3">
          {reading.silence.skipped.map((skip) => (
            <li key={skip.date} className="text-foreground/80">
              {sentence(skipLine(skip, template.pattern), t, locale)}
            </li>
          ))}
        </ul>
      ) : null}
    </div>
  )
}

export function RecurrenceExplorer() {
  const t = useT()
  const locale = useLocale()

  const [setup, setSetup] = React.useState<Setup>(CASES[0]!.setup)
  const [loaded, setLoaded] = React.useState<MessageKey | "">(CASES[0]!.title)
  const [month, setMonth] = React.useState(fromIso(CASES[0]!.setup.from))
  const [hideSilent, setHideSilent] = React.useState(false)
  const [newHoliday, setNewHoliday] = React.useState("")

  const set = <K extends keyof Setup>(key: K, value: Setup[K]) => {
    setSetup((s) => ({ ...s, [key]: value }))
    setLoaded("")
  }

  const loadCase = (title: MessageKey, next: Setup) => {
    setSetup(next)
    setLoaded(title)
    setMonth(fromIso(next.from))
  }

  const holidays = React.useMemo(
    () => new Set(setup.holidays),
    [setup.holidays],
  )

  const templateFor = React.useCallback(
    (pattern: WorkPattern): Template => ({
      id: `preview-${pattern}`,
      recurrence: setup.recurrence,
      dayOfWeek: setup.dayOfWeek,
      dayOfMonth: setup.dayOfMonth,
      active: setup.active,
      pattern,
    }),
    [setup],
  )

  const days = React.useMemo(
    () =>
      setup.from <= setup.to ? eachDay(setup.from, setup.to) : [setup.from],
    [setup.from, setup.to],
  )

  const rows = React.useMemo(
    () =>
      days.map((date) => ({
        date,
        readings: PATTERNS.map((pattern) => ({
          pattern,
          reading: read(templateFor(pattern), date, holidays),
        })),
      })),
    [days, templateFor, holidays],
  )

  const shown = hideSilent
    ? rows.filter((r) => r.readings.some((x) => x.reading.due))
    : rows

  /*
    Whole sentences, joined, rather than clauses glued with commas somebody
    wrote in English word order. Which sentence is `lib/recurrence-preview.ts`'s
    decision, like every other sentence on this screen; turning it into words is
    this file's.
  */
  const summary = [
    rangeLine(days),
    ...PATTERNS.map((pattern) =>
      generatedLine(
        pattern,
        rows.filter(
          (r) => r.readings.find((x) => x.pattern === pattern)?.reading.due,
        ).length,
      ),
    ),
  ]
    .map((line) => sentence(line, t, locale))
    .join(" ")

  const range: DateRange = {
    from: fromIso(setup.from),
    to: fromIso(setup.to),
  }

  const addHoliday = () => {
    if (!isIsoDate(newHoliday) || setup.holidays.includes(newHoliday)) return
    set("holidays", [...setup.holidays, newHoliday].sort())
    setNewHoliday("")
  }

  const theCase = CASES.find((c) => c.title === loaded)

  return (
    <div className="flex w-full flex-col gap-4">
      <Card>
        <CardHeader>
          <CardTitle>{t("recurrence.startFromACase")}</CardTitle>
          <CardDescription>
            {t("recurrence.startFromACaseHint")}
          </CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-4">
          <div className="grid gap-2 sm:grid-cols-2 xl:grid-cols-3">
            {CASES.map((c) => (
              <Button
                key={c.title}
                size="lg"
                variant={loaded === c.title ? "default" : "outline"}
                className="h-auto justify-start py-2 text-left whitespace-normal"
                onClick={() => loadCase(c.title, c.setup)}
              >
                {loaded === c.title ? (
                  <IconCheck data-icon="inline-start" />
                ) : null}
                {t(c.title)}
              </Button>
            ))}
          </div>
          {theCase ? (
            <p className="max-w-prose leading-relaxed">{t(theCase.rule)}</p>
          ) : (
            <p className="max-w-prose leading-relaxed text-foreground/80">
              {t("recurrence.changedByHand")}
            </p>
          )}
        </CardContent>
      </Card>

      <div className="grid gap-4 xl:grid-cols-2">
        <Card>
          <CardHeader>
            <CardTitle>{t("recurrence.duty")}</CardTitle>
          </CardHeader>
          <CardContent className="flex flex-col gap-6">
            <Field>
              <FieldLabel>{t("taskForm.howOften")}</FieldLabel>
              <RadioGroup
                value={setup.recurrence}
                onValueChange={(v) => set("recurrence", v as Recurrence)}
                className="grid gap-2 sm:grid-cols-2"
              >
                {(Object.keys(RECURRENCE_WORD) as Recurrence[]).map((r) => (
                  <label
                    key={r}
                    className="flex cursor-pointer items-center gap-3 rounded-lg px-3 py-2 ring-1 ring-foreground/10 has-data-checked:ring-2 has-data-checked:ring-primary"
                  >
                    <RadioGroupItem value={r} />
                    {t(RECURRENCE_WORD[r])}
                  </label>
                ))}
              </RadioGroup>
            </Field>

            {setup.recurrence === "weekly" ? (
              <Field>
                <FieldLabel>{t("taskForm.whichDay")}</FieldLabel>
                <RadioGroup
                  value={String(setup.dayOfWeek)}
                  onValueChange={(v) => set("dayOfWeek", Number(v))}
                  className="flex flex-wrap gap-2"
                >
                  {WEEKDAYS.map((d) => (
                    <label
                      key={d}
                      className="flex cursor-pointer items-center gap-2 rounded-lg px-3 py-2 ring-1 ring-foreground/10 has-data-checked:ring-2 has-data-checked:ring-primary"
                    >
                      <RadioGroupItem value={String(d)} />
                      {t(WEEKDAY_WORD[d])}
                    </label>
                  ))}
                </RadioGroup>
              </Field>
            ) : null}

            {setup.recurrence === "monthly" ||
            setup.recurrence === "quarterly" ? (
              <Field>
                <FieldLabel htmlFor="dayOfMonth">
                  {t("taskForm.whichDay")}
                </FieldLabel>
                <Input
                  id="dayOfMonth"
                  type="number"
                  min={1}
                  max={31}
                  className="w-24"
                  value={setup.dayOfMonth}
                  onChange={(e) =>
                    set(
                      "dayOfMonth",
                      Math.min(31, Math.max(1, Number(e.target.value) || 1)),
                    )
                  }
                />
                <FieldDescription>
                  {t("recurrence.dayOfMonthHint")}
                </FieldDescription>
              </Field>
            ) : null}

            <Field orientation="horizontal">
              <Checkbox
                id="active"
                checked={setup.active}
                onCheckedChange={(v) => set("active", v === true)}
              />
              <FieldLabel htmlFor="active">{t("recurrence.active")}</FieldLabel>
            </Field>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>{t("recurrence.holidays")}</CardTitle>
            <CardDescription>{t("recurrence.holidaysHint")}</CardDescription>
          </CardHeader>
          <CardContent className="flex flex-col gap-4">
            <div className="flex flex-wrap items-end gap-2">
              <Field className="w-auto">
                <FieldLabel htmlFor="holiday">
                  {t("recurrence.addADay")}
                </FieldLabel>
                <Input
                  id="holiday"
                  type="date"
                  className="w-44"
                  value={newHoliday}
                  onChange={(e) => setNewHoliday(e.target.value)}
                />
              </Field>
              <Button size="lg" onClick={addHoliday}>
                <IconPlus data-icon="inline-start" />
                {t("recurrence.add")}
              </Button>
            </div>
            {setup.holidays.length ? (
              <ul className="flex flex-wrap gap-2">
                {setup.holidays.map((h) => (
                  <li key={h}>
                    <Button
                      size="lg"
                      variant="outline"
                      onClick={() =>
                        set(
                          "holidays",
                          setup.holidays.filter((x) => x !== h),
                        )
                      }
                    >
                      {shortDay(h, locale)}
                      <IconX data-icon="inline-end" />
                    </Button>
                  </li>
                ))}
              </ul>
            ) : (
              <p className="max-w-prose text-foreground/80">
                {t("recurrence.noHolidays")}
              </p>
            )}
          </CardContent>
        </Card>
      </div>

      <Card>
        <CardHeader>
          <CardTitle>{t("recurrence.range")}</CardTitle>
          <CardDescription>{summary}</CardDescription>
        </CardHeader>
        <CardContent className="flex flex-col gap-4">
          <div className="flex justify-center">
            <Calendar
              mode="range"
              selected={range}
              month={month}
              onMonthChange={setMonth}
              numberOfMonths={2}
              captionLayout="dropdown"
              formatters={calendarChrome(locale)}
              startMonth={new Date(2024, 0)}
              endMonth={new Date(2030, 11)}
              className="[--cell-size:--spacing(10)] sm:[--cell-size:--spacing(12)]"
              onSelect={(next) => {
                if (!next?.from) return
                set("from", toIso(next.from))
                set("to", toIso(next.to ?? next.from))
              }}
              components={{
                DayButton: ({ children, modifiers, day, ...props }) => {
                  const iso = toIso(day.date)
                  const marks = PATTERNS.map((pattern) => ({
                    pattern,
                    reading: read(templateFor(pattern), iso, holidays),
                  }))
                  return (
                    <CalendarDayButton
                      day={day}
                      modifiers={modifiers}
                      {...props}
                    >
                      {children}
                      {!modifiers.outside ? (
                        <div className="flex items-center gap-0.5">
                          {marks.map((m) => (
                            <div
                              key={m.pattern}
                              className={
                                m.reading.due
                                  ? "size-1.5 rounded-full bg-foreground"
                                  : m.reading.holiday
                                    ? "size-1.5 rounded-full bg-destructive/70"
                                    : "size-1.5 rounded-full bg-transparent"
                              }
                            />
                          ))}
                        </div>
                      ) : null}
                    </CalendarDayButton>
                  )
                },
              }}
            />
          </div>
          <p className="max-w-prose text-foreground/80">
            {t("recurrence.dotsHint")}
          </p>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle>{t("recurrence.dayByDay")}</CardTitle>
          <CardAction>
            <label className="flex cursor-pointer items-center gap-3">
              <Checkbox
                checked={hideSilent}
                onCheckedChange={(v) => setHideSilent(v === true)}
              />
              <span>
                {hideSilent && rows.length - shown.length > 0
                  ? t("recurrence.onlyActiveDaysHidden", {
                      count: rows.length - shown.length,
                    })
                  : t("recurrence.onlyActiveDays")}
              </span>
            </label>
          </CardAction>
        </CardHeader>
        <CardContent className="flex flex-col gap-4">
          {shown.length === 0 ? (
            <p className="max-w-prose leading-relaxed">
              {t("recurrence.nothingInRange")}
            </p>
          ) : null}
          {shown.map((row) => (
            <div key={row.date} className="flex flex-col gap-2">
              <h3 className="font-semibold">{fullDay(row.date, locale)}</h3>
              <div className="grid gap-2 md:grid-cols-2">
                {row.readings.map(({ pattern, reading }) => (
                  <Holder
                    key={pattern}
                    reading={reading}
                    template={templateFor(pattern)}
                  />
                ))}
              </div>
            </div>
          ))}
        </CardContent>
      </Card>
    </div>
  )
}
