/**
 * The page-scoped read tools Piggy gets while docked.
 *
 * The equivalent answers already exist in the MCP server, but every one of
 * those tools is an authenticated HTTP call carrying a `pig_…` API key. Piggy
 * has no way to mint one and calling the API back through the network to read
 * a database it already holds a handle to would be a round trip for nothing —
 * so the queries are ported here as direct Drizzle reads.
 *
 * The calendar is the exception, and deliberately so. Its projection spans
 * thirteen kinds across nine tables and it is the answer a user is looking at
 * on /calendar; a second implementation here would not merely duplicate it,
 * it would disagree with it, and Piggy contradicting the page it has just been
 * told it is reading is worse than Piggy having no calendar tool. So @pig/piggy
 * depends on @pig/api and calls `CalendarService` in-process — the service
 * layer takes a `Database`, not a request, precisely so it can be called this
 * way. Lifting it into @pig/core instead would drag nine table imports into a
 * package the browser bundles.
 *
 * The hard constraint is size, not capability. Interactive chat runs at
 * `max_tokens` 1024 across at most four turns, so a tool that returns rows
 * spends the whole budget on transcription and truncates mid-answer. Every
 * result here is aggregated first and capped at a handful of exemplar rows:
 * the model is given the conclusion and enough evidence to quote, never the
 * ledger. The bounded reads that feed them are wide, but that width never
 * leaves this process.
 */
import {
  ACCOUNT_SIDES,
  CONSUMING_ALLOCATION_STATUSES,
  DEMAND_OPEN_STAGES,
  DEMAND_STAGES,
  MOTION_KINDS,
  RESERVING_ALLOCATION_STATUSES,
  SUPPLY_OPEN_STAGES,
  aggregateMargin,
  breakEvenPricePerGpuHourCents,
  computeMargin,
  formatCents,
  type AllocationInput,
  type CalendarEvent,
  type CalendarEventKind,
  type DemandStage,
  type MarginResult,
  type MotionKind,
  type PiggyPageRoute,
} from '@pig/core';
import {
  accounts,
  allocations,
  capacityCommitments,
  contacts,
  demandDeals,
  engagementArtifacts,
  engagements,
  motionTemplates,
  qualificationScores,
  supplyDeals,
  type Database,
} from '@pig/db';
import { CalendarService } from '@pig/api/src/services/calendar';
import {
  and,
  count,
  countDistinct,
  desc,
  eq,
  gte,
  ilike,
  inArray,
  isNotNull,
  isNull,
  or,
  type SQL,
} from 'drizzle-orm';
import { z } from 'zod';
import { likeFragment } from './chat-tools';
import { piggyPageGuide, type PiggyPageToolName } from './page-routes';
import { defineTool, type AgentTool } from './provider';

const noInput = z.object({}).strict();

/** How many exemplar rows a result may carry. Everything else is a total. */
const EXEMPLARS = 8;

/** Bound on the internal read. Wide enough for a real book, still finite. */
const SCAN_LIMIT = 500;

/**
 * A bounded read that knows whether it was bounded.
 *
 * Every list here is capped, and a cap the caller cannot see is how a
 * book-level figure ends up asserted over an arbitrary slice: the model is
 * told these results are already aggregated and quotes them verbatim. So each
 * read asks for one row more than its budget — the same trick the calendar
 * service uses — and every result that could have been cut carries the flag.
 */
function bounded<Row>(rows: Row[], limit = SCAN_LIMIT): { rows: Row[]; truncated: boolean } {
  const truncated = rows.length > limit;
  return { rows: truncated ? rows.slice(0, limit) : rows, truncated };
}

/**
 * Written into the headline because that is the field the model quotes. A
 * `truncated: true` sitting further down the payload is routinely ignored.
 */
const TRUNCATION_NOTE =
  'One or more reads hit their row cap, so these figures cover part of a larger ' +
  'book — present them as a lower bound, not as the whole.';

/** Prefixes a count the model must not read as exact. */
export function atLeast(rows: number, truncated: boolean): string {
  return truncated ? `at least ${rows}` : `${rows}`;
}

/**
 * What a result covers, and what it was drawn from.
 *
 * Measured in production on /capacity, an hour before this was written. Asked
 * "How many capacity commitments are on the book?", the model called
 * `pig_get_idle_capacity` — the only tool that page offers — and answered "3".
 * The book held 5. The tool filters to blocks at least 25% unsold, so 3 was the
 * size of a filter; nothing in the payload said so, and reading the length of
 * the list it had been handed as the size of the book was the only reading the
 * data supported.
 *
 * The system prompt already forbade exactly that, naming this exact tool, and
 * the model did it anyway. Prompting a 30B model out of a mistake its data
 * invites does not work, so the data stopped inviting it: every result here
 * that carries a count or a collection carries this object beside it, naming
 * the filter, the denominator it was drawn from, and how much of the matched
 * set is actually listed. A filtered count is therefore never the only number
 * in its own result.
 *
 * One shape to learn rather than one per tool — a different shape per tool is
 * how this happened. The result's primary subject gets a top-level `scope`;
 * every other count or collection in the same payload gets its own, nested
 * where the payload already groups it and named `<field>Scope` where it does
 * not. And `summary` restates the numbers as prose on purpose — it is the
 * field a small model quotes, and a figure it has to assemble out of three
 * other fields is a figure it will assemble wrongly.
 *
 * `filters` is not decoration either. This product has shipped three different
 * idle figures across three surfaces because each applied its own threshold, so
 * a result that does not name the threshold it used cannot be reconciled with
 * the screen beside it.
 */
export interface ResultScope {
  /** The whole scope in one sentence, figures included. Quote this. */
  summary: string;
  /** What made a row match, as a clause: "are at least 25% unsold". */
  covers: string;
  /** How many rows matched. Never the answer to "how many are there". */
  matched: number;
  /** The set `matched` was drawn from. This is the total. */
  total: number;
  /** What `total` counts, as a noun phrase. */
  totalLabel: string;
  /** How many of `matched` this payload lists. The rest are counted only. */
  listed: number;
  /** Every filter applied, named, so a threshold is never invisible. */
  filters: Record<string, string | number | boolean | null>;
  /** True when a read hit its row cap, so both figures are lower bounds. */
  truncated: boolean;
}

export function resultScope(input: {
  covers: string;
  matched: number;
  total: number;
  totalLabel: string;
  listed: number;
  filters?: Record<string, string | number | boolean | null>;
  truncated?: boolean;
}): ResultScope {
  const { covers, matched, total, totalLabel, listed } = input;
  const filters = input.filters ?? {};
  const truncated = input.truncated ?? false;
  // Nothing was filtered out, so `matched` IS the total and saying otherwise
  // would teach the model to distrust a figure that is exact.
  const unfiltered = matched === total && Object.keys(filters).length === 0;
  const listedClause = listed > 0 ? `; ${listed} listed here` : '';
  // Both figures are hedged together when a read was cut. Hedging only the
  // total would present a capped `matched` as exact, which is the same class of
  // overstatement this whole object exists to stop.
  const summary = unfiltered
    ? `All ${atLeast(total, truncated)} ${totalLabel}${listedClause}.`
    : `${atLeast(matched, truncated)} of ${atLeast(total, truncated)} ${totalLabel} ` +
      `${covers}${listedClause}. That is a filtered count — the total is ` +
      `${atLeast(total, truncated)} ${totalLabel}.`;
  return {
    summary: truncated ? `${summary} ${TRUNCATION_NOTE}` : summary,
    covers,
    matched,
    total,
    totalLabel,
    listed,
    filters,
    truncated,
  };
}

/** The denominator every commitment figure in this file is drawn from. */
const COMMITMENTS_LABEL = 'live capacity commitment(s) on the book';

/** The denominators the two pipelines are drawn from. */
const DEMAND_DEALS_LABEL = 'demand deal(s) on the book';
const SUPPLY_DEALS_LABEL = 'supply deal(s) on the book';

