import type { Sql } from '../../db.js';
import type { ToolCall, ToolResult } from '../../types/api.js';

// v1.13.0: message_parts write helpers. v1.13.20: legacy tool_calls/
// tool_results JSON columns dropped; message_parts is the sole source of
// truth. All writes go through insertParts / partsFromAssistantMessage /
// partsFromToolMessage. Reads use the messages_with_parts view.

// v1.13.13: 'synthesis' added. Schema CHECK constraint is updated in lockstep
// (schema.sql adds 'synthesis' to message_parts_kind_chk on startup). The
// dispatch's claim that no schema migration was needed assumed kind was a
// bare text column — it isn't; the constraint enumerates allowed values.
// v1.14.x-html-artifact-panes: 'html_artifact' added. Schema CHECK constraint
// in schema.sql updated in lockstep.
export type PartKind =
  | 'text'
  | 'tool_call'
  | 'tool_result'
  | 'reasoning'
  | 'step_start'
  | 'synthesis'
  | 'html_artifact';

export interface PartInsert {
  message_id: string;
  sequence: number;
  kind: PartKind;
  payload: unknown;
}

export async function insertParts(sql: Sql, parts: PartInsert[]): Promise<void> {
  if (parts.length === 0) return;
  // postgres-js fans out an array of objects to a multi-row INSERT. Each
  // payload field needs sql.json() so jsonb storage receives a JSON value
  // rather than a quoted string.
  await sql`
    INSERT INTO message_parts ${sql(
      parts.map((p) => ({
        message_id: p.message_id,
        sequence: p.sequence,
        kind: p.kind,
        payload: sql.json(p.payload as never),
        retry_count: 0,
      })),
      'message_id',
      'sequence',
      'kind',
      'payload',
      'retry_count',
    )}
  `;
}

// Derive parts from the canonical messages row for an assistant message.
// reasoning (when non-empty) becomes a 'reasoning' part at sequence 0 —
// it precedes user-visible content logically. content (when non-empty)
// becomes a 'text' part next; each tool_call becomes a 'tool_call' part
// with payload { id, name, args } where args is the parsed object (we
// use the in-memory ToolCall shape, not the OpenAI stringified one).
export function partsFromAssistantMessage(args: {
  content: string;
  tool_calls: ToolCall[] | null;
  // v1.13.1-C: optional reasoning text streamed alongside the answer.
  // Most rows have none — only models with separate reasoning channels
  // (qwen3.6 etc.) populate this.
  reasoning?: string;
  // Phase 2 (anthropic): per-thinking-block reasoning with signatures. When
  // present (and non-empty) this supersedes `reasoning` — one reasoning part
  // per block, each carrying its signature for verbatim replay.
  reasoningBlocks?: Array<{ text: string; signature?: string }>;
}): Omit<PartInsert, 'message_id'>[] {
  const out: Omit<PartInsert, 'message_id'>[] = [];
  let seq = 0;
  const blocks = args.reasoningBlocks?.filter((b) => b.text.length > 0 || b.signature);
  if (blocks && blocks.length > 0) {
    for (const b of blocks) {
      out.push({
        sequence: seq,
        kind: 'reasoning',
        payload: { text: b.text, ...(b.signature ? { signature: b.signature } : {}) },
      });
      seq += 1;
    }
  } else if (args.reasoning && args.reasoning.length > 0) {
    out.push({ sequence: seq, kind: 'reasoning', payload: { text: args.reasoning } });
    seq += 1;
  }
  if (args.content && args.content.length > 0) {
    out.push({ sequence: seq, kind: 'text', payload: { text: args.content } });
    seq += 1;
  }
  for (const tc of args.tool_calls ?? []) {
    out.push({
      sequence: seq,
      kind: 'tool_call',
      payload: { id: tc.id, name: tc.name, args: tc.args },
    });
    seq += 1;
  }
  return out;
}

// Derive a single tool_result part from a tool message's tool_results JSON.
// The payload includes the same shape that buildMessagesPayload reads from
// later: tool_call_id, output, optional error/truncated metadata.
export function partsFromToolMessage(args: {
  tool_results: ToolResult | null;
}): Omit<PartInsert, 'message_id'>[] {
  if (!args.tool_results) return [];
  const tr = args.tool_results;
  return [
    {
      sequence: 0,
      kind: 'tool_result',
      payload: {
        tool_call_id: tr.tool_call_id,
        output: tr.output,
        truncated: tr.truncated,
        ...(tr.error ? { error: tr.error } : {}),
      },
    },
  ];
}
