import { notFound } from "next/navigation"

import { AppShell } from "@/components/app-shell"
import { Badge } from "@/components/ui/badge"
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card"
import { dayIn } from "@/lib/calendar"
import { translator } from "@/lib/i18n"
/**
 * Days and instants are printed by two different functions on purpose: a shared
 * one would be the first step towards comparing them.
 */
import { fullDay, moment as formatMoment } from "@/lib/i18n/dates"
import type { MessageKey } from "@/lib/i18n/messages"
import { requireUser } from "@/lib/services/session"
import { taskDetail, type TaskPerson } from "@/lib/services/task-detail"
import type { TaskStatus } from "@/lib/task-actions"
import type { Occurrence } from "@/lib/task-form"
import { TaskActionsPanel } from "./task-actions-panel"

/**
 * One task, and the only task page there is.
 *
 * `routes.json`, `single_task_detail`: "/tasks/[id] is the only task page;
 * visible actions depend on the viewer's relation to THIS task". So this file
 * never asks what tier somebody is. It asks `taskDetail`, which asks
 * `relationsOf`, and renders the actions that came back. The old
 * `/preview/task/checker` and `/preview/task/verifier` were the same screen
 * written three times, and the three copies could disagree.
 */

/**
 * The five statuses in words, not colour alone: this is read on a factory floor
 * by 193 people and some of them cannot separate red from green.
 *
 * The same five words are in `../columns.tsx`, which is a client component and
 * cannot import a value from anywhere with a database under it. Five strings in
 * two files is the smaller cost; they meet again when both screens move to
 * `/tasks`.
 */
const STATUS: Record<
  TaskStatus,
  {
    /**
     * A key and not a sentence, and typed as one. It was typed `string`, which
     * let the render below print `status.todo` on the screen instead of the
     * word: the compiler had no way to tell a key from what a key stands for.
     */
    label: MessageKey
    variant: "default" | "secondary" | "outline" | "destructive"
  }
> = {
  todo: { label: "status.todo", variant: "outline" },
  started: { label: "status.started", variant: "secondary" },
  finished: { label: "status.finished", variant: "default" },
  checked: { label: "status.checked", variant: "secondary" },
  missed: { label: "status.missed", variant: "destructive" },
}

/** How often it comes round, said as somebody would say it out loud. */
const OCCURRENCE_WORD: Record<Occurrence, MessageKey> = {
  once: "occurrence.onceShort",
  daily: "occurrence.dailyWorking",
  weekly: "occurrence.weekly",
  monthly: "occurrence.monthly",
  quarterly: "occurrence.quarterly",
  // A general duty never becomes a task, so no instance can carry this. It is
  // here because the type has six members and a partial map hides the day one
  // of them turns up.
  general: "occurrence.general",
}

function Who({
  role,
  person,
  absent,
}: {
  role: string
  person: TaskPerson | null
  absent: string
}) {
  return (
    <div className="flex flex-col gap-0.5">
      <span className="text-sm text-foreground/70">{role}</span>
      <span className={person ? "font-medium" : "text-foreground/70"}>
        {person?.name ?? absent}
      </span>
    </div>
  )
}