/** The denominators the two party tables are drawn from. */
const ACCOUNTS_LABEL = 'account(s) on the book';
const CONTACTS_LABEL = 'contact(s) in the CRM';

/**
 * The denominators the motion tools are drawn from.
 *
 * The library one says `shared` and says `lineage` because both are real
 * restrictions on the figure: private drafts are outside it by design, and a
 * lineage is one piece of practice however many versions it has carried.
 */
const MOTION_LIBRARY_LABEL = 'shared template lineage(s) in the motion library';
const ENGAGEMENTS_LABEL = 'engagement(s) on the book';

/** One whole-table count, for use as a denominator. */
function rowCount(rows: readonly { value: number }[]): number {
  return rows[0]?.value ?? 0;
}

/**
 * One tool per page. The dock is present everywhere, so the model sees this
 * list on every message — a second tool would be a second thing to choose
 * wrongly, and choosing wrongly costs one of four turns.
 */
export function createPagePigTools(db: Database, route: PiggyPageRoute): AgentTool[] {
  return [pageTool(db, piggyPageGuide(route).tool)];
}

function pageTool(db: Database, name: PiggyPageToolName): AgentTool {
  switch (name) {
    case 'pig_get_margin_summary':
      return defineTool({
        name,
        description:
          'Read book-level margin across every live capacity commitment: revenue, cost, ' +
          'gross margin, utilisation and idle hours, plus the largest blocks. Cost is charged ' +
          'against the full commitment, not only the hours that sold.',
        inputSchema: noInput,
        execute: async () => readMarginSummary(db),
      });
    case 'pig_get_idle_capacity':
      return defineTool({
        name,
        description:
          'Read committed capacity that is bought and unsold, ranked by what the idle hours ' +
          'cost, with the break-even price for the remainder of each block.',
        inputSchema: noInput,
        execute: async () => readIdleCapacity(db),
      });
    case 'pig_get_pipeline':
      return defineTool({
        name,
        description:
          'Read the open demand and supply pipelines: how many deals sit at each stage, what ' +
          'they are worth, and the largest few on each side.',
        inputSchema: noInput,
        execute: async () => readPipeline(db),
      });
    case 'pig_get_calendar_ahead':
      return defineTool({
        name,
        description:
          'Read the same calendar projection the /calendar page renders: everything dated in ' +
          'the near future — deals expected to close, contract effective, expiry and execution ' +
          'dates, renewal notices, obligations due, capacity and allocation windows, hold ' +
          'expiries, supply availability, export authorisation and compliance artefact ' +
          'expiries, and calendar entries — plus what is already overdue.',
        inputSchema: z
          .object({
            /**
             * `.nullish()` rather than `.optional()`, and `.describe()` before
             * it rather than after.
             *
             * `zodToJsonSchema(..., { target: 'openAi' })` emits an optional
             * field as required-and-nullable, so a model that follows the
             * schema it was handed sends `{"withinDays": null}` — which
             * `.optional()` rejects, spending one of four turns on a tool
             * result that reads as a failure. Described after the wrapper, the
             * sentence is dropped from the emitted schema entirely and the
             * default is never communicated.
             */
            withinDays: z
              .number()
              .int()
              .min(1)
              .max(365)
              .describe('Horizon in days. null uses the default of 30.')
              .nullish(),
          })
          .strict(),
        execute: async ({ withinDays }) => readCalendarAhead(db, withinDays ?? 30),
      });
    case 'pig_get_motion_summary':
      return defineTool({
        name,
        description:
          'Read whether the go-to-market motion is repeating: which demand stages the shared ' +
          'library covers and which it does not, how many live engagements sit at each stage, ' +
          'the shared library by kind, and the artifacts most recently promoted back into it. ' +
          'Private drafts are not visible to this tool.',
        inputSchema: noInput,
        execute: async () => readMotionSummary(db),
      });
    case 'pig_search_motion_library':
      return defineTool({
        name,
        description:
          'Search the shared motion library — discovery guides, qualification frameworks, POC ' +
          'structures, proposal blocks, pricing inputs, reference architectures, case studies, ' +
          'technical narratives and deployment playbooks. Returns the newest version of each ' +
          'template. Private drafts are never searched, whoever is asking.',
        inputSchema: z
          .object({
            kind: z
              .enum(MOTION_KINDS)
              .describe('Restrict to one kind of template. null for every kind.')
              .nullish(),
            stage: z
              .enum(DEMAND_STAGES)
              .describe('Restrict to templates serving one demand stage. null for every stage.')
              .nullish(),
            query: z
              .string()
              .trim()
              .min(2)
              .max(64)
              .describe(
                'Word or phrase matched against the title, summary and slug. null returns the ' +
                  'whole shared library.',
              )
              .nullish(),
          })
          .strict(),
        execute: async (filter) => readMotionLibrary(db, filter),
      });
    case 'pig_get_engagement':
      return defineTool({
        name,
        description:
          'Read the engagements running against demand deals: which stage the deal sits at, ' +
          'how many artifacts have been produced and how many are final, and the latest ' +
          'qualification score with its band.',
        inputSchema: z
          .object({
            query: z
              .string()
              .trim()
              .min(2)
              .max(64)
              .describe(
                'Word or phrase matched against the deal name and the engagement summary. ' +
                  'null returns the most recently opened engagements.',
              )
              .nullish(),
          })
          .strict(),
        execute: async ({ query }) => readEngagements(db, query ?? null),
      });
    case 'pig_get_workspace_summary':
      return defineTool({
        name,
        description:
          'Read a bounded overview of the PIG workspace: how many accounts (by side) and ' +
          'contacts are on the book, book margin and utilisation, how many deals exist and how ' +
          'many are open on each side, and the worst idle capacity. Counts only — it returns no ' +
          'account, contact or deal rows, and it cannot see the team, settings, imports or ' +
          'facts. This cannot inspect the filesystem or external systems.',
        inputSchema: noInput,
        execute: async () => readWorkspaceSummary(db),
      });
  }
}

// ---------------------------------------------------------------------------
// The book
// ---------------------------------------------------------------------------

interface LiveBlock {
  name: string;
  gpuType: string;
  gpuCount: number;
  startsAt: Date;
  endsAt: Date;
  totalGpuHours: number;
  soldGpuHours: number;
  /** Held by a live hold: removed from availability, but not revenue. */
  heldGpuHours: number;
  costPerGpuHourCents: number;
  /** The sold slices, kept so book totals can sum cents rather than ratios. */
  sold: readonly AllocationInput[];
  margin: MarginResult;
  breakEvenPriceCents: number | null;
}

interface LiveBook {
  blocks: LiveBlock[];
  /** True when the book is wider than SCAN_LIMIT, so the totals are partial. */
  truncated: boolean;
}

/**
 * Live commitments with sold and held hours counted separately.
 *
 * A port of `CapacityService.availability`, minus the shape integration and
 * matching the API does not need here. Sold and held stay distinct because a
 * pipeline of optimistic holds must never be able to make the book look full.
 * Expired holds are ignored rather than swept, so the figures are right even
 * when the cleanup job is behind.
 *
 * The cap is reported rather than hidden: revenue, cost and gross margin here
 * are sums over whatever came back, and past 500 live commitments that is an
 * arbitrary slice of the book being stated as the book.
 */
