import type { Level } from "@/lib/levels"
import { rankOf } from "@/lib/levels"

/**
 * The duties the demonstration database invents, and who in a department
 * carries which.
 *
 * The roster is real and this is not. `data/employees.tsv` holds the client's
 * own 193 people, 46 departments and 193 positions, and nobody has told us what
 * any of them actually do; everything below is a plausible guess written the
 * way somebody in a jewellery factory would write it. Departments, positions
 * and people are never invented here, only the work.
 *
 * ## Why 23 departments and not 46
 *
 * A pilot is rolled out, not switched on. These 23 are the chain a piece of
 * jewellery actually travels — wax, casting, metal, stone, setting, polishing,
 * plating — with the offices that plan it, cost it, buy for it, check it and
 * ship it, and management above them. That is 99 of the 193 people, so a
 * manager picked at random has something to manage and a department picked at
 * random is as likely to be in the pilot as out of it.
 *
 * The 23 left out are there for a reason too. Most are the second and third
 * lines of a trade already covered (Polishing 1 and 2 behind Polishing Center,
 * Plating 2 behind Plating 1, J-Work 1 to 5, Slide, Tumbling), which is what a
 * real rollout leaves for the second wave. Two are held empty on purpose,
 * because an empty department is a fixture in its own right and
 * docs/test-accounts.csv has an account sitting in each: Human Resources and
 * Administration, where `hr.manager` reads every screen in its empty state, and
 * Asset Security, where `ram` has nothing at all on his plate.
 *
 * Pure, and no database: this decides what the duties are, and lib/db/seed-demo.ts
 * decides where they go.
 */

export type DemoOccurrence =
  "daily" | "weekly" | "monthly" | "quarterly" | "general"

/**
 * Who audits a duty after it has been checked, named by the chair rather than
 * by the person sitting in it. The roster changes and this file should not have
 * to; lib/db/seed-demo.ts resolves each of these to whoever holds the job today.
 *
 * Verification is a sample audit and it is deliberately somebody outside the
 * chain that did and checked the work — the point of an audit is that it is not
 * the same two people again.
 */
export type Auditor =
  /** The head of Quality Control. Audits what the lines produce. */
  | "quality"
  /** The head of Accounting. Audits what the company spends. */
  | "accounts"
  /** The other person in Accounting. Audits what Costing works out. */
  | "accounts_clerk"

export type Shape = {
  text: string
  occurrence: DemoOccurrence
  /** Weekly only. 1 Monday to 5 Friday; nothing here falls on a Saturday. */
  dayOfWeek?: number
  /** Monthly and quarterly only. */
  dayOfMonth?: number
  /**
   * Which holder carries it, by rank within the department: 0 is the top. The
   * default, and the right one for a duty that belongs to whoever runs the
   * place rather than to everybody in it.
   */
  at?: number
  /** Everybody in the department carries their own copy. */
  everyone?: boolean
  /** One each, handed round the department in rank order. */
  rotate?: boolean
  auditedBy?: Auditor
}

/**
 * A department's work. Every list holds the same four kinds of line, and the
 * mixture is what makes a week look like a week:
 *
 *   - a `daily` or two, on the one or two people a daily check really belongs
 *     to, because a daily duty on all 99 people would be 3,600 instances a
 *     month and a screen nobody can read
 *   - two or three `weekly`, handed round so neighbours do not all count the
 *     same thing on the same afternoon
 *   - one or two `monthly`, likewise
 *   - one `general` for everybody, which never generates a task at all: it is
 *     the line in the job description that says what somebody is for
 */