export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>
}) {
  const user = await requireUser()
  const { id } = await params

  // One day for the whole screen. The badge and the due line are two readings of
  // the same task, and a page that asked twice could straddle midnight in
  // Bangkok and call a task both late and on time.
  const today = dayIn(new Date())
  const detail = await taskDetail(user, id, today)

  if (!detail) notFound()

  /*
    The scope, on the way in. `taskDetail` answers existence and nothing else, so
    the page decides who may look, and the relations it already computed are the
    answer: somebody with no relation at all to this task is not one of its
    readers. A verifier and a manager of the department both have one, which is
    why they get the page read-only rather than a refusal.

    404 and not a message, because telling a stranger that this id names a real
    task is already telling them something.
  */
  if (detail.relations.length === 0) notFound()

  const t = translator(user.language)

  const history: { at: Date; what: MessageKey; note?: string | null }[] = []
  if (detail.startedAt) {
    history.push({ at: detail.startedAt, what: "task.workStarted" })
  }
  if (detail.finishedAt) {
    history.push({ at: detail.finishedAt, what: "task.handedIn" })
  }
  if (detail.sentBackAt) {
    history.push({
      at: detail.sentBackAt,
      what: "task.sentBackAgain",
      note: detail.sentBackNote,
    })
  }
  if (detail.checkedAt) {
    history.push({ at: detail.checkedAt, what: "task.checkedComplete" })
  }
  if (detail.verifiedAt) {
    history.push({ at: detail.verifiedAt, what: "task.verifiedSample" })
  }
  // Sorted rather than listed in schema order: a task sent back and finished
  // again has a finish that came after the send-back, and a fixed order would
  // print the story backwards.
  history.sort((a, b) => a.at.getTime() - b.at.getTime())

  return (
    <AppShell title={"task.title"} user={user}>
      <div className="flex max-w-3xl flex-col gap-6">
        <Card>
          <CardHeader>
            <CardTitle>{detail.description}</CardTitle>
            <CardDescription>
              {detail.position} · {detail.department} ·{" "}
              {t(OCCURRENCE_WORD[detail.occurrence])}
            </CardDescription>
          </CardHeader>
          <CardContent className="flex flex-wrap items-center gap-3">
            <Badge variant={STATUS[detail.status].variant}>
              {t(STATUS[detail.status].label)}
            </Badge>
            {/*
              Beside the status and never instead of it. Late is computed from
              the day at the moment somebody looks, so it is a second fact about
              a task rather than a sixth status: a task is To do and late.
            */}
            {detail.late ? (
              <Badge variant="destructive">{t("status.late")}</Badge>
            ) : null}
            <span className="text-foreground/80">
              {t("task.dueOn", { day: fullDay(detail.dueDate, user.language) })}
            </span>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>{t("task.whoItGoesThrough")}</CardTitle>
          </CardHeader>
          <CardContent className="grid gap-6 sm:grid-cols-2">
            <Who
              role={t("task.doingIt")}
              person={detail.assignee}
              absent={t("task.nobodyHolds")}
            />
            <Who
              role={t("task.checkingIt")}
              person={detail.checker}
              absent={t("task.nobodyNamed")}
            />
            <Who
              role={t("task.verifyingASample")}
              person={detail.verifier}
              absent={t("task.notSampled")}
            />
            {/*
              Written out rather than run through `Who`: the creator is never
              missing, `created_by` is NOT NULL and nothing here is ever deleted,
              so an "absent" string for this row would be one nobody can reach.
            */}
            <div className="flex flex-col gap-0.5">
              <span className="text-sm text-foreground/70">
                {t("task.askedForIt")}
              </span>
              <span className="font-medium">{detail.createdBy.name}</span>
            </div>
          </CardContent>
        </Card>

        <Card>
          <CardHeader>
            <CardTitle>{t("task.whatHasHappened")}</CardTitle>
          </CardHeader>
          <CardContent className="flex flex-col gap-4">
            {/*
              The issue sits above the sequence rather than in it. It is a flag
              and not an event: one per task, cleared by setting it back to
              null, and the row it is on carries no moment this page can read.
              Dating it from anything else here would date it wrongly.
            */}
            {detail.issueNote ? (
              <div className="flex flex-col items-start gap-2 rounded-lg bg-destructive/10 p-3">
                <Badge variant="destructive">{t("task.issueReported")}</Badge>
                <p className="max-w-prose leading-relaxed">
                  {detail.issueNote}
                </p>
              </div>
            ) : null}

            {history.length === 0 ? (
              <p className="text-foreground/70">{t("task.nothingYet")}</p>
            ) : (
              <ol className="flex flex-col gap-4">
                {history.map((moment) => (
                  <li key={moment.what} className="flex flex-col gap-0.5">
                    <span className="font-medium">{t(moment.what)}</span>
                    <span className="text-sm text-foreground/70 tabular-nums">
                      {formatMoment(moment.at, user.language)}
                    </span>
                    {moment.note ? (
                      <p className="mt-2 max-w-prose rounded-lg bg-muted p-3 leading-relaxed">
                        {moment.note}
                      </p>
                    ) : null}
                  </li>
                ))}
              </ol>
            )}
          </CardContent>
        </Card>

        {/*
          What came back, immediately above the buttons that answer it. Approve
          and Send back are a judgement of this and nothing else, and a checker
          who has to scroll for the words is a checker who presses Approve.

          Nothing at all when there is no submission, the way `/tasks` draws no
          carried-over section for somebody with a clean slate. A task still to
          do has not been handed in; a card saying so on every one of them would
          be the screen filling space with an absence.

          Everybody who reached this page sees it. The guard above already
          decided who may read the task, and the assignee reading back what they
          wrote and a manager reading their department's work are both people it
          let in.
        */}
        {detail.submission ? (
          <Card>
            <CardHeader>
              <CardTitle>{t("submission.whatCameBack")}</CardTitle>
              <CardDescription>
                {t("submission.handedInBy", {
                  name: detail.submission.by.name,
                  at: formatMoment(detail.submission.at, user.language),
                })}
              </CardDescription>
            </CardHeader>
            <CardContent>
              {/*
                Printed and never translated: this is what somebody typed, in
                whichever language they typed it, and it is data exactly as a
                name is. `whitespace-pre-line` because it was written into a
                textarea, where a line somebody broke on purpose is part of what
                they said.
              */}
              <p className="max-w-prose leading-relaxed whitespace-pre-line">
                {detail.submission.note}
              </p>
            </CardContent>
          </Card>
        ) : null}

        {/*
          `availableActions` is the authority, through `detail.actions`. Nothing
          here adds a button because a role ought to have one, and nothing hides
          one either: an empty list means this person may read the task and do
          nothing to it, and a card headed "What you can do" holding nothing
          would be the screen saying so twice.
        */}
        {detail.actions.length > 0 ? (
          <Card>
            {/*
              No description under the title. It said "None of these write
              anything yet", which was true for as long as the buttons were
              inert and is a lie now. A card whose heading is "What you can do"
              above a row of verbs has nothing left to explain.
            */}
            <CardHeader>
              <CardTitle>{t("task.whatYouCanDo")}</CardTitle>
            </CardHeader>
            <CardContent>
              <TaskActionsPanel taskId={detail.id} actions={detail.actions} />
            </CardContent>
          </Card>
        ) : null}
      </div>
    </AppShell>
  )
}