async function readLiveBlocks(db: Database, now = new Date()): Promise<LiveBook> {
  const { rows: commitments, truncated } = bounded(
    await db
      .select()
      .from(capacityCommitments)
      .where(and(isNull(capacityCommitments.terminatedAt), gte(capacityCommitments.endsAt, now)))
      .limit(SCAN_LIMIT + 1),
  );
  if (commitments.length === 0) return { blocks: [], truncated };

  const reservations = await db
    .select()
    .from(allocations)
    .where(
      and(
        inArray(
          allocations.capacityCommitmentId,
          commitments.map((commitment) => commitment.id),
        ),
        inArray(allocations.status, [...RESERVING_ALLOCATION_STATUSES]),
      ),
    );

  const blocks = commitments.map((commitment) => {
    const mine = reservations.filter((row) => row.capacityCommitmentId === commitment.id);
    let soldGpuHours = 0;
    let heldGpuHours = 0;
    for (const row of mine) {
      // numeric columns arrive as strings; adding them unconverted concatenates.
      const hours = Number(row.gpuHours);
      if ((CONSUMING_ALLOCATION_STATUSES as readonly string[]).includes(row.status)) {
        soldGpuHours += hours;
      } else if (!row.holdExpiresAt || row.holdExpiresAt > now) {
        heldGpuHours += hours;
      }
    }

    const book = {
      gpuHours: Number(commitment.totalGpuHours),
      costPerGpuHourCents: commitment.costPerGpuHourCents,
    };
    const sold = mine
      .filter((row) => (CONSUMING_ALLOCATION_STATUSES as readonly string[]).includes(row.status))
      .map((row) => ({
        gpuHours: Number(row.gpuHours),
        pricePerGpuHourCents: row.pricePerGpuHourCents,
      }));

    return {
      name: commitment.name,
      gpuType: commitment.gpuType,
      gpuCount: commitment.gpuCount,
      startsAt: commitment.startsAt,
      endsAt: commitment.endsAt,
      totalGpuHours: book.gpuHours,
      soldGpuHours,
      heldGpuHours,
      costPerGpuHourCents: commitment.costPerGpuHourCents,
      sold,
      margin: computeMargin(book, sold),
      breakEvenPriceCents: breakEvenPricePerGpuHourCents(book, sold),
    };
  });

  return { blocks, truncated };
}

function bookTotals(blocks: readonly LiveBlock[]): MarginResult {
  // Sum cents, never average per-block percentages: an average of ratios
  // weights a tiny block equally with a huge one.
  return aggregateMargin(
    blocks.map((block) => ({
      commitment: {
        gpuHours: block.totalGpuHours,
        costPerGpuHourCents: block.costPerGpuHourCents,
      },
      allocations: block.sold,
    })),
  );
}

async function readMarginSummary(db: Database): Promise<unknown> {
  const { blocks, truncated } = await readLiveBlocks(db);
  const totals = bookTotals(blocks);
  const largest = [...blocks]
    .sort((a, b) => b.margin.costCents - a.margin.costCents)
    .slice(0, EXEMPLARS);

  return {
    headline:
      `Revenue ${formatCents(totals.revenueCents)} against cost ${formatCents(totals.costCents)}; ` +
      `gross margin ${formatCents(totals.grossMarginCents)} (${percent(totals.grossMarginPct)}) ` +
      `at ${percent(totals.utilisation)} utilisation across all ` +
      `${atLeast(blocks.length, truncated)} ${COMMITMENTS_LABEL} — the whole book, unfiltered. ` +
      `The ${largest.length} largest by cost are listed; the book holds ` +
      `${atLeast(blocks.length, truncated)}.` +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    /**
     * Unfiltered, and the only tool here that is: `matched` equals `total`, so
     * `liveCommitments` below is a real answer to "how many are on the book".
     * `largestBlocks` is still a slice, which is what `listed` is for.
     */
    scope: resultScope({
      covers: 'are live',
      matched: blocks.length,
      total: blocks.length,
      totalLabel: COMMITMENTS_LABEL,
      listed: largest.length,
      truncated,
    }),
    truncated,
    totals: {
      revenueCents: totals.revenueCents,
      costCents: totals.costCents,
      grossMarginCents: totals.grossMarginCents,
      grossMarginPct: totals.grossMarginPct,
      utilisation: totals.utilisation,
      idleGpuHours: Math.round(totals.idleGpuHours),
      marginPerAllocatedGpuHourCents: totals.marginPerAllocatedGpuHourCents,
    },
    liveCommitments: blocks.length,
    largestBlocks: largest.map((block) => ({
      name: block.name,
      gpuType: block.gpuType,
      utilisation: block.margin.utilisation,
      soldGpuHours: Math.round(block.soldGpuHours),
      totalGpuHours: Math.round(block.totalGpuHours),
      costPerGpuHourCents: block.costPerGpuHourCents,
      grossMarginCents: block.margin.grossMarginCents,
    })),
  };
}

/**
 * The threshold and horizon this tool filters on.
 *
 * The same defaults the API and MCP use — and NOT the same as the workspace
 * summary's worst-idle list, which takes any block with idle hours at all.
 * Both are correct for what they answer and they return different counts, so
 * each states its own threshold in `filters` rather than leaving the reader to
 * reconcile two figures that were never the same figure.
 */
const IDLE_THRESHOLD_PCT = 0.25;
const IDLE_WITHIN_DAYS = 30;

/** Idle blocks, on the same defaults the API and MCP use: 25% within 30 days. */
async function readIdleCapacity(db: Database): Promise<unknown> {
  const now = new Date();
  const horizon = new Date(now.getTime() + IDLE_WITHIN_DAYS * 86_400_000);
  const { blocks, truncated } = await readLiveBlocks(db, now);
  const idle = blocks
    .filter(
      (block) => block.startsAt <= horizon && 1 - block.margin.utilisation >= IDLE_THRESHOLD_PCT,
    )
    .map((block) => ({
      block,
      idleGpuHours: block.margin.idleGpuHours,
      // The number that makes the case: what the unsold hours already cost us.
      idleCostCents: Math.round(block.margin.idleGpuHours * block.costPerGpuHourCents),
    }))
    .sort((a, b) => b.idleCostCents - a.idleCostCents);

  const totalIdleCostCents = idle.reduce((sum, row) => sum + row.idleCostCents, 0);
  const listed = idle.slice(0, EXEMPLARS);
  const filter =
    `are at least ${IDLE_THRESHOLD_PCT * 100}% unsold and start within ` +
    `${IDLE_WITHIN_DAYS} day(s)`;

  return {
    /**
     * The sentence the production defect was answered from, so it carries the
     * denominator first and the filtered figure second. "3" alone was true of
     * the filter and false of the book; "3 of 5" cannot be misread as 5.
     */
    headline:
      (idle.length === 0
        ? `None of the ${atLeast(blocks.length, truncated)} ${COMMITMENTS_LABEL} ${filter}.`
        : `${idle.length} of ${atLeast(blocks.length, truncated)} ${COMMITMENTS_LABEL} ${filter}, ` +
          `${formatCents(totalIdleCostCents)} of capacity bought and not yet earning. ` +
          `${idle.length} is a filtered count — the book holds ` +
          `${atLeast(blocks.length, truncated)} live commitment(s) in total.`) +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    scope: resultScope({
      covers: filter,
      matched: idle.length,
      total: blocks.length,
      totalLabel: COMMITMENTS_LABEL,
      listed: listed.length,
      filters: { idleThresholdPct: IDLE_THRESHOLD_PCT, withinDays: IDLE_WITHIN_DAYS },
      truncated,
    }),
    truncated,
    /** The denominator, repeated as a bare field: this is the size of the book. */
    liveCommitments: blocks.length,
    thresholdPct: IDLE_THRESHOLD_PCT,
    withinDays: IDLE_WITHIN_DAYS,
    idleBlocks: idle.length,
    totalIdleCostCents,
    blocks: listed.map((row) => ({
      name: row.block.name,
      gpuType: row.block.gpuType,
      gpuCount: row.block.gpuCount,
      utilisation: row.block.margin.utilisation,
      idleGpuHours: Math.round(row.idleGpuHours),
      idleCostCents: row.idleCostCents,
      // What the rest of the block must fetch to come out even.
      breakEvenPricePerGpuHourCents: row.block.breakEvenPriceCents,
      endsAt: row.block.endsAt.toISOString(),
    })),
  };
}

// ---------------------------------------------------------------------------
// The two pipelines
// ---------------------------------------------------------------------------

