"use client"

import {
  AlertDialog,
  AlertDialogAction,
  AlertDialogCancel,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogFooter,
  AlertDialogHeader,
  AlertDialogTitle,
  AlertDialogTrigger,
} from "@/components/ui/alert-dialog"
import * as React from "react"
import { useRouter } from "next/navigation"

import { useT } from "@/components/locale-provider"
import { Button } from "@/components/ui/button"
import { Field, FieldLabel } from "@/components/ui/field"
import { Textarea } from "@/components/ui/textarea"
import type { MessageKey } from "@/lib/i18n/messages"
import type { TaskAction } from "@/lib/task-actions"
import type { CommandResult } from "@/lib/services/task-commands"
import {
  approveTask,
  closeTask,
  finishTask,
  remindTask,
  reportTaskIssue,
  sendTaskBack,
  startTask,
  verifyTask,
} from "./actions"

/**
 * The acts this task allows this viewer, as buttons.
 *
 * The list arrives as a prop and is never worked out here. `availableActions`
 * needs the status and the relations, and both of those are read from the
 * database, so deciding it in the browser would mean shipping the decision to
 * the one place that must not be trusted with it. This component takes the
 * `TaskAction` type, which costs nothing at runtime, and the values as data.
 *
 * Pressing one calls a Server Action, which asks again whether this viewer may
 * do this. The list here decides what is drawn and nothing else: a button that
 * is not on screen is not a permission, and neither is one that is.
 */

/**
 * The acts that carry a note, and what the note is for.
 *
 * `send_back` and `report_issue` are named for theirs in `lifecycle.json`;
 * `finish` hands one to the checker, which is what its own consequence line
 * says; and `remind` is `send_reminder_with_note` in `routes.json`, because a
 * reminder with nothing in it is a nudge that says nothing.
 *
 * `start`, `approve`, `close` and `verify` take none. They are a decision, not
 * a message.
 */
const NOTE_LABEL: Partial<Record<TaskAction["id"], MessageKey>> = {
  finish: "note.finish",
  send_back: "note.sendBack",
  report_issue: "note.reportIssue",
  remind: "note.remind",
}

/** Which Server Action each verb reaches. The one place the two lists meet. */
const SEND: Record<
  TaskAction["id"],
  (id: string, note: string) => Promise<CommandResult>
> = {
  start: (id) => startTask(id),
  finish: (id, note) => finishTask(id, note),
  report_issue: (id, note) => reportTaskIssue(id, note),
  approve: (id) => approveTask(id),
  send_back: (id, note) => sendTaskBack(id, note),
  verify: (id) => verifyTask(id),
  close: (id) => closeTask(id),
  remind: (id, note) => remindTask(id, note),
}

/**
 * What a refusal reads as. The server answers with a reason and never a
 * sentence, for the same reason the sign-in form does: what somebody reads is
 * written next to the thing they pressed, not in a service.
 */
const REFUSAL: Record<string, MessageKey> = {
  no_such_task: "refusal.noSuchTask",
  not_allowed: "refusal.notAllowed",
  note_required: "refusal.noteRequired",
  already_moved: "refusal.alreadyMoved",
}

export function TaskActionsPanel({
  taskId,
  actions,
}: {
  taskId: string
  actions: TaskAction[]
}) {
  return (
    <div className="flex flex-wrap gap-2">
      {actions.map((action) => (
        <Act key={action.id} taskId={taskId} action={action} />
      ))}
    </div>
  )
}

function Act({ taskId, action }: { taskId: string; action: TaskAction }) {
  const noteLabel = NOTE_LABEL[action.id]
  const variant = action.primary ? "default" : "outline"
  const router = useRouter()
  const t = useT()

  const [open, setOpen] = React.useState(false)
  const [note, setNote] = React.useState("")
  const [pending, setPending] = React.useState(false)
  const [refused, setRefused] = React.useState<string | null>(null)

  async function send() {
    setRefused(null)
    setPending(true)
    try {
      const result = await SEND[action.id](taskId, note)
      if (result.ok) {
        setOpen(false)
        setNote("")
        // The page is a Server Component reading the row, so refreshing is what
        // shows the new status, the new history line and the actions that now
        // apply. Nothing is mirrored in the browser to go stale.
        router.refresh()
        return
      }
      const key = REFUSAL[result.reason]
      setRefused(key ? t(key) : t("refusal.noAnswer"))
    } catch {
      setRefused(t("refusal.noAnswer"))
    } finally {
      setPending(false)
    }
  }

  // Nothing to write down and nothing that needs saying first, so the press is
  // the whole act. A dialog in front of it would be a step to click through.
  if (!noteLabel && !action.consequence) {
    return (
      <Button variant={variant} disabled={pending} onClick={() => void send()}>
        {t(action.label)}
      </Button>
    )
  }

  return (
    <AlertDialog open={open} onOpenChange={setOpen}>
      <AlertDialogTrigger render={<Button variant={variant} />}>
        {t(action.label)}
      </AlertDialogTrigger>
      <AlertDialogContent className="sm:max-w-md">
        <AlertDialogHeader>
          <AlertDialogTitle>{t(action.label)}</AlertDialogTitle>
          {/*
            The consequence, here and nowhere else. Under the button it is a
            warning nobody reads twice; at the moment of the act it is the last
            thing somebody sees before they commit to it.
          */}
          {action.consequence ? (
            <AlertDialogDescription>
              {t(action.consequence)}
            </AlertDialogDescription>
          ) : null}
        </AlertDialogHeader>

        {noteLabel ? (
          <Field>
            <FieldLabel htmlFor={`note-${action.id}`}>
              {t(noteLabel)}
            </FieldLabel>
            <Textarea
              id={`note-${action.id}`}
              name="note"
              rows={4}
              value={note}
              onChange={(e) => setNote(e.target.value)}
            />
          </Field>
        ) : null}

        {refused ? <p className="text-sm text-destructive">{refused}</p> : null}

        <AlertDialogFooter>
          <AlertDialogCancel>{t("action.cancel")}</AlertDialogCancel>
          <AlertDialogAction
            disabled={pending}
            // `nativeButton={false}` is not used: this must not close the
            // dialog on its own. It closes when the server says it worked, so a
            // refusal stays in front of the person who caused it.
            onClick={(event) => {
              event.preventDefault()
              void send()
            }}
          >
            {pending ? t("action.working") : t(action.label)}
          </AlertDialogAction>
        </AlertDialogFooter>
      </AlertDialogContent>
    </AlertDialog>
  )
}
