import type { Occurrence } from "@/lib/task-form"
import type { Relation } from "@/lib/task-actions"

/**
 * How the person looking stands to THIS task.
 *
 * `routes.json`, `single_task_detail`: "/tasks/[id] is the only task page;
 * visible actions depend on the viewer's relation to THIS task". So there is one
 * page and no checker page, and what somebody may do is decided here and then
 * handed to `availableActions`. Nothing branches on the tier.
 *
 * Pure, with nothing under it but two type imports, because the answer is also
 * wanted in the browser: the page renders from it and every server action asks
 * the same question again on the way in. A module that reached for the database
 * could not be asked twice from both sides.
 *
 * Relations are not exclusive, and the roster is why. Several departments hold
 * two people, so the same person is regularly the assignee and the checker of
 * their own work, and the head of a department is often the checker of what it
 * produces. Anything that returned one relation would answer the wrong question
 * for those people.
 */

export type ViewerRelations = {
  relations: Relation[]
  /** Carried alongside, because `availableActions` asks for it separately. */
  isOneTime: boolean
}

export type RelationInput = {
  viewerPersonId: string
  viewerTier: "admin" | "manager" | "staff"
  /** The department name, as `SignedIn` carries it. Null between positions. */
  viewerDepartment: string | null
  /** Null while the position has no holder: the instance waits for one. */
  assigneePersonId: string | null
  checkerPersonId: string | null
  verifierPersonId: string | null
  createdByPersonId: string
  taskDepartment: string
  occurrence: Occurrence
}

export function relationsOf(input: RelationInput): ViewerRelations {
  const {
    viewerPersonId,
    viewerTier,
    viewerDepartment,
    assigneePersonId,
    checkerPersonId,
    verifierPersonId,
    createdByPersonId,
    taskDepartment,
    occurrence,
  } = input

  const isOneTime = occurrence === "once"
  const relations: Relation[] = []

  // Three separate ifs and not a chain: one person holds as many of these as the
  // duty names them in, and the two-person departments name the same person
  // twice as a matter of course.
  //
  // A null on the duty needs no guard of its own. The viewer is signed in, so
  // their person id is a real one, and no string equals null: an unheld position
  // and an unnamed verifier fall out of the comparison already. A guard that
  // cannot change an answer was written here first and no mutation of it could
  // be made to fail a test, which is how it was found.
  if (viewerPersonId === assigneePersonId) {
    relations.push("assignee")
  }
  if (viewerPersonId === checkerPersonId) {
    relations.push("checker")
  }
  if (viewerPersonId === verifierPersonId) {
    relations.push("verifier")
  }

  // Creating a duty is not itself a relation. "close" ends a task nothing will
  // reissue, which is only true of a `once` duty; the creator of a daily one has
  // a template to deactivate instead, and closing today's instance would say
  // nothing about tomorrow's.
  if (isOneTime && viewerPersonId === createdByPersonId) {
    relations.push("one_time_creator")
  }

  // An admin is not the manager of every department, they are an admin, and
  // `availableActions` already reads "admin" as satisfying any relation. Adding
  // both would make a hidden departmental scope for somebody who has none.
  //
  // A manager between positions has no department, and null matches no
  // department name, so the comparison already refuses them.
  if (viewerTier === "manager" && viewerDepartment === taskDepartment) {
    relations.push("manager_of_dept")
  }

  if (viewerTier === "admin") {
    relations.push("admin")
  }

  return { relations, isOneTime }
}