async function readPipeline(db: Database): Promise<unknown> {
  // The two whole-table counts are the denominators. Without them "12 open
  // demand deals" is a filtered count with nothing to be filtered from, and
  // "how many deals do we have" is answered with the number of open ones.
  const [demandRead, supplyRead, demandAll, supplyAll] = await Promise.all([
    db
      .select()
      .from(demandDeals)
      .where(inArray(demandDeals.stage, [...DEMAND_OPEN_STAGES]))
      .limit(SCAN_LIMIT + 1),
    db
      .select()
      .from(supplyDeals)
      .where(inArray(supplyDeals.stage, [...SUPPLY_OPEN_STAGES]))
      .limit(SCAN_LIMIT + 1),
    db.select({ value: count() }).from(demandDeals),
    db.select({ value: count() }).from(supplyDeals),
  ]);
  const { rows: demand, truncated: demandTruncated } = bounded(demandRead);
  const { rows: supply, truncated: supplyTruncated } = bounded(supplyRead);
  const truncated = demandTruncated || supplyTruncated;
  const demandTotal = rowCount(demandAll);
  const supplyTotal = rowCount(supplyAll);
  const demandListed = Math.min(demand.length, EXEMPLARS);
  const supplyListed = Math.min(supply.length, EXEMPLARS);
  const openStages = 'are at an open stage (not closed, not lost)';

  // Total contract value where it is known, annual value otherwise: a deal
  // valued only by ACV is still worth counting, and treating it as zero would
  // understate the pipeline rather than admit the gap.
  const valueOf = (deal: (typeof demand)[number]) => deal.tcvCents ?? deal.acvCents ?? 0;
  const demandValueCents = demand.reduce((sum, deal) => sum + valueOf(deal), 0);

  return {
    headline:
      `${demand.length} of ${atLeast(demandTotal, demandTruncated)} ${DEMAND_DEALS_LABEL} are open, ` +
      `worth ${formatCents(demandValueCents)}, and ${supply.length} of ` +
      `${atLeast(supplyTotal, supplyTruncated)} ${SUPPLY_DEALS_LABEL} are open — ` +
      `${demand.length + supply.length} of ${atLeast(demandTotal + supplyTotal, truncated)} ` +
      'deal(s) on the book in all. Those are open-stage counts, not the size of either pipeline.' +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    // Both sides together, so a question about "deals" has a denominator too.
    scope: resultScope({
      covers: openStages,
      matched: demand.length + supply.length,
      total: demandTotal + supplyTotal,
      totalLabel: 'deal(s) on the book, demand and supply together',
      listed: demandListed + supplyListed,
      filters: { stages: 'open only' },
      truncated,
    }),
    truncated: { demandDeals: demandTruncated, supplyDeals: supplyTruncated },
    demand: {
      scope: resultScope({
        covers: openStages,
        matched: demand.length,
        total: demandTotal,
        totalLabel: DEMAND_DEALS_LABEL,
        listed: demandListed,
        filters: { stages: [...DEMAND_OPEN_STAGES].join(', ') },
        truncated: demandTruncated,
      }),
      openDeals: demand.length,
      totalDeals: demandTotal,
      valueCents: demandValueCents,
      byStage: countByStage(demand.map((deal) => deal.stage)),
      largest: [...demand]
        .sort((a, b) => valueOf(b) - valueOf(a))
        .slice(0, EXEMPLARS)
        .map((deal) => ({
          name: deal.name,
          stage: deal.stage,
          valueCents: valueOf(deal),
          expectedCloseDate: deal.expectedCloseDate?.toISOString() ?? null,
        })),
    },
    supply: {
      scope: resultScope({
        covers: openStages,
        matched: supply.length,
        total: supplyTotal,
        totalLabel: SUPPLY_DEALS_LABEL,
        listed: supplyListed,
        filters: { stages: [...SUPPLY_OPEN_STAGES].join(', ') },
        truncated: supplyTruncated,
      }),
      openDeals: supply.length,
      totalDeals: supplyTotal,
      byStage: countByStage(supply.map((deal) => deal.stage)),
      largest: [...supply]
        .sort((a, b) => (b.gpuCount ?? 0) - (a.gpuCount ?? 0))
        .slice(0, EXEMPLARS)
        .map((deal) => ({
          name: deal.name,
          stage: deal.stage,
          gpuType: deal.gpuType,
          gpuCount: deal.gpuCount,
          targetCostPerGpuHourCents: deal.targetCostPerGpuHourCents,
        })),
    },
  };
}

function countByStage(stages: readonly string[]): Record<string, number> {
  const counts: Record<string, number> = {};
  for (const stage of stages) counts[stage] = (counts[stage] ?? 0) + 1;
  return counts;
}

// ---------------------------------------------------------------------------
// Dates
// ---------------------------------------------------------------------------

/**
 * How far back a lapsed item still counts as this week's problem.
 *
 * Unbounded, the overdue arm surfaced whatever was oldest — a stale obligation
 * from two years ago crowding out a renewal notice that lapsed on Friday. Past
 * a quarter it is a data-hygiene job, not an operational one, so the window
 * stops there and the exemplars run most-recent-first within it.
 */
const OVERDUE_LOOKBACK_DAYS = 90;

/**
 * The kinds that can honestly be late.
 *
 * Lateness needs a completion column: an obligation, a renewal notice and a
 * deal's expected close all have somewhere to record that the thing happened.
 * A capacity window that has ended is finished, not overdue, and an expiry
 * that has passed is a state of the world rather than an errand — listing
 * either as overdue work invents a backlog.
 */
const OVERDUE_KINDS = [
  'obligation_due',
  'renewal_notice',
  'expected_close',
] as const satisfies readonly CalendarEventKind[];

/**
 * What is dated in the near future — the same projection /calendar renders.
 *
 * This used to reimplement the projection over two tables. The page shows
 * thirteen kinds, so Piggy asserted a total that was missing contract
 * expiries, renewal notices, hold expiries, capacity and allocation windows,
 * authorisation and artefact expiries, and every human-owned calendar entry.
 * Being confidently wrong about the screen in front of the reader is the one
 * failure that costs the tool its credibility, so it calls the service.
 *
 * Two projections, not one: overdue work sits BEFORE `now` and the horizon
 * starts at it, and a single wide window would let a quarter of stale rows
 * consume the per-source budget that the coming month needs.
 *
 * Counts are taken over the full projected set and only then sliced for
 * exemplars — the previous version interpolated the capped list lengths, so a
 * book with two hundred overdue obligations reported eight, and the system
 * prompt tells the model to quote these figures rather than recompute them.
 */
