"use client"

import * as React from "react"

import { Button } from "@/components/ui/button"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import {
  Collapsible,
  CollapsibleContent,
  CollapsibleTrigger,
} from "@/components/ui/collapsible"
import { ChoicePicker } from "@/components/choice-picker"
import { Field, FieldDescription, FieldLabel } from "@/components/ui/field"
import { useLocale, useT } from "@/components/locale-provider"
import type { Choice } from "@/lib/choice"
import { submitDuty } from "@/app/tasks/new/actions"
import { fullDay } from "@/lib/i18n/dates"
import { OCCURRENCE_WORD, WEEKDAY_WORD } from "@/lib/i18n/duty-words"
import type { MessageKey } from "@/lib/i18n/messages"
import type { Translate } from "@/lib/i18n"
import { Input } from "@/components/ui/input"
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group"
import { Textarea } from "@/components/ui/textarea"
import { IconChevronRight, IconPlus } from "@tabler/icons-react"
import {
  OCCURRENCES,
  WEEKDAYS,
  becomesTasks,
  dayOfMonthOf,
  describeSchedule,
  invalidFields,
  minutesOf,
  missingFields,
  scheduleFor,
  type Draft,
  type InvalidField,
  type Occurrence,
  type Refusal,
  type RequiredField,
  type ScheduleLine,
} from "@/lib/task-form"

/**
 * Creating a task.
 *
 * The occurrence is asked third and decides the rest, because `deadline`,
 * `dayOfWeek` and `dayOfMonth` are conditional and a form showing all thirteen
 * fields at once is a form nobody fills.
 *
 * `general` is refused here rather than accepted and written somewhere else. A
 * general duty never becomes a task, so a screen called "create a task" has no
 * business taking one.
 *
 * Every word on it comes from the dictionary. 183 of the 193 people read Thai
 * and the 39 of the manager tier are exactly who this screen is for, so an
 * English sentence hard-coded here is a screen the people who need it cannot
 * read. Nothing it imports reaches below `lib/i18n` or `lib/task-form`, both of
 * which are pure, because this is a client component and what it imports it
 * puts in the browser.
 */

const MISSING_WORD: Record<RequiredField, MessageKey> = {
  positionId: "taskForm.missing.position",
  description: "taskForm.missing.description",
  deadline: "taskForm.missing.deadline",
  dayOfWeek: "taskForm.missing.dayOfWeek",
  dayOfMonth: "taskForm.missing.dayOfMonth",
  checker: "taskForm.missing.checker",
}

/**
 * A whole sentence each, not a word in a list. "Still needed: a day of the
 * month" names what is absent; a field holding 99 needs to be told what a day
 * of the month is, and the range is the entire point of saying anything.
 */
const INVALID_WORD: Record<InvalidField, MessageKey> = {
  dayOfMonth: "taskForm.invalid.dayOfMonth",
  minutes: "taskForm.invalid.minutes",
}

/**
 * The two range sentences the form already says serve a refusal from the server
 * too, because they say the same thing and a sentence written twice is a
 * sentence corrected once. The server should never reach them from this screen:
 * the button is disabled while either is true. It reaches them from a stale page
 * or a posted body, which is exactly what the second check exists for.
 *
 * The weekday and the deadline have no such pair, because this screen cannot
 * make either wrong. Their sentences exist for the posted body alone.
 */
const REFUSAL_WORD: Record<Refusal, MessageKey> = {
  position_out_of_scope: "taskForm.positionNotScoped",
  checker_out_of_scope: "taskForm.checkerNotScoped",
  verifier_out_of_scope: "taskForm.verifierNotScoped",
  occurrence_not_allowed: "taskForm.occurrenceNotAllowed",
  day_of_month_out_of_range: "taskForm.invalid.dayOfMonth",
  day_of_week_out_of_range: "taskForm.invalid.dayOfWeek",
  deadline_not_a_date: "taskForm.invalid.deadline",
  time_to_do_not_a_number: "taskForm.invalid.minutes",
}

/**
 * The one place a `ScheduleLine` becomes words.
 *
 * `describeSchedule` hands back a key and the data beside it and stops there,
 * because it does not know who is reading. This does: the day goes through
 * `fullDay`, which gives a Thai reader the Buddhist year, and the weekday
 * through the dictionary, so neither is ever glued into a template string.
 */
function scheduleSentence(
  line: ScheduleLine,
  t: Translate,
  locale: Parameters<typeof fullDay>[1],
): string {
  if ("day" in line) return t(line.key, { day: fullDay(line.day, locale) })
  if ("weekday" in line)
    return t(line.key, { day: t(WEEKDAY_WORD[line.weekday]) })
  if ("dayOfMonth" in line) return t(line.key, { day: line.dayOfMonth })
  return t(line.key)
}