export const DEMO_WORK: Record<string, Shape[]> = {
  /* ---------------------------------------------------------------- offices */

  "Top management": [
    {
      text: "Review the week's late work with the department managers",
      occurrence: "weekly",
      dayOfWeek: 1,
      rotate: true,
    },
    {
      text: "Sign off the shipment list for the week",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Approve the month's production plan against the order book",
      occurrence: "monthly",
      dayOfMonth: 3,
      rotate: true,
    },
    {
      text: "Read the quality report and record what was done about it",
      occurrence: "monthly",
      dayOfMonth: 8,
      rotate: true,
    },
    {
      text: "Review headcount and pay against the quarter's plan",
      occurrence: "quarterly",
      dayOfMonth: 10,
    },
    {
      text: "Represent the company to customers and suppliers",
      occurrence: "general",
      everyone: true,
    },
  ],

  Accounting: [
    { text: "Post the day's supplier invoices", occurrence: "daily", at: 1 },
    {
      text: "Reconcile the petty cash box",
      occurrence: "weekly",
      dayOfWeek: 3,
      rotate: true,
    },
    {
      text: "Chase the invoices past their payment terms",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Close the month and issue the trial balance",
      occurrence: "monthly",
      dayOfMonth: 5,
      rotate: true,
    },
    {
      text: "Prepare the VAT return",
      occurrence: "monthly",
      dayOfMonth: 12,
      rotate: true,
    },
    {
      text: "Keep the fixed asset register",
      occurrence: "general",
      everyone: true,
    },
  ],

  Costing: [
    {
      text: "Cost the styles released this week",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Check the metal loss rates against standard",
      occurrence: "weekly",
      dayOfWeek: 4,
      rotate: true,
      auditedBy: "accounts_clerk",
    },
    {
      text: "Issue the standard cost update",
      occurrence: "monthly",
      dayOfMonth: 6,
      rotate: true,
      auditedBy: "accounts_clerk",
    },
    {
      text: "Reconcile the month's gold price to the costing rate",
      occurrence: "monthly",
      dayOfMonth: 9,
      rotate: true,
      auditedBy: "accounts_clerk",
    },
    {
      text: "Answer costing questions from Sales",
      occurrence: "general",
      everyone: true,
    },
  ],

  Purchase: [
    {
      text: "Place the week's orders for findings and packaging",
      occurrence: "weekly",
      dayOfWeek: 1,
      rotate: true,
    },
    {
      text: "Review supplier prices against the last three orders",
      occurrence: "monthly",
      dayOfMonth: 7,
      rotate: true,
      auditedBy: "accounts",
    },
    {
      text: "Keep the approved supplier list",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Store Room": [
    {
      text: "Issue the day's material against the job cards",
      occurrence: "daily",
    },
    {
      text: "Count the fast moving items",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
      auditedBy: "accounts",
    },
    {
      text: "Full count of the store room",
      occurrence: "monthly",
      dayOfMonth: 25,
      rotate: true,
      auditedBy: "accounts",
    },
    {
      text: "Keep the store room labelled and tidy",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Master Data": [
    {
      text: "Enter the item codes released this week",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Clear the data corrections raised by the departments",
      occurrence: "weekly",
      dayOfWeek: 4,
      rotate: true,
    },
    {
      text: "Check the item master for codes with no movement",
      occurrence: "monthly",
      dayOfMonth: 14,
      rotate: true,
    },
    {
      text: "Keep the item master in step with the customer's own codes",
      occurrence: "general",
      everyone: true,
    },
  ],

  IT: [
    { text: "Check the overnight backup finished", occurrence: "daily", at: 1 },
    {
      text: "Clear the helpdesk queue",
      occurrence: "weekly",
      dayOfWeek: 3,
      rotate: true,
    },
    {
      text: "Apply the week's updates to the office machines",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Test a restore from backup",
      occurrence: "monthly",
      dayOfMonth: 18,
      rotate: true,
    },
    {
      text: "Keep the network and the printers running",
      occurrence: "general",
      everyone: true,
    },
  ],

  Shipping: [
    { text: "Book the day's outgoing parcels", occurrence: "daily" },
    {
      text: "Check the shipping documents against the invoices",
      occurrence: "weekly",
      dayOfWeek: 4,
      rotate: true,
      auditedBy: "accounts",
    },
    {
      text: "Reconcile the courier bill to the shipments sent",
      occurrence: "monthly",
      dayOfMonth: 10,
      rotate: true,
      auditedBy: "accounts",
    },
    {
      text: "Keep the export paperwork in order",
      occurrence: "general",
      everyone: true,
    },
  ],

  Packing: [
    { text: "Pack the day's finished orders", occurrence: "daily" },
    {
      text: "Count the packaging stock",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Check the packing materials against the order book",
      occurrence: "monthly",
      dayOfMonth: 20,
      rotate: true,
    },
    {
      text: "Keep the packing bench clean",
      occurrence: "general",
      everyone: true,
    },
  ],

  /* ------------------------------------------- planning, engineering, quality */

  "Production Planning": [
    { text: "Release the day's job cards to the lines", occurrence: "daily" },
    {
      text: "Re-plan the week against what the lines actually finished",
      occurrence: "weekly",
      dayOfWeek: 1,
      rotate: true,
    },
    {
      text: "Check the mould schedule against the order book",
      occurrence: "weekly",
      dayOfWeek: 3,
      rotate: true,
    },
    {
      text: "Publish next month's production plan",
      occurrence: "monthly",
      dayOfMonth: 24,
      rotate: true,
    },
    {
      text: "Answer delivery date questions from Sales",
      occurrence: "general",
      everyone: true,
    },
  ],

  Production: [
    {
      text: "Walk the lines and record what stopped them",
      occurrence: "daily",
    },
    {
      text: "Review last week's output against plan with the line leaders",
      occurrence: "weekly",
      dayOfWeek: 1,
      rotate: true,
    },
    {
      text: "Check the overtime hours against the plan",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Report the month's output, rework and scrap",
      occurrence: "monthly",
      dayOfMonth: 4,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the lines staffed and the work moving",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Production (R&D)": [
    {
      text: "Review the process changes the lines have asked for",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Report on the trials run this week",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Publish the month's process improvement report",
      occurrence: "monthly",
      dayOfMonth: 11,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Review the tooling and equipment plan",
      occurrence: "quarterly",
      dayOfMonth: 15,
    },
    {
      text: "Keep the process documents up to date",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Quality Control": [
    {
      text: "Inspect the day's finished goods before packing",
      occurrence: "daily",
      at: 1,
    },
    { text: "Record the day's reject reasons", occurrence: "daily", at: 2 },
    {
      text: "Report the week's reject rate by line",
      occurrence: "weekly",
      dayOfWeek: 1,
      rotate: true,
    },
    {
      text: "Check the plating thickness samples",
      occurrence: "weekly",
      dayOfWeek: 3,
      rotate: true,
    },
    {
      text: "Re-inspect the work sent back last week",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Issue the month's quality report",
      occurrence: "monthly",
      dayOfMonth: 6,
      rotate: true,
    },
    {
      text: "Audit one line against its work instruction",
      occurrence: "monthly",
      dayOfMonth: 19,
      rotate: true,
    },
    {
      text: "Hold the final say on what leaves the factory",
      occurrence: "general",
      everyone: true,
    },
  ],

  /* ----------------------------------------------------------------- design */

  "CAD Design": [
    // On the second chair, which is where docs/test-accounts.csv's `palita`
    // sits: the panel wants one staff account whose carried-over list is long
    // enough to scroll, and a department of four with nothing daily in it does
    // not produce one.
    {
      text: "Back up the day's files and update the style log",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Send the week's finished files to Model Making",
      occurrence: "weekly",
      dayOfWeek: 4,
      rotate: true,
    },
    {
      text: "Check the customer's drawings against the order",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Back up the month's CAD files off the machine",
      occurrence: "monthly",
      dayOfMonth: 22,
      rotate: true,
    },
    {
      text: "Keep the style library named the way the customer names it",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Accessory Center": [
    {
      text: "Issue accessories against the day's job cards",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Count the accessory stock",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Check the incoming findings against the order",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Reconcile the accessory stock to the ledger",
      occurrence: "monthly",
      dayOfMonth: 26,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the accessory store in order",
      occurrence: "general",
      everyone: true,
    },
  ],

  /* -------------------------------------------------------- the floor itself */

  Wax: [
    {
      text: "Check the wax injection temperatures at the start of the shift",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Count the trees sent to Casting",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Check the rubber moulds for wear",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Clean and re-oil the injection machines",
      occurrence: "weekly",
      dayOfWeek: 3,
      rotate: true,
    },
    {
      text: "Report the month's wax loss",
      occurrence: "monthly",
      dayOfMonth: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Log the moulds cut and the moulds retired",
      occurrence: "monthly",
      dayOfMonth: 17,
      rotate: true,
    },
    {
      text: "Keep the wax room to temperature",
      occurrence: "general",
      everyone: true,
    },
  ],

  Casting: [
    {
      text: "Record the flask temperatures and the burnout times",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Weigh the metal in and the metal out",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Check the investment mix against the batch card",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Report the month's casting loss and porosity rate",
      occurrence: "monthly",
      dayOfMonth: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the furnace and the flasks in working order",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Metal Center": [
    {
      text: "Cut the trees and book the pieces into the jobs",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Reconcile the metal issued to the metal returned",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Check the castings for porosity before they go on",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Count the scrap and send it for refining",
      occurrence: "weekly",
      dayOfWeek: 4,
      rotate: true,
    },
    {
      text: "Report the month's metal loss by job",
      occurrence: "monthly",
      dayOfMonth: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the metal locked and accounted for",
      occurrence: "general",
      everyone: true,
    },
  ],

  Stone: [
    {
      text: "Issue stones against the day's setting jobs",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Weigh the stones returned against the stones issued",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Sort and grade the incoming parcels",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Reconcile the stone stock to the ledger",
      occurrence: "monthly",
      dayOfMonth: 27,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the stone room locked and the safe in order",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Setting 1": [
    {
      text: "Check the day's set work under the loupe",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Count the stones lost and broken this week",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Check the setting tools and re-tip what needs it",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Report the month's setting rework",
      occurrence: "monthly",
      dayOfMonth: 6,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the benches lit and the sweeps collected",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Polishing Center": [
    {
      text: "Check the day's polished work before it goes to plating",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Change the wheels and the compounds",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Weigh the polishing sweeps",
      occurrence: "weekly",
      dayOfWeek: 5,
      rotate: true,
    },
    {
      text: "Report the month's polishing loss",
      occurrence: "monthly",
      dayOfMonth: 6,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the dust extraction working",
      occurrence: "general",
      everyone: true,
    },
  ],

  "Plating 1": [
    {
      text: "Titrate the baths and record the readings",
      occurrence: "daily",
      at: 1,
    },
    {
      text: "Check the rectifier settings against the standard",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Send the thickness samples to Quality Control",
      occurrence: "weekly",
      dayOfWeek: 4,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Report the month's gold consumption in the baths",
      occurrence: "monthly",
      dayOfMonth: 5,
      rotate: true,
      auditedBy: "quality",
    },
    {
      text: "Keep the bath log and the chemical safety sheets current",
      occurrence: "general",
      everyone: true,
    },
  ],

  /**
   * One person, and she has left. The duties are here so that
   * docs/test-accounts.csv's `somsak.left` has a history to have kept: an
   * archived person whose past work still names her is the fixture, and an
   * archived person with nothing behind her is not.
   */
  "Center 3": [
    { text: "Laser mark the day's finished pieces", occurrence: "daily" },
    {
      text: "Check the laser alignment against the test plate",
      occurrence: "weekly",
      dayOfWeek: 2,
      rotate: true,
    },
    {
      text: "Report the month's marking rework",
      occurrence: "monthly",
      dayOfMonth: 8,
      rotate: true,
    },
    { text: "Keep the laser serviced", occurrence: "general", everyone: true },
  ],
}

/** A position that can carry a duty, with the rank that decides which one. */
export type Chair = {
  positionId: string
  title: string
  level: Level
}

export type PlannedDuty = {
  positionId: string
  description: string
  occurrence: DemoOccurrence
  dayOfWeek: number | null
  dayOfMonth: number | null
  auditedBy: Auditor | null
}

/** Highest rank first, then title, so `at: 0` means the same thing every run. */
export function inRankOrder(chairs: readonly Chair[]): Chair[] {
  return [...chairs].sort(
    (a, b) =>
      rankOf(a.level) - rankOf(b.level) || a.title.localeCompare(b.title),
  )
}

/**
 * One department's shapes turned into duties on its actual positions.
 *
 * The three placements are the whole of it. `everyone` gives each holder their
 * own copy; `rotate` deals the shapes of one occurrence round the department so
 * every holder ends with exactly one weekly and one monthly; `at` puts a duty
 * on the nth chair from the top and nowhere else, which is how a daily check
 * ends up with the line leader rather than with all eleven people on the line.
 *
 * A shape that names a chair the department does not have is dropped rather
 * than moved. A two-person department has no third-ranked holder, and putting
 * its daily check on somebody else would be inventing an arrangement to avoid
 * admitting the shape did not fit.
 */
export function dutiesFor(
  shapes: readonly Shape[],
  chairs: readonly Chair[],
): PlannedDuty[] {
  const ranked = inRankOrder(chairs)
  if (ranked.length === 0) return []

  const duties: PlannedDuty[] = []
  const put = (shape: Shape, chair: Chair) =>
    duties.push({
      positionId: chair.positionId,
      description: shape.text,
      occurrence: shape.occurrence,
      dayOfWeek: shape.occurrence === "weekly" ? (shape.dayOfWeek ?? 1) : null,
      dayOfMonth:
        shape.occurrence === "monthly" || shape.occurrence === "quarterly"
          ? (shape.dayOfMonth ?? 1)
          : null,
      auditedBy: shape.auditedBy ?? null,
    })

  for (const shape of shapes) {
    if (shape.everyone) {
      for (const chair of ranked) put(shape, chair)
    } else if (!shape.rotate) {
      const chair = ranked[shape.at ?? 0]
      if (chair) put(shape, chair)
    }
  }

  // The rotations last, grouped by occurrence, so that the deal is over the
  // shapes of one kind and not over a list that happens to hold a daily in the
  // middle of it.
  const rotating = shapes.filter((s) => s.rotate)
  for (const occurrence of new Set(rotating.map((s) => s.occurrence))) {
    const hand = rotating.filter((s) => s.occurrence === occurrence)
    ranked.forEach((chair, i) => put(hand[i % hand.length]!, chair))
  }

  return duties
}