async function readCalendarAhead(db: Database, withinDays: number): Promise<unknown> {
  const now = new Date();
  const horizon = new Date(now.getTime() + withinDays * 86_400_000);
  const lookback = new Date(now.getTime() - OVERDUE_LOOKBACK_DAYS * 86_400_000);
  const calendar = new CalendarService(db, () => now);

  const [ahead, behind] = await Promise.all([
    calendar.project({ from: now, to: horizon }),
    calendar.project({ from: lookback, to: now, kinds: OVERDUE_KINDS }),
  ]);

  // A done event is a dated fact, not something anyone must act on; the page
  // shows it greyed out and a count that includes it reads as a workload.
  const upcoming = ahead.events.filter((event) => event.state !== 'done');
  const overdue = behind.events.filter((event) => event.state === 'overdue');
  const truncated = ahead.truncated || behind.truncated;
  const upcomingByKind = countByKind(upcoming);
  const upcomingListed = Math.min(upcoming.length, EXEMPLARS * 2);
  const overdueListed = Math.min(overdue.length, EXEMPLARS);
  /**
   * Both denominators are windows, not the book, and the labels say so. Nothing
   * here can answer "how many obligations are there" — only how many fall in
   * these dates — so a label that read "obligations on the book" would be the
   * same lie in a different tool.
   */
  const windowLabel = `dated item(s) falling in the next ${withinDays} day(s), done or not`;
  const lookbackLabel = `dated item(s) in the last ${OVERDUE_LOOKBACK_DAYS} day(s) that can fall late`;

  return {
    headline:
      `Next ${withinDays} day(s) only, not the whole book: ` +
      `${atLeast(upcoming.length, ahead.truncated)} of ` +
      `${atLeast(ahead.events.length, ahead.truncated)} dated item(s) ` +
      `across ${Object.keys(upcomingByKind).length} kind(s), of which ` +
      `${ahead.totals.obligationCount} obligation(s) due, ${ahead.totals.closingCount} demand ` +
      `deal(s) expected to close worth ${formatCents(ahead.totals.weightedPipelineCents)} ` +
      `weighted, ${ahead.totals.renewalCount} renewal notice(s) and ` +
      `${ahead.totals.expiringAuthorizationCount} export authorisation(s) expiring; ` +
      `${atLeast(overdue.length, behind.truncated)} item(s) overdue in the last ` +
      `${OVERDUE_LOOKBACK_DAYS} day(s).` +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    scope: resultScope({
      covers: 'are still outstanding',
      matched: upcoming.length,
      total: ahead.events.length,
      totalLabel: windowLabel,
      listed: upcomingListed,
      filters: { withinDays, state: 'excludes done' },
      truncated: ahead.truncated,
    }),
    withinDays,
    truncated,
    /**
     * Counted in SQL by the service, so these five stay exact even when a
     * source truncates. Everything else on this payload is counted off the
     * event list and moves with `truncated`.
     */
    exactTotals: {
      obligationsDue: ahead.totals.obligationCount,
      dealsExpectedToClose: ahead.totals.closingCount,
      weightedPipelineCents: ahead.totals.weightedPipelineCents,
      renewalNotices: ahead.totals.renewalCount,
      expiringExportAuthorizations: ahead.totals.expiringAuthorizationCount,
    },
    // The top-level `scope` is this list's: upcoming work is what the tool is
    // for, and a second copy of the same eight fields is eight fields of budget.
    upcoming: {
      count: upcoming.length,
      inWindow: ahead.events.length,
      truncated: ahead.truncated,
      byKind: upcomingByKind,
      byState: countByState(upcoming),
      // Soonest first: the near edge of the horizon is what gets acted on.
      events: upcoming.slice(0, EXEMPLARS * 2).map(exemplar),
    },
    overdue: {
      scope: resultScope({
        covers: 'have lapsed without being completed',
        matched: overdue.length,
        total: behind.events.length,
        totalLabel: lookbackLabel,
        listed: overdueListed,
        filters: {
          lookbackDays: OVERDUE_LOOKBACK_DAYS,
          kinds: [...OVERDUE_KINDS].join(', '),
        },
        truncated: behind.truncated,
      }),
      count: overdue.length,
      truncated: behind.truncated,
      lookbackDays: OVERDUE_LOOKBACK_DAYS,
      byKind: countByKind(overdue),
      events: [...overdue]
        .sort((a, b) => b.startsAt.localeCompare(a.startsAt))
        .slice(0, EXEMPLARS)
        .map(exemplar),
    },
  };
}

/**
 * One event, small enough to quote. `meta`, `id` and the ids are dropped: the
 * model cannot navigate and a uuid in a 1024-token answer is pure cost.
 */
function exemplar(event: CalendarEvent): Record<string, unknown> {
  return {
    kind: event.kind,
    title: event.title,
    startsAt: event.startsAt,
    endsAt: event.endsAt,
    state: event.state,
    accountName: event.accountName,
    amountCents: event.amountCents,
  };
}

function countByKind(events: readonly CalendarEvent[]): Record<string, number> {
  const counts: Record<string, number> = {};
  for (const event of events) counts[event.kind] = (counts[event.kind] ?? 0) + 1;
  return counts;
}

function countByState(events: readonly CalendarEvent[]): Record<string, number> {
  const counts: Record<string, number> = {};
  for (const event of events) counts[event.state] = (counts[event.state] ?? 0) + 1;
  return counts;
}

// ---------------------------------------------------------------------------
// The parties
// ---------------------------------------------------------------------------

interface BookParties {
  /** What the /accounts list shows: every account that is not archived. */
  onBook: number;
  /** Archived accounts, excluded from `onBook` and counted so the gap is visible. */
  archived: number;
  /** The book partitioned by side. The three buckets sum to `onBook`. */
  bySide: Record<string, number>;
  /** Every contact row, which is what the contacts tab lists. */
  contacts: number;
}

/**
 * How many accounts and contacts the book holds.
 *
 * Measured in production on /accounts, minutes before this was written. Asked
 * "How many accounts are on the book in total? One sentence.", Piggy answered
 * "The book contains 7 demand deals (accounts) in total." The book held 17
 * accounts and 7 demand deals — so the figure was real, the payload had
 * correctly scoped it as deals, and the prose relabelled it as accounts.
 *
 * This tool is the fallback for /accounts and five other routes, and it carried
 * commitments, deals, margin and idle hours: no count of accounts or contacts
 * anywhere. Asked about accounts with no account figure in front of it, the
 * model reached for the nearest countable thing. That is the sibling of the
 * defect `ResultScope` was built for — one substitutes the size of a filter for
 * a total, this one substitutes another noun's total for a total that is simply
 * absent — and the cure for an absent number is not a firmer instruction. It is
 * the number.
 *
 * Counted in SQL rather than by measuring a list, so these figures are exact
 * and cannot truncate; every other count in this file rides on a capped read.
 *
 * Archived accounts are excluded because `/api/accounts` excludes them, and
 * Piggy contradicting the list the user is looking at is the failure that costs
 * the tool its credibility. They are counted rather than silently dropped, so a
 * figure that differs from a raw table count can still be reconciled. Contacts
 * are deliberately NOT filtered the same way: `/api/contacts` applies no archive
 * filter, so every contact row is the denominator that matches the screen.
 */
async function readParties(db: Database): Promise<BookParties> {
  const [sides, archived, contactRows] = await Promise.all([
    db
      .select({ side: accounts.side, value: count() })
      .from(accounts)
      .where(isNull(accounts.archivedAt))
      .groupBy(accounts.side),
    db.select({ value: count() }).from(accounts).where(isNotNull(accounts.archivedAt)),
    db.select({ value: count() }).from(contacts),
  ]);

  // Every side is present at zero rather than absent: a missing key reads as
  // "not known" to a model quoting the payload, and this breakdown is only
  // trustworthy if it visibly adds up.
  const bySide: Record<string, number> = Object.fromEntries(
    ACCOUNT_SIDES.map((side) => [side, 0]),
  );
  for (const row of sides) bySide[row.side] = row.value;
  // Summed from the same grouped read the breakdown is printed from. A total
  // read by a second query can disagree with its own parts under a concurrent
  // write, and a breakdown that does not add up invites the reader to pick.
  const onBook = Object.values(bySide).reduce((sum, value) => sum + value, 0);

  return { onBook, archived: rowCount(archived), bySide, contacts: rowCount(contactRows) };
}

/**
 * The side split as prose, for the headline.
 *
 * `supply`, `demand` and `both` partition the book, so these three figures sum
 * to the total and no account is counted twice. The /accounts side tabs do not
 * partition it — each tab matches `side = X or side = both`, so the two tabs
 * overlap — which is why the note below travels with the numbers rather than
 * being left for the reader to work out from a screen that disagrees.
 */
function sideClause(bySide: Record<string, number>): string {
  return ACCOUNT_SIDES.map((side) => `${bySide[side] ?? 0} ${side}`).join(', ');
}

const BY_SIDE_NOTE =
  'supply, demand and both partition the book: these three figures sum to the total and ' +
  'no account is counted twice. An account whose side is both trades on each side of the ' +
  'market and is counted once, under both. The side tabs on the /accounts page instead show ' +
  'supply plus both, and demand plus both, so those two figures overlap and do not sum.';

// The motion
// ---------------------------------------------------------------------------

export interface MotionLibraryFilter {
  kind?: MotionKind | null;
  stage?: DemandStage | null;
  query?: string | null;
}

