/**
 * What an activity looks like on the wire, and who is recorded as having done it.
 *
 * Two endpoints render a timeline — the account record and the dashboard feed —
 * and both selected the whole row, so the payload was whatever the table
 * happened to hold that week. That is how `meta` came to ship every stage
 * transition blob and Slack permalink to every browser, and it is why nothing
 * on the client could safely depend on `external_id` being there at all: it
 * arrived by accident, and the next hand-written column list would have removed
 * it without anyone noticing. The projection is declared here instead, once,
 * and shaped around the two questions a reader of a timeline actually asks:
 * what happened, and who did it.
 *
 * **Who did it is the part that was broken.** Piggy signs a row in one of two
 * places, because it makes two shapes of write:
 *
 *   - `pig_log_activity`'s row IS its own audit event, so there is no separate
 *     audit row to stamp. It carries an `external_id` of `piggy:<uuid>`, which
 *     doubles as the idempotency key that stops a retried tool call logging the
 *     same conversation twice (`write-tools.ts`).
 *   - Every other write tool leaves `meta.actorAgent = 'piggy'` on the audit row
 *     the mutation convention inserts beside the change (`attributedToPiggy`).
 *
 * `activities.actor_agent` itself stays exactly what it has always been — set
 * only when the request authenticated as an agent — because a browser session
 * authorised by a person really was authorised by that person, and stamping it
 * otherwise would be a lie told inside the audit trail. So the derivation lives
 * on the read, not on the row: the wire says "an agent produced this entry, and
 * it was Piggy", which is precisely what the column means, while the stored row
 * goes on recording how the request was authorised. Nothing here writes.
 */
import type { ActivityType } from '@pig/core';
import { activities } from '@pig/db';

/**
 * The mark on an `external_id` that Piggy logged the row.
 *
 * Exported because the client draws the distinction too: a row Piggy logged
 * gets attribution in the timeline, and a row synced from Slack or Buzz — which
 * also carries an external id — does not.
 */
export const PIGGY_EXTERNAL_ID_PREFIX = 'piggy:';

/** What `actorAgent` reads when the row is Piggy's. One spelling, one source. */
export const PIGGY_AGENT_NAME = 'piggy';

/**
 * The columns a timeline needs.
 *
 * `meta`, `source`, `created_at` and `actor_user_id` are deliberately absent.
 * `meta` is an internal payload with no reader in the browser, and its one
 * client-relevant fact is folded into `actorAgent` below; the other three say
 * nothing a timeline shows. Ship them and they become a contract by default.
 */
export const activityPayloadColumns = {
  id: activities.id,
  type: activities.type,
  subject: activities.subject,
  body: activities.body,
  accountId: activities.accountId,
  contactId: activities.contactId,
  demandDealId: activities.demandDealId,
  supplyDealId: activities.supplyDealId,
  actorAgent: activities.actorAgent,
  externalId: activities.externalId,
  meta: activities.meta,
  occurredAt: activities.occurredAt,
};

/** The shape `activityPayloadColumns` selects, before serialisation. */
export interface ActivityRow {
  id: string;
  type: ActivityType;
  subject: string | null;
  body: string | null;
  accountId: string | null;
  contactId: string | null;
  demandDealId: string | null;
  supplyDealId: string | null;
  actorAgent: string | null;
  externalId: string | null;
  meta: Record<string, unknown> | null;
  occurredAt: Date;
}

export interface ActivityPayload {
  id: string;
  type: ActivityType;
  subject: string | null;
  body: string | null;
  accountId: string | null;
  contactId: string | null;
  demandDealId: string | null;
  supplyDealId: string | null;
  /**
   * Which agent produced the entry, or null when a person typed it. `'piggy'`
   * covers both of the ways Piggy signs a write; see the note at the top.
   */
  actorAgent: string | null;
  /**
   * External identity, for idempotent sync. A `piggy:` prefix — see
   * `PIGGY_EXTERNAL_ID_PREFIX` — means Piggy logged this activity itself, as
   * opposed to Piggy having changed a record and this being the audit of it.
   */
  externalId: string | null;
  occurredAt: string;
}

/**
 * Whether Piggy produced this entry, asked of both places it can have said so.
 *
 * The `meta` arm is read defensively rather than cast: `meta` is free-form JSON
 * written by every mutation in the product, and a row where it holds a string
 * or an array must answer "no", not throw on the account page.
 */
function producedByPiggy(row: Pick<ActivityRow, 'externalId' | 'meta'>): boolean {
  if (row.externalId?.startsWith(PIGGY_EXTERNAL_ID_PREFIX)) return true;
  const stamped = row.meta && typeof row.meta === 'object' ? row.meta.actorAgent : null;
  return stamped === PIGGY_AGENT_NAME;
}

export function toActivityPayload(row: ActivityRow): ActivityPayload {
  return {
    id: row.id,
    type: row.type,
    subject: row.subject,
    body: row.body,
    accountId: row.accountId,
    contactId: row.contactId,
    demandDealId: row.demandDealId,
    supplyDealId: row.supplyDealId,
    actorAgent: row.actorAgent ?? (producedByPiggy(row) ? PIGGY_AGENT_NAME : null),
    externalId: row.externalId,
    occurredAt: row.occurredAt.toISOString(),
  };
}