/**
 * A list of one is already the answer.
 *
 * A staff member is offered their own position and their own name, one choice
 * each, and asking somebody to search a list of one for the thing they are is
 * asking them to confirm a fact. A manager whose department holds a single
 * position meets the same thing and it is the same answer.
 */
function onlyChoice(choices: Choice[]): string {
  return choices.length === 1 ? choices[0]!.id : ""
}

export function NewTaskForm({
  positions,
  people,
  occurrences = OCCURRENCES,
}: {
  positions: Choice[]
  people: Choice[]
  /**
   * Which of the six this viewer may pick, from `occurrencesFor`. A staff
   * member gets the four that repeat: the client turned down one-time self
   * to-dos on 3 August, and a general duty is a line in a job description
   * rather than work. `createDuty` refuses the other two again on the way in.
   */
  occurrences?: readonly Occurrence[]
}) {
  const t = useT()
  const locale = useLocale()

  const [draft, setDraft] = React.useState<Draft>({
    positionId: onlyChoice(positions),
    description: "",
    occurrence: "daily",
    deadline: "",
    dayOfWeek: "",
    dayOfMonth: "",
    checker: onlyChoice(people),
    verifier: "",
    report: "",
    sopUrl: "",
    exampleUrl: "",
    minutes: "",
    customer: "",
  })
  const set = <K extends keyof Draft>(key: K, value: Draft[K]) =>
    setDraft((d) => ({ ...d, [key]: value }))

  const schedule = scheduleFor(draft.occurrence)
  const tracked = becomesTasks(draft.occurrence)
  const missing = missingFields(draft)
  const invalid = invalidFields(draft)

  const [pending, setPending] = React.useState(false)
  // A key rather than a sentence, so the outcome of a write is read in the same
  // language as everything around it.
  const [outcome, setOutcome] = React.useState<MessageKey | null>(null)

  // The draft holds ids, because that is what the server needs. The summary is
  // read by a person, so it says the name they picked. A name is data and stays
  // exactly as the roster spells it, in either language.
  const checkerName =
    people.find((c) => c.id === draft.checker)?.label ?? draft.checker

  async function create() {
    setOutcome(null)
    setPending(true)
    try {
      const result = await submitDuty({
        positionId: draft.positionId,
        description: draft.description.trim(),
        occurrence: draft.occurrence,
        deadline: draft.deadline || null,
        dayOfWeek: draft.dayOfWeek ? Number(draft.dayOfWeek) : null,
        dayOfMonth: dayOfMonthOf(draft),
        checkerPersonId: draft.checker || null,
        verifierPersonId: draft.verifier || null,
        reportToDeliver: draft.report,
        wiSopUrl: draft.sopUrl,
        reportExampleUrl: draft.exampleUrl,
        timeToDoMinutes: minutesOf(draft),
        customerTag: draft.customer,
      })
      if (result.ok) {
        setOutcome("taskForm.created")
        // What this duty is changes from one to the next; who watches over it
        // and where it sits usually do not. So the description, the date and
        // everything the duty produces are cleared, and the position, the
        // checker and the verifier stay: clearing those would make somebody
        // retype what they were about to reuse.
        setDraft((d) => ({
          ...d,
          description: "",
          deadline: "",
          report: "",
          sopUrl: "",
          exampleUrl: "",
          minutes: "",
          customer: "",
        }))
        return
      }
      setOutcome(REFUSAL_WORD[result.reason])
    } catch {
      setOutcome("refusal.noAnswer")
    } finally {
      setPending(false)
    }
  }

  return (
    <div className="flex max-w-3xl flex-col gap-6">
      <Card>
        <CardHeader>
          <CardTitle>{t("taskForm.whoDoesIt")}</CardTitle>
          <CardDescription>{t("taskForm.whoDoesItHint")}</CardDescription>
        </CardHeader>
        <CardContent>
          <Field>
            <FieldLabel htmlFor="position">
              {t("positions.position")}
            </FieldLabel>
            <ChoicePicker
              id="position"
              value={draft.positionId}
              onChange={(v) => set("positionId", v)}
              choices={positions}
              placeholder={t("taskForm.searchPositions")}
              empty={t("taskForm.noSuchPosition")}
            />
          </Field>
        </CardContent>
      </Card>

      <Card>
        <CardHeader>
          <CardTitle>{t("taskForm.whatHasToBeDone")}</CardTitle>
        </CardHeader>
        <CardContent className="flex flex-col gap-6">
          <Field>
            <FieldLabel htmlFor="description">
              {t("taskForm.description")}
            </FieldLabel>
            <Textarea
              id="description"
              rows={3}
              value={draft.description}
              onChange={(e) => set("description", e.target.value)}
              placeholder={t("taskForm.descriptionPlaceholder")}
            />
          </Field>

          <Field>
            {/*
              A radio group is a `role="radiogroup"` and not a form control, so
              nothing can point a `for` at it. `aria-labelledby` is how Base UI
              says to name one, and it is what gives the group a name to
              announce before the seven options inside it.
            */}
            <FieldLabel id="how-often">{t("taskForm.howOften")}</FieldLabel>
            <RadioGroup
              aria-labelledby="how-often"
              value={draft.occurrence}
              onValueChange={(v) => set("occurrence", v as Occurrence)}
              className="grid gap-3 sm:grid-cols-3"
            >
              {occurrences.map((o) => (
                <label
                  key={o}
                  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={o} />
                  {t(OCCURRENCE_WORD[o])}
                </label>
              ))}
            </RadioGroup>
          </Field>

          {schedule.kind === "deadline" ? (
            <Field>
              <FieldLabel htmlFor="deadline">{t("taskForm.dueOn")}</FieldLabel>
              <Input
                id="deadline"
                type="date"
                value={draft.deadline}
                onChange={(e) => set("deadline", e.target.value)}
              />
            </Field>
          ) : null}

          {schedule.kind === "day_of_week" ? (
            <Field>
              <FieldLabel id="which-weekday">
                {t("taskForm.whichDay")}
              </FieldLabel>
              <RadioGroup
                aria-labelledby="which-weekday"
                value={draft.dayOfWeek}
                onValueChange={(v) => set("dayOfWeek", v)}
                className="flex flex-wrap gap-3"
              >
                {WEEKDAYS.map((day) => (
                  <label
                    key={day}
                    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(day)} />
                    {t(WEEKDAY_WORD[day])}
                  </label>
                ))}
              </RadioGroup>
              <FieldDescription>{t("taskForm.weekdayHint")}</FieldDescription>
            </Field>
          ) : null}

          {schedule.kind === "day_of_month" ? (
            <Field>
              <FieldLabel htmlFor="dayOfMonth">
                {t("taskForm.whichDay")}
              </FieldLabel>
              <Input
                id="dayOfMonth"
                type="number"
                min={1}
                max={31}
                className="w-32"
                value={draft.dayOfMonth}
                onChange={(e) => set("dayOfMonth", e.target.value)}
              />
              <FieldDescription>{t("taskForm.monthDayHint")}</FieldDescription>
            </Field>
          ) : null}
        </CardContent>
      </Card>

      {tracked ? (
        <>
          <Card>
            <CardHeader>
              <CardTitle>{t("taskForm.whoDecides")}</CardTitle>
            </CardHeader>
            <CardContent className="grid gap-6 sm:grid-cols-2">
              <Field>
                <FieldLabel htmlFor="checker">
                  {t("taskForm.checker")}
                </FieldLabel>
                <ChoicePicker
                  id="checker"
                  value={draft.checker}
                  onChange={(v) => set("checker", v)}
                  choices={people}
                  placeholder={t("taskForm.checkerPlaceholder")}
                  empty={t("taskForm.noSuchPerson")}
                />
                <FieldDescription>{t("taskForm.checkerHint")}</FieldDescription>
              </Field>
              <Field>
                <FieldLabel htmlFor="verifier">
                  {t("taskForm.verifier")}
                </FieldLabel>
                <ChoicePicker
                  id="verifier"
                  value={draft.verifier}
                  onChange={(v) => set("verifier", v)}
                  choices={people}
                  placeholder={t("taskForm.verifierPlaceholder")}
                  empty={t("taskForm.noSuchPerson")}
                />
                <FieldDescription>
                  {t("taskForm.verifierHint")}
                </FieldDescription>
              </Field>
            </CardContent>
          </Card>

          <Collapsible>
            <Card>
              <CollapsibleTrigger
                render={<CardHeader className="cursor-pointer" />}
              >
                <CardTitle className="flex items-center gap-2">
                  <IconChevronRight className="transition-transform group-data-open/collapsible:rotate-90" />
                  {t("taskForm.extras")}
                </CardTitle>
                <CardDescription>{t("taskForm.extrasHint")}</CardDescription>
              </CollapsibleTrigger>
              <CollapsibleContent>
                <CardContent className="flex flex-col gap-6">
                  <Field>
                    <FieldLabel htmlFor="report">
                      {t("taskForm.report")}
                    </FieldLabel>
                    <Input
                      id="report"
                      value={draft.report}
                      onChange={(e) => set("report", e.target.value)}
                      placeholder={t("taskForm.reportPlaceholder")}
                    />
                  </Field>
                  <div className="grid gap-6 sm:grid-cols-2">
                    <Field>
                      <FieldLabel htmlFor="sop">
                        {t("taskForm.workInstruction")}
                      </FieldLabel>
                      {/*
                        A document reference and a customer code are spelled the
                        same in both languages, so the example of one is not a
                        sentence the dictionary has anything to say about.
                      */}
                      <Input
                        id="sop"
                        value={draft.sopUrl}
                        onChange={(e) => set("sopUrl", e.target.value)}
                        placeholder="WI-PK-011"
                      />
                    </Field>
                    <Field>
                      <FieldLabel htmlFor="example">
                        {t("taskForm.example")}
                      </FieldLabel>
                      <Input
                        id="example"
                        value={draft.exampleUrl}
                        onChange={(e) => set("exampleUrl", e.target.value)}
                        placeholder={t("taskForm.examplePlaceholder")}
                      />
                    </Field>
                    <Field>
                      <FieldLabel htmlFor="minutes">
                        {t("taskForm.timeToDo")}
                      </FieldLabel>
                      <Input
                        id="minutes"
                        type="number"
                        min={1}
                        step={1}
                        value={draft.minutes}
                        onChange={(e) => set("minutes", e.target.value)}
                        placeholder="15"
                        className="w-32"
                      />
                      <FieldDescription>
                        {t("taskForm.minutesHint")}
                      </FieldDescription>
                    </Field>
                    <Field>
                      <FieldLabel htmlFor="customer">
                        {t("taskForm.customer")}
                      </FieldLabel>
                      <Input
                        id="customer"
                        value={draft.customer}
                        onChange={(e) => set("customer", e.target.value)}
                        placeholder="CUST-4471"
                      />
                    </Field>
                  </div>
                </CardContent>
              </CollapsibleContent>
            </Card>
          </Collapsible>
        </>
      ) : (
        <Card>
          <CardHeader>
            <CardTitle>{t("taskForm.notATaskTitle")}</CardTitle>
          </CardHeader>
          <CardContent className="flex flex-col gap-4">
            <p className="max-w-prose leading-relaxed">
              {t("taskForm.notATaskBody")}
            </p>
            {/*
              The real position, not the sample screen it pointed at until the
              routes moved. Without one chosen there is nowhere to go, and a
              link that lands on a list somebody has to search again is worse
              than a button that says it is waiting for a choice.
            */}
            <div>
              <Button
                variant="outline"
                disabled={!draft.positionId}
                nativeButton={false}
                render={
                  draft.positionId ? (
                    <a href={`/positions/${draft.positionId}`} />
                  ) : (
                    <span />
                  )
                }
              >
                {t("taskForm.addToJobDescription")}
              </Button>
            </div>
          </CardContent>
        </Card>
      )}

      {tracked ? (
        <Card>
          <CardContent className="flex flex-wrap items-center justify-between gap-4">
            <div className="max-w-prose">
              {outcome ? (
                <p>{t(outcome)}</p>
              ) : missing.length ? (
                /*
                  The words are translated first and joined after, the way
                  `holidays.loaded` takes its list of years. Where the list
                  lands in the sentence is the sentence's business, which is why
                  the whole of it is one placeholder.
                */
                <p>
                  {t("taskForm.stillNeeded", {
                    fields: missing.map((f) => t(MISSING_WORD[f])).join(", "),
                  })}
                </p>
              ) : invalid.length ? (
                /*
                  Not joined into the list above. Each of these is a whole
                  sentence naming a range, and a range dropped into "Still
                  needed: ..." reads as one more absent field.
                */
                invalid.map((f) => <p key={f}>{t(INVALID_WORD[f])}</p>)
              ) : (
                /*
                  Two sentences and not one. The schedule and the checker were a
                  single line joined by a comma, which is an English comma in an
                  English order; each stands on its own so each can be written
                  the way its language writes it.
                */
                <>
                  <p>{scheduleSentence(describeSchedule(draft), t, locale)}</p>
                  <p>{t("position.checkedBy", { name: checkerName })}</p>
                </>
              )}
            </div>
            {/*
              Disabled while a field is missing or holds a number it cannot
              hold, so the button is never the thing that reports the problem.
              The sentence beside it already names what is left, and a button
              that refuses without saying why teaches people to click twice.
            */}
            <Button
              onClick={() => void create()}
              disabled={pending || missing.length > 0 || invalid.length > 0}
            >
              <IconPlus />
              {pending ? t("taskForm.creating") : t("taskForm.create")}
            </Button>
          </CardContent>
        </Card>
      ) : null}
    </div>
  )
}