/**
 * Shared templates only, unconditionally — not "unless the asker owns it".
 *
 * Motion is the one place in PIG with a row-level access rule: a `private`
 * template is readable by its owner and by a platform admin, and by nobody
 * else. Piggy has no reliable notion of who is asking. The dock publishes a
 * route, the relay checks a capability, and the tool then runs as the process;
 * nothing reaches this query that identifies a person strongly enough to widen
 * it on. So it is not widened. A model that can be argued into reading a
 * colleague's private draft is a leak with the extra step of asking politely,
 * and the argument would arrive as ordinary conversation the guard never sees.
 *
 * This is the only clause here that must not become a parameter. If private
 * drafts ever need an answer, the identity has to arrive with the request and
 * be enforced in `services/motion.ts` where the API already enforces it — not
 * by relaxing this. `motion-tools.test.ts` fails if it is.
 */
export function motionLibraryWhere(filter: MotionLibraryFilter): SQL {
  const conditions: SQL[] = [
    eq(motionTemplates.visibility, 'shared'),
    isNull(motionTemplates.archivedAt),
  ];
  if (filter.kind) conditions.push(eq(motionTemplates.kind, filter.kind));
  if (filter.stage) conditions.push(eq(motionTemplates.stage, filter.stage));
  if (filter.query) {
    const fragment = likeFragment(filter.query);
    conditions.push(
      or(
        ilike(motionTemplates.title, fragment),
        ilike(motionTemplates.summary, fragment),
        ilike(motionTemplates.slug, fragment),
      )!,
    );
  }
  return and(...conditions)!;
}

interface LineageRow {
  slug: string;
  kind: MotionKind;
  stage: DemandStage;
  version: number;
  title: string;
  summary: string;
  usageCount: number;
}

/**
 * One row per lineage, newest version winning.
 *
 * The library counts lineages rather than rows because a template promoted
 * three times is one piece of practice with a history, and counting its
 * versions reports a library four times the size of the one anybody can choose
 * from. The slug is the identity of the lineage — see the schema header.
 */
function newestPerSlug<Row extends { slug: string; version: number }>(rows: readonly Row[]): Row[] {
  const newest = new Map<string, Row>();
  for (const row of rows) {
    const held = newest.get(row.slug);
    if (!held || row.version > held.version) newest.set(row.slug, row);
  }
  return [...newest.values()];
}

function countBy<Row>(rows: readonly Row[], key: (row: Row) => string): Record<string, number> {
  const counts: Record<string, number> = {};
  for (const row of rows) counts[key(row)] = (counts[key(row)] ?? 0) + 1;
  return counts;
}

/**
 * The question the /motion page exists to answer: is the motion repeating?
 *
 * Coverage is reported as the stages the shared library does NOT reach, not
 * only as a count, because "6 of 8 stages covered" is a figure nobody acts on
 * and "nothing covers procurement or deployment" is a piece of work. The two
 * closed stages are excluded throughout — a won deal has left the motion.
 */
async function readMotionSummary(db: Database): Promise<unknown> {
  // One predicate for the exemplars and for the count below them, so the list
  // and the figure cannot come to describe different sets. It is also the
  // predicate the /motion tile counts: `motionLibraryWhere({})` already excludes
  // archived rows, and promotion always writes `visibility: 'shared'`.
  const promoted = and(motionLibraryWhere({}), isNotNull(motionTemplates.originArtifactId))!;
  const [libraryRead, engagementRead, promotions, promotionTotal] = await Promise.all([
    db
      .select({
        slug: motionTemplates.slug,
        kind: motionTemplates.kind,
        stage: motionTemplates.stage,
        version: motionTemplates.version,
        title: motionTemplates.title,
        summary: motionTemplates.summary,
        usageCount: motionTemplates.usageCount,
      })
      .from(motionTemplates)
      .where(motionLibraryWhere({}))
      .limit(SCAN_LIMIT + 1),
    db
      .select({ stage: demandDeals.stage, dealName: demandDeals.name })
      .from(engagements)
      .innerJoin(demandDeals, eq(engagements.demandDealId, demandDeals.id))
      .where(eq(engagements.status, 'open'))
      .limit(SCAN_LIMIT + 1),
    db
      .select({
        title: motionTemplates.title,
        kind: motionTemplates.kind,
        version: motionTemplates.version,
        createdAt: motionTemplates.createdAt,
      })
      .from(motionTemplates)
      .where(promoted)
      .orderBy(desc(motionTemplates.createdAt))
      .limit(EXEMPLARS),
    // Counted in SQL rather than read off the list above it, which is capped at
    // EXEMPLARS. Promotions is the one figure whose job is to show the loop
    // closing, so it is the one figure that must not stop moving: read off the
    // list it would say 8 the moment the loop started working, for ever, beside
    // a tile counting 12 exactly. Versions rather than lineages, like the tile —
    // a second promotion into one lineage is a second time the loop closed.
    db.select({ value: count() }).from(motionTemplates).where(promoted),
  ]);
  const { rows: templateRows, truncated: libraryTruncated } = bounded(libraryRead);
  const { rows: openEngagements, truncated: engagementsTruncated } = bounded(engagementRead);
  const truncated = libraryTruncated || engagementsTruncated;
  const promotionCount = rowCount(promotionTotal);

  const lineages = newestPerSlug(templateRows);
  const engagementsByStage = countBy(openEngagements, (row) => row.stage);
  const uncovered = DEMAND_OPEN_STAGES.filter(
    (stage) => !lineages.some((template) => template.stage === stage),
  );

  return {
    headline:
      `${atLeast(lineages.length, libraryTruncated)} shared template(s) across ` +
      `${Object.keys(countBy(lineages, (row) => row.kind)).length} of ${MOTION_KINDS.length} ` +
      `kind(s), and ${atLeast(openEngagements.length, engagementsTruncated)} open engagement(s). ` +
      (libraryTruncated
        ? 'Which stages the library misses cannot be told from this scan, because it hit its ' +
          'row cap. '
        : uncovered.length === 0
          ? 'Every live demand stage has at least one shared template. '
          : `No shared template covers ${uncovered.join(', ')}. `) +
      `${promotionCount} artifact(s) promoted back into the library in all` +
      (promotions.length ? `, newest ${promotions.length} listed.` : '.') +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    /** Unfiltered within the shared library, so this read IS its own denominator. */
    scope: resultScope({
      covers: 'are shared and not archived',
      matched: lineages.length,
      total: lineages.length,
      totalLabel: MOTION_LIBRARY_LABEL,
      listed: 0,
      truncated: libraryTruncated,
    }),
    truncated,
    sharedTemplates: lineages.length,
    openEngagements: openEngagements.length,
    /**
     * Null rather than a list when the scan was capped. "No shared template
     * covers procurement" is a definite negative, and a definite negative drawn
     * from part of the library is a claim the read cannot support — the stage
     * may well be covered by a lineage beyond the cap. An exact answer under
     * truncation needs a per-slug newest-version aggregate in SQL, which is a
     * larger change than this figure is worth.
     */
    uncoveredStages: libraryTruncated ? null : uncovered,
    stages: DEMAND_OPEN_STAGES.map((stage) => ({
      stage,
      openEngagements: engagementsByStage[stage] ?? 0,
      sharedTemplates: lineages.filter((template) => template.stage === stage).length,
    })),
    libraryByKind: countBy(lineages, (row) => row.kind),
    promotions: promotionCount,
    /**
     * The exact figure sits beside the list on purpose. A model handed eight
     * rows and no total reads the length of the list as the count, which is the
     * mistake `ResultScope` exists for.
     */
    recentPromotionsScope: resultScope({
      covers: 'were promoted back into the library from an engagement artefact',
      matched: promotionCount,
      total: promotionCount,
      totalLabel: 'promoted template version(s) in the motion library',
      listed: promotions.length,
    }),
    // The loop made visible: what the last few engagements gave back.
    recentPromotions: promotions.map((row) => ({
      title: row.title,
      kind: row.kind,
      version: row.version,
      promotedAt: row.createdAt.toISOString(),
    })),
  };
}

