import { getUserById, markAgentPanelIntroduced, type AgentPanel } from 'officerdb';
import * as sidecar from '@@/sidecar-registry';
import { resolveBaseCwd } from '../chat/websocket';
import { logger } from '../chat/logger';
import { getOwnerHomeDir } from '@@/data-path';

/**
 * Push a turn into an agent panel's session — with or without a browser attached.
 *
 * This is the whole delivery mechanism, and it is four lines because the hard parts already existed:
 *
 *   - `spawnClaudeStreaming` on an EXISTING sessionKey adopts the live session and queues the prompt
 *     (`claude/claude-manager.ts:379-391`). On a reaped one it re-creates the session and resumes the
 *     same Claude transcript from the sidecar's write-through `sessionKey → claudeSessionId` map
 *     (`claude-manager.ts:258`). So we never have to know, or care, whether the target is warm.
 *   - the sidecar commits the resulting output to `chat_session_events` itself, keyed by sessionKey
 *     (`claude/session-log.ts`, `durable` defaults to true). A browser that opens the dashboard later
 *     replays it from its cursor via `resume-cursor`.
 *
 * Which is why no browser needs to be open, and why nothing here subscribes to the output: the point
 * of the durable log is that officer is not on the delivery path.
 *
 * NOT awaited to completion — `spawnClaudeStreaming` resolves when the turn has been *accepted*, not
 * when it finishes (`sidecar/user-instance.ts:169` acks before doing the work). A handoff is fire-and-
 * observe: the sender learns nothing about the outcome except by being told in turn.
 */
export async function deliverToAgentPanel(target: AgentPanel, prompt: string): Promise<void> {
  const user = await getUserById(target.userId);
  if (!user) throw new Error(`agent panel ${target.id} belongs to a user that no longer exists`);

  await sidecar.spawnClaudeStreaming({
    userId: user.id,
    email: user.email,
    username: user.name ?? user.email,
    prompt,
    sessionKey: target.sessionKey,
    cwd: resolveBaseCwd(getOwnerHomeDir(user.email), target.cwd ?? undefined),
    // Deliberately no `model`: a live session ignores it anyway, and a resumed one keeps whatever the
    // panel started with. Passing one here would only look like it worked.
    durable: true,
  });

  logger.info('Delivered a turn to an agent panel', {
    dashboardId: target.dashboardId,
    to: target.name,
    sessionKey: target.sessionKey,
  });
}

/**
 * The envelope. The receiving agent is a language model reading its own conversation, so the sender
 * has to be *in the text* — there is no out-of-band "from" header it could see. Fenced so a message
 * containing its own markdown cannot be mistaken for the frame around it.
 */
export function composeHandoff(fromName: string, message: string): string {
  return [
    `[handoff from \`${fromName}\`]`,
    '',
    message.trim(),
    '',
    `---`,
    `That message came from another agent on this dashboard, not from the human. Reply to it by doing`,
    `the work, and hand back when you are done.`,
  ].join('\n');
}

/** Send the role prompt as the session's very first turn, once. Roles are prompts, not features. */
export async function introduceAgentPanel(panel: AgentPanel, peers: AgentPanel[], apiOrigin: string): Promise<void> {
  if (panel.introducedAt) return;
  const prompt = composeIntroduction(panel, peers, apiOrigin);
  await deliverToAgentPanel(panel, prompt);
  await markAgentPanelIntroduced(panel.id);
}

/**
 * What an agent is told about the world it lives in, verbatim, at creation.
 *
 * The handoff token is inlined into a runnable curl. That is a bearer credential sitting in a
 * transcript, and it is a deliberate trade for this MVP on a single-owner machine: its authority is
 * "deliver a prompt to a named peer on this one dashboard" and nothing else. Revisit before this
 * platform has a second human on it — see docs/agent-coordination.md.
 */
export function composeIntroduction(panel: AgentPanel, peers: AgentPanel[], apiOrigin: string): string {
  const others = peers.filter((p) => p.id !== panel.id);
  const peerList = others.length
    ? others.map((p) => `  - \`${p.name}\`${p.cwd ? ` (working in ${p.cwd})` : ''}`).join('\n')
    : '  (none yet — the human may add more panels to this dashboard later)';

  return [
    `You are \`${panel.name}\`, one of several agents working together on the dashboard`,
    `\`${panel.dashboardId}\`. Each of us is a separate Claude session in its own panel, on the same`,
    `page, with our own working directory and our own transcript.`,
    '',
    ...(panel.rolePrompt?.trim() ? [`Your role, as the human described it:`, '', panel.rolePrompt.trim(), ''] : []),
    `The other agents on this dashboard:`,
    peerList,
    '',
    `## Handing work to another agent`,
    '',
    `When you finish something a peer needs to act on, tell them. Run:`,
    '',
    '```bash',
    `curl -sS -X POST ${apiOrigin}/api/agent-handoff \\`,
    `  -H 'content-type: application/json' \\`,
    `  -H 'x-officer-agent-token: ${panel.handoffToken}' \\`,
    `  -d "$(jq -n --arg to 'PEER_NAME' --arg message "$(cat HANDOFF.md)" '{to:$to, message:$message}')"`,
    '```',
    '',
    `Replace \`PEER_NAME\` with a name from the list above. The message is delivered to that agent as a`,
    `turn in their own session, labelled as coming from you. For anything longer than a sentence, write`,
    `it to a file first and pass the file as above — that is what the human expects to be able to read`,
    `afterwards.`,
    '',
    `To see who is currently on this dashboard:`,
    '',
    '```bash',
    `curl -sS ${apiOrigin}/api/agent-handoff/peers -H 'x-officer-agent-token: ${panel.handoffToken}'`,
    '```',
    '',
    `An unknown name is an error, not a silent no-op: the response tells you which names exist.`,
    '',
    `## What to expect back`,
    '',
    `A message from a peer arrives as a new turn in this conversation, prefixed \`[handoff from ...]\`.`,
    `It may arrive minutes or hours later, and the human may not be watching when it does. Treat it as`,
    `a real instruction and continue working; do not wait for the human to confirm it.`,
    '',
    `Acknowledge this message briefly and then wait.`,
  ].join('\n');
}