async function readMotionLibrary(db: Database, filter: MotionLibraryFilter): Promise<unknown> {
  const [libraryRead, libraryTotal] = await Promise.all([
    db
      .select({
        slug: motionTemplates.slug,
        kind: motionTemplates.kind,
        stage: motionTemplates.stage,
        version: motionTemplates.version,
        title: motionTemplates.title,
        summary: motionTemplates.summary,
        usageCount: motionTemplates.usageCount,
      })
      .from(motionTemplates)
      .where(motionLibraryWhere(filter))
      .orderBy(desc(motionTemplates.updatedAt))
      .limit(SCAN_LIMIT + 1),
    /**
     * The denominator, and both halves of it are decisions.
     *
     * `motionLibraryWhere({})` rather than a bare `count()` over the table: a
     * total that included private rows would publish the existence and the size
     * of colleagues' drafts through the back door, which is the one thing the
     * library rule exists to withhold — a denominator leaks as readily as a
     * list. And `countDistinct(slug)` rather than `count()`, because the slug is
     * the identity of a lineage: counting versions reports a library several
     * times the size of the one anybody can choose from, which is the same
     * mistake `newestPerSlug` exists to avoid on the matched side. Counted in
     * SQL, so the total stays exact when the read beside it hits its cap.
     */
    db
      .select({ value: countDistinct(motionTemplates.slug) })
      .from(motionTemplates)
      .where(motionLibraryWhere({})),
  ]);
  const { rows, truncated } = bounded(libraryRead);

  const lineages = newestPerSlug(rows as LineageRow[]);
  const sharedTemplates = rowCount(libraryTotal);
  const described = [
    filter.kind ? `kind ${filter.kind}` : null,
    filter.stage ? `stage ${filter.stage}` : null,
    filter.query ? `"${filter.query}"` : null,
  ].filter((part): part is string => part !== null);
  const matching = described.length ? ` matching ${described.join(', ')}` : '';

  return {
    headline:
      (lineages.length === 0
        ? `None of the ${sharedTemplates} ${MOTION_LIBRARY_LABEL}${matching}. Private drafts are ` +
          'not searched, so a template may exist and not be visible here.'
        : `${atLeast(lineages.length, truncated)} of ${sharedTemplates} ` +
          `${MOTION_LIBRARY_LABEL}${matching}, newest version of each.`) +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    scope: resultScope({
      covers: described.length ? `match ${described.join(', ')}` : 'are shared and not archived',
      matched: lineages.length,
      total: sharedTemplates,
      totalLabel: MOTION_LIBRARY_LABEL,
      listed: Math.min(lineages.length, EXEMPLARS),
      filters: {
        ...(filter.kind ? { kind: filter.kind } : {}),
        ...(filter.stage ? { stage: filter.stage } : {}),
        ...(filter.query ? { query: filter.query } : {}),
      },
      truncated,
    }),
    truncated,
    count: lineages.length,
    /** The denominator as a bare field: this is how big the shared library is. */
    sharedTemplates,
    byKind: countBy(lineages, (row) => row.kind),
    // Most recently updated first: the practice people are actually amending.
    templates: lineages.slice(0, EXEMPLARS).map((template) => ({
      slug: template.slug,
      kind: template.kind,
      stage: template.stage,
      version: template.version,
      title: template.title,
      summary: template.summary,
      // How many engagements have instantiated it — the only evidence here of
      // whether a template is practice or an unread document.
      usageCount: template.usageCount,
    })),
  };
}

/**
 * Engagements, with the two figures anyone asks for: how much has been produced
 * and where qualification landed.
 *
 * Artifacts and scores are read only for the exemplars, so the second and third
 * queries stay small however wide the book is. The engagement's playbook
 * template is deliberately not joined: it may be a private draft, and naming it
 * would walk round the library rule by another door.
 */
async function readEngagements(db: Database, query: string | null): Promise<unknown> {
  const fragment = query ? likeFragment(query) : null;
  const [engagementRead, engagementTotal] = await Promise.all([
    db
      .select({
        id: engagements.id,
        status: engagements.status,
        summary: engagements.summary,
        openedAt: engagements.openedAt,
        stage: demandDeals.stage,
        dealName: demandDeals.name,
        accountName: accounts.name,
      })
      .from(engagements)
      .innerJoin(demandDeals, eq(engagements.demandDealId, demandDeals.id))
      .leftJoin(accounts, eq(demandDeals.accountId, accounts.id))
      .where(
        fragment
          ? or(ilike(demandDeals.name, fragment), ilike(engagements.summary, fragment))
          : undefined,
      )
      .orderBy(desc(engagements.openedAt))
      .limit(SCAN_LIMIT + 1),
    // Every engagement, open or closed and whatever the query. Without it a
    // search that matches two is the only figure in the payload, and "we have
    // two engagements" is the answer that comes back.
    db.select({ value: count() }).from(engagements),
  ]);
  const { rows, truncated } = bounded(engagementRead);
  const totalEngagements = rowCount(engagementTotal);

  const exemplars = rows.slice(0, EXEMPLARS);
  const ids = exemplars.map((row) => row.id);
  const [artifacts, scores] = ids.length
    ? await Promise.all([
        db
          .select({ engagementId: engagementArtifacts.engagementId, status: engagementArtifacts.status })
          .from(engagementArtifacts)
          .where(
            and(
              inArray(engagementArtifacts.engagementId, ids),
              isNull(engagementArtifacts.archivedAt),
            ),
          )
          .limit(SCAN_LIMIT),
        db
          .select({
            engagementId: qualificationScores.engagementId,
            basisPoints: qualificationScores.basisPoints,
            band: qualificationScores.band,
            scoredAt: qualificationScores.scoredAt,
          })
          .from(qualificationScores)
          .where(inArray(qualificationScores.engagementId, ids))
          .orderBy(desc(qualificationScores.scoredAt))
          .limit(SCAN_LIMIT),
      ])
    : [[], []];

  // Newest first out of the query, so the first score seen for an engagement is
  // its latest; a later one must not overwrite it.
  const latestScore = new Map<string, (typeof scores)[number]>();
  for (const score of scores) if (!latestScore.has(score.engagementId)) latestScore.set(score.engagementId, score);

  return {
    headline:
      (rows.length === 0
        ? query
          ? `None of the ${totalEngagements} ${ENGAGEMENTS_LABEL} match "${query}".`
          : 'No demand deal has an engagement running against it yet.'
        : `${atLeast(rows.length, truncated)} of ${totalEngagements} ${ENGAGEMENTS_LABEL}` +
          `${query ? ` match "${query}"` : ''}, of which ` +
          `${rows.filter((row) => row.status === 'open').length} open.`) +
      (truncated ? ` ${TRUNCATION_NOTE}` : ''),
    scope: resultScope({
      covers: query ? `match "${query}"` : 'are on the book',
      matched: rows.length,
      total: totalEngagements,
      totalLabel: ENGAGEMENTS_LABEL,
      listed: exemplars.length,
      filters: query ? { query } : {},
      truncated,
    }),
    truncated,
    count: rows.length,
    /** The denominator as a bare field: engagements exist that this did not match. */
    totalEngagements,
    byStatus: countBy(rows, (row) => row.status),
    byStage: countBy(rows, (row) => row.stage),
    engagements: exemplars.map((row) => {
      const mine = artifacts.filter((artifact) => artifact.engagementId === row.id);
      const score = latestScore.get(row.id);
      return {
        dealName: row.dealName,
        accountName: row.accountName,
        stage: row.stage,
        status: row.status,
        summary: row.summary,
        openedAt: row.openedAt.toISOString(),
        artifacts: mine.length,
        finalArtifacts: mine.filter((artifact) => artifact.status === 'final').length,
        latestScore: score
          ? {
              // Basis points of the maximum, so a tenth of a per cent — the
              // band is the part a seller acts on.
              basisPoints: score.basisPoints,
              percent: `${(score.basisPoints / 100).toFixed(1)}%`,
              band: score.band,
              scoredAt: score.scoredAt.toISOString(),
            }
          : null,
      };
    }),
  };
}

// ---------------------------------------------------------------------------
// The fallback
// ---------------------------------------------------------------------------

/**
 * The default when no page names a better tool.
 *
 * This replaced a dump of up to 600 rows. That version could not survive one
 * turn of a 1024-token budget, so the model saw a truncated ledger and
 * answered from the fragment it happened to receive.
 */
async function readWorkspaceSummary(db: Database): Promise<unknown> {
  const [book, parties, demandRead, supplyRead, demandAll, supplyAll] = await Promise.all([
    readLiveBlocks(db),
    readParties(db),
    db
      .select({ id: demandDeals.id })
      .from(demandDeals)
      .where(inArray(demandDeals.stage, [...DEMAND_OPEN_STAGES]))
      .limit(SCAN_LIMIT + 1),
    db
      .select({ id: supplyDeals.id })
      .from(supplyDeals)
      .where(inArray(supplyDeals.stage, [...SUPPLY_OPEN_STAGES]))
      .limit(SCAN_LIMIT + 1),
    db.select({ value: count() }).from(demandDeals),
    db.select({ value: count() }).from(supplyDeals),
  ]);
  const { blocks } = book;
  const demandTotal = rowCount(demandAll);
  const supplyTotal = rowCount(supplyAll);
  const { rows: demand, truncated: demandTruncated } = bounded(demandRead);
  const { rows: supply, truncated: supplyTruncated } = bounded(supplyRead);
  const truncated = {
    commitments: book.truncated,
    demandDeals: demandTruncated,
    supplyDeals: supplyTruncated,
  };
  const anyTruncated = Object.values(truncated).some(Boolean);
  const totals = bookTotals(blocks);
  /**
   * Any idle at all, which is a THIRD threshold — `pig_get_idle_capacity` uses
   * 25% and the web uses its own. Three surfaces have quoted three different
   * idle counts for one book because of exactly this, so the scope below names
   * the threshold rather than leaving the reader to guess which one produced
   * the number in front of them.
   */
  const withIdle = [...blocks]
    .filter((block) => block.margin.idleGpuHours > 0)
    .sort(
      (a, b) =>
        b.margin.idleGpuHours * b.costPerGpuHourCents -
        a.margin.idleGpuHours * a.costPerGpuHourCents,
    );
  const worstIdle = withIdle.slice(0, 3);

  return {
    /**
     * The parties lead the headline, and that ordering is the fix.
     *
     * This sentence is what a small model quotes, and the production answer was
     * assembled by taking the first countable thing in it. Every count in it now
     * states the noun it counts immediately beside the figure, and the noun the
     * six fallback routes are most often asked about — accounts — is no longer
     * missing from it.
     */
    headline:
      `${parties.onBook} ${ACCOUNTS_LABEL} (${sideClause(parties.bySide)}) and ` +
      `${parties.contacts} ${CONTACTS_LABEL}` +
      (parties.archived > 0
        ? `, with a further ${parties.archived} account(s) archived and off the book`
        : '') +
      `. All ${atLeast(blocks.length, truncated.commitments)} ${COMMITMENTS_LABEL} at ` +
      `${percent(totals.utilisation)} utilisation; ` +
      `gross margin ${formatCents(totals.grossMarginCents)}; ` +
      `${demand.length} of ${atLeast(demandTotal, demandTruncated)} ${DEMAND_DEALS_LABEL} ` +
      `and ${supply.length} of ${atLeast(supplyTotal, supplyTruncated)} ${SUPPLY_DEALS_LABEL} ` +
      `are open. The ${worstIdle.length} block(s) listed below are the worst idle of ` +
      `${withIdle.length} with any idle hours, not the whole book.` +
      (anyTruncated ? ` ${TRUNCATION_NOTE}` : ''),
    // The book, unfiltered: `matched` equals `total`, so this is the figure to
    // quote when someone asks how large the book is.
    scope: resultScope({
      covers: 'are live',
      matched: blocks.length,
      total: blocks.length,
      totalLabel: COMMITMENTS_LABEL,
      listed: 0,
      truncated: truncated.commitments,
    }),
    truncated,
    /**
     * The two figures whose absence produced the /accounts defect, first in the
     * payload as well as first in the headline, each with its own scope. Both
     * are exact: they are SQL counts, so neither can be a lower bound the way
     * the capped reads below can.
     */
    accounts: {
      scope: resultScope({
        covers: 'are on the book and not archived',
        matched: parties.onBook,
        total: parties.onBook,
        totalLabel: ACCOUNTS_LABEL,
        listed: 0,
      }),
      onBook: parties.onBook,
      /** Excluded from `onBook`, and from the /accounts list, but not hidden. */
      archived: parties.archived,
      bySide: parties.bySide,
      bySideNote: BY_SIDE_NOTE,
      /**
       * Said in the payload because the route guide cannot say it often enough:
       * this tool counts accounts, it does not read them. A question about a
       * named account is a `pig_search_records` question.
       */
      rows: 'not available from this tool — counts only, no account rows',
    },
    contacts: {
      scope: resultScope({
        covers: 'are in the CRM',
        matched: parties.contacts,
        total: parties.contacts,
        totalLabel: CONTACTS_LABEL,
        listed: 0,
      }),
      total: parties.contacts,
      rows: 'not available from this tool — counts only, no contact rows',
    },
    book: {
      liveCommitments: blocks.length,
      revenueCents: totals.revenueCents,
      costCents: totals.costCents,
      grossMarginCents: totals.grossMarginCents,
      utilisation: totals.utilisation,
      idleGpuHours: Math.round(totals.idleGpuHours),
    },
    // Two filtered counts, each next to the denominator it came from. Without
    // `totalDemandDeals` beside it, `openDemandDeals` is the only deal figure
    // in the payload and becomes the answer to "how many deals do we have".
    openDemandDeals: demand.length,
    totalDemandDeals: demandTotal,
    openDemandDealsScope: resultScope({
      covers: 'are at an open stage (not closed, not lost)',
      matched: demand.length,
      total: demandTotal,
      totalLabel: DEMAND_DEALS_LABEL,
      listed: 0,
      filters: { stages: [...DEMAND_OPEN_STAGES].join(', ') },
      truncated: demandTruncated,
    }),
    openSupplyDeals: supply.length,
    totalSupplyDeals: supplyTotal,
    openSupplyDealsScope: resultScope({
      covers: 'are at an open stage (not closed, not lost)',
      matched: supply.length,
      total: supplyTotal,
      totalLabel: SUPPLY_DEALS_LABEL,
      listed: 0,
      filters: { stages: [...SUPPLY_OPEN_STAGES].join(', ') },
      truncated: supplyTruncated,
    }),
    worstIdle: {
      scope: resultScope({
        covers: 'have any unsold hours at all',
        matched: withIdle.length,
        total: blocks.length,
        totalLabel: COMMITMENTS_LABEL,
        listed: worstIdle.length,
        // Not 0.25. This list and pig_get_idle_capacity answer different
        // questions and will disagree; the thresholds say which is which.
        filters: { idleThresholdPct: 0 },
        truncated: truncated.commitments,
      }),
      blocks: worstIdle.map((block) => ({
        name: block.name,
        gpuType: block.gpuType,
        idleGpuHours: Math.round(block.margin.idleGpuHours),
        idleCostCents: Math.round(block.margin.idleGpuHours * block.costPerGpuHourCents),
      })),
    },
  };
}

/**
 * One decimal, matching the web's own `percent` for these two quantities.
 *
 * Both call sites report a blended figure the reader has on screen beside
 * them — Overview and Margin render utilisation and gross margin to a tenth —
 * and rounding to a whole number here had Piggy answer "5% margin at 87%
 * utilisation" about a book the page was calling 5.3% and 87.3%. On a book
 * clearing five per cent, a tenth is a twentieth of the whole margin, so this
 * is a different number rather than a shorter one.
 */
function percent(value: number | null): string {
  return value == null ? 'n/a' : `${(value * 100).toFixed(1)}%`;
}
