import type { ChatMessage, CondensePolicy } from '@genesislcap/foundation-ai';
import { assert, createLogicSuite } from '@genesislcap/foundation-testing';
import {
  applyCondensation,
  CONDENSE_MIN_CHARS,
  type CondenseContext,
  type CondensedEventDetail,
  type RegisteredCondensePolicy,
} from './condense-history';

const suite = createLogicSuite('applyCondensation');

// Payloads must clear the driver-level floor to be condensed.
const big = (n: number = CONDENSE_MIN_CHARS + 500): string => 'x'.repeat(n);

const asstCall = (
  id: string,
  name: string,
  args: Record<string, unknown> = {},
  extra: Record<string, unknown> = {},
): ChatMessage => ({ role: 'assistant', content: '', toolCalls: [{ id, name, args, ...extra }] });

const toolMsg = (toolCallId: string, content: string): ChatMessage => ({
  role: 'tool',
  content: '',
  toolResult: { toolCallId, content },
});

const reg = (
  policy: CondensePolicy,
  clocks: { iteration?: number; turn?: number; activation?: number; phaseEpoch?: number } = {},
): RegisteredCondensePolicy => ({
  policy,
  iteration: clocks.iteration ?? 1,
  turn: clocks.turn ?? 1,
  activation: clocks.activation ?? 1,
  phaseEpoch: clocks.phaseEpoch ?? 1,
});

/** A CondenseContext for the pure transform. Defaults fire nothing; override per test. */
const ctx = (
  o: {
    modelCall?: number;
    turn?: number;
    activationEnded?: (a: number) => boolean;
    phaseEnded?: (e: number) => boolean;
  } = {},
): CondenseContext => ({
  modelCall: o.modelCall ?? 0,
  turn: o.turn ?? 0,
  activationEnded: o.activationEnded ?? (() => false),
  phaseEnded: o.phaseEnded ?? (() => false),
});

const sink = () => {
  const events: CondensedEventDetail[] = [];
  return { events, on: (d: CondensedEventDetail) => events.push(d) };
};

// ---------------------------------------------------------------------------
// supersession
// ---------------------------------------------------------------------------

suite('superseded response: collapses the earlier call, keeps the latest full', () => {
  const history: ChatMessage[] = [
    asstCall('r1', 'vfs_read', { path: 'A' }),
    toolMsg('r1', big()),
    asstCall('r2', 'vfs_read', { path: 'A' }),
    toolMsg('r2', big()),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
    ['r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
  ]);
  const { events, on } = sink();
  const out = applyCondensation(history, policies, ctx(), on);

  assert.match(out[1].toolResult!.content, /re-call to restore/);
  assert.is(out[3].toolResult!.content, big(), 'latest call for the key stays full');
  // Exactly one transition reported, for the earlier call's response.
  assert.is(events.length, 1);
  assert.is(events[0].toolCallId, 'r1');
  assert.is(events[0].target, 'response');
  assert.is(events[0].trigger, 'superseded:A');
  // Input is never mutated; unchanged messages keep their identity.
  assert.is(history[1].toolResult!.content, big(), 'stored history untouched');
  assert.is(out[3], history[3], 'unchanged message reuses the same object');
  assert.ok(out[1] !== history[1], 'collapsed message is a new object');
});

suite('superseded crosses tools: a read collapses the earlier write args of the same key', () => {
  const history: ChatMessage[] = [
    asstCall('w1', 'vfs_write', { path: 'A', content: big() }),
    toolMsg('w1', 'written'),
    asstCall('r1', 'vfs_read', { path: 'A' }),
    toolMsg('r1', big()),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['w1', reg({ on: { kind: 'superseded', by: 'A' }, args: 'pointer' })],
    ['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
  ]);
  const { events, on } = sink();
  const out = applyCondensation(history, policies, ctx(), on);

  // Earlier write is superseded by the later read of the same key → its args collapse.
  assert.match(String(out[0].toolCalls![0].args.condensed), /re-call to restore/);
  // The read is the latest for key A → its response stays full.
  assert.is(out[3].toolResult!.content, big());
  assert.is(events.length, 1);
  assert.is(events[0].toolCallId, 'w1');
  assert.is(events[0].target, 'args');
});

// ---------------------------------------------------------------------------
// age
// ---------------------------------------------------------------------------

suite('age: the model sees the result on `turns` turns, then it collapses', () => {
  // Call made on iteration 1 → the result is first visible to the model on
  // iteration 2. With turns:1 it may be read on that one turn, then collapses.
  const history: ChatMessage[] = [asstCall('g1', 'grep_source', {}), toolMsg('g1', big())];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['g1', reg({ on: { kind: 'age', turns: 1 }, response: 'pointer' }, { iteration: 1 })],
  ]);
  const { events, on } = sink();

  // Iteration 2 — its one allowed look: still full, nothing reported.
  const seen = applyCondensation(history, policies, ctx({ modelCall: 2 }), on);
  assert.is(seen[1].toolResult!.content, big(), 'full on the one turn it may be seen');
  assert.is(events.length, 0, 'not collapsed while the model may still see it');

  // Iteration 3 — one turn past the allowed view: collapses.
  const collapsed = applyCondensation(history, policies, ctx({ modelCall: 3 }), on);
  assert.match(collapsed[1].toolResult!.content, /aged out/);
  assert.match(collapsed[1].toolResult!.content, /re-call to restore/);
  assert.is(events.length, 1);
  assert.is(events[0].trigger, 'age:1');
});

// ---------------------------------------------------------------------------
// min-size floor
// ---------------------------------------------------------------------------

suite('payloads below the size floor are never condensed', () => {
  const history: ChatMessage[] = [
    asstCall('r1', 'vfs_read', { path: 'A' }),
    toolMsg('r1', 'tiny'),
    asstCall('r2', 'vfs_read', { path: 'A' }),
    toolMsg('r2', big()),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
    ['r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
  ]);
  const { events, on } = sink();
  const out = applyCondensation(history, policies, ctx(), on);

  assert.is(out[1].toolResult!.content, 'tiny', 'superseded but too small to bother');
  assert.is(events.length, 0);
});

// ---------------------------------------------------------------------------
// result modes
// ---------------------------------------------------------------------------

suite('drop / pointer / replaceWith produce distinct collapsed forms', () => {
  const mk = (id: string, result: CondensePolicy['response']): [ChatMessage, ChatMessage] => [
    asstCall(id, 'vfs_read', { path: id }),
    toolMsg(id, big()),
  ];
  // Each call is superseded by a later same-key call so it actually fires.
  const history: ChatMessage[] = [
    ...mk('drop', 'drop'),
    asstCall('drop2', 'vfs_read', { path: 'drop' }),
    toolMsg('drop2', big()),
    ...mk('ptr', 'pointer'),
    asstCall('ptr2', 'vfs_read', { path: 'ptr' }),
    toolMsg('ptr2', big()),
    ...mk('rep', { replaceWith: 'SUMMARY' }),
    asstCall('rep2', 'vfs_read', { path: 'rep' }),
    toolMsg('rep2', big()),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['drop', reg({ on: { kind: 'superseded', by: 'drop' }, response: 'drop' })],
    ['drop2', reg({ on: { kind: 'superseded', by: 'drop' }, response: 'drop' })],
    ['ptr', reg({ on: { kind: 'superseded', by: 'ptr' }, response: 'pointer' })],
    ['ptr2', reg({ on: { kind: 'superseded', by: 'ptr' }, response: 'pointer' })],
    ['rep', reg({ on: { kind: 'superseded', by: 'rep' }, response: { replaceWith: 'SUMMARY' } })],
    ['rep2', reg({ on: { kind: 'superseded', by: 'rep' }, response: { replaceWith: 'SUMMARY' } })],
  ]);
  const { on } = sink();
  const out = applyCondensation(history, policies, ctx(), on);

  assert.is(out[1].toolResult!.content, '[elided]', 'drop is a minimal non-empty placeholder');
  assert.match(
    out[5].toolResult!.content,
    /re-call to restore/,
    'pointer carries the restore hint',
  );
  assert.is(out[9].toolResult!.content, 'SUMMARY', 'replaceWith is verbatim — no hint appended');
});

suite('args drop yields an empty object; pointer stashes a stub under one key', () => {
  const history: ChatMessage[] = [
    asstCall('w1', 'vfs_write', { path: 'A', content: big() }),
    toolMsg('w1', 'ok'),
    asstCall('w2', 'vfs_write', { path: 'A', content: big() }),
    toolMsg('w2', 'ok'),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['w1', reg({ on: { kind: 'superseded', by: 'A' }, args: 'drop' })],
    ['w2', reg({ on: { kind: 'superseded', by: 'A' }, args: 'drop' })],
  ]);
  const { on } = sink();
  const out = applyCondensation(history, policies, ctx(), on);
  assert.equal(out[0].toolCalls![0].args, {}, 'dropped args is an empty object');
  assert.equal(out[2].toolCalls![0].args, { path: 'A', content: big() }, 'latest write kept');
});

// ---------------------------------------------------------------------------
// envelope + metadata invariants
// ---------------------------------------------------------------------------

suite('collapsing args preserves the tool-call envelope and providerMetadata', () => {
  const history: ChatMessage[] = [
    asstCall(
      'w1',
      'vfs_write',
      { path: 'A', content: big() },
      { providerMetadata: { sig: 'keep' } },
    ),
    toolMsg('w1', 'ok'),
    asstCall('w2', 'vfs_write', { path: 'A', content: big() }),
    toolMsg('w2', 'ok'),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['w1', reg({ on: { kind: 'superseded', by: 'A' }, args: 'pointer' })],
    ['w2', reg({ on: { kind: 'superseded', by: 'A' }, args: 'pointer' })],
  ]);
  const { on } = sink();
  const out = applyCondensation(history, policies, ctx(), on);
  const tc = out[0].toolCalls![0];
  assert.is(tc.id, 'w1', 'id preserved');
  assert.is(tc.name, 'vfs_write', 'name preserved');
  assert.equal(tc.providerMetadata, { sig: 'keep' }, 'reasoning signature round-trips');
});

// ---------------------------------------------------------------------------
// report-once + no-op
// ---------------------------------------------------------------------------

suite('the event fires once per payload across repeated applies', () => {
  const history: ChatMessage[] = [
    asstCall('r1', 'vfs_read', { path: 'A' }),
    toolMsg('r1', big()),
    asstCall('r2', 'vfs_read', { path: 'A' }),
    toolMsg('r2', big()),
  ];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['r1', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
    ['r2', reg({ on: { kind: 'superseded', by: 'A' }, response: 'pointer' })],
  ]);
  const { events, on } = sink();
  applyCondensation(history, policies, ctx(), on);
  applyCondensation(history, policies, ctx(), on);
  applyCondensation(history, policies, ctx(), on);
  assert.is(events.length, 1, 'collapse re-runs every call but the transition is reported once');
});

suite('an empty registry is a pure pass-through (same array reference)', () => {
  const history: ChatMessage[] = [asstCall('r1', 'vfs_read'), toolMsg('r1', big())];
  const { events, on } = sink();
  const out = applyCondensation(history, new Map(), ctx(), on);
  assert.is(out, history);
  assert.is(events.length, 0);
});

// ---------------------------------------------------------------------------
// turnEnd
// ---------------------------------------------------------------------------

suite('turnEnd: full during its own turn, collapsed on a later turn', () => {
  const history: ChatMessage[] = [asstCall('t1', 'read'), toolMsg('t1', big())];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['t1', reg({ on: { kind: 'turnEnd' }, response: 'pointer' }, { turn: 1 })],
  ]);
  const { events, on } = sink();

  const sameTurn = applyCondensation(history, policies, ctx({ turn: 1 }), on);
  assert.is(sameTurn[1].toolResult!.content, big(), 'full for the rest of the request');
  assert.is(events.length, 0);

  const laterTurn = applyCondensation(history, policies, ctx({ turn: 2 }), on);
  assert.match(laterTurn[1].toolResult!.content, /turn ended/);
  assert.is(events.length, 1);
  assert.is(events[0].trigger, 'turnEnd');
});

// ---------------------------------------------------------------------------
// agentEnd
// ---------------------------------------------------------------------------

suite('agentEnd: kept while the agent is active, collapsed once its activation ends', () => {
  const history: ChatMessage[] = [asstCall('a1', 'load'), toolMsg('a1', big())];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['a1', reg({ on: { kind: 'agentEnd' }, response: 'pointer' }, { activation: 1 })],
  ]);
  const { events, on } = sink();

  const active = applyCondensation(history, policies, ctx({ activationEnded: () => false }), on);
  assert.is(active[1].toolResult!.content, big(), 'full while the agent flow is active');
  assert.is(events.length, 0);

  const ended = applyCondensation(history, policies, ctx({ activationEnded: (a) => a === 1 }), on);
  assert.match(ended[1].toolResult!.content, /agent finished/);
  assert.is(events.length, 1);
  assert.is(events[0].trigger, 'agentEnd');
});

// ---------------------------------------------------------------------------
// phaseEnd
// ---------------------------------------------------------------------------

suite('phaseEnd: kept within the phase, collapsed once the app advances the epoch', () => {
  // Registered in phase epoch 1.
  const history: ChatMessage[] = [asstCall('p1', 'load_entity'), toolMsg('p1', big())];
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['p1', reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { phaseEpoch: 1 })],
  ]);
  const { events, on } = sink();

  // Still in phase 1 (no endPhase yet) — full.
  const sameP = applyCondensation(history, policies, ctx({ phaseEnded: (e) => e < 1 }), on);
  assert.is(sameP[1].toolResult!.content, big(), 'full while the phase is current');
  assert.is(events.length, 0);

  // endPhase() advanced the current epoch to 2 → the phase-1 payload collapses.
  const ended = applyCondensation(history, policies, ctx({ phaseEnded: (e) => e < 2 }), on);
  assert.match(ended[1].toolResult!.content, /phase ended/);
  assert.match(ended[1].toolResult!.content, /re-call to restore/);
  assert.is(events.length, 1);
  assert.is(events[0].trigger, 'phaseEnd');
});

suite('phaseEnd: falls back to agentEnd when no phase boundary is ever declared', () => {
  // Phase epoch never advances (phaseEnded always false), but the activation ends.
  const history: ChatMessage[] = [asstCall('p1', 'load_entity'), toolMsg('p1', big())];
  const policies = new Map<string, RegisteredCondensePolicy>([
    [
      'p1',
      reg({ on: { kind: 'phaseEnd' }, response: 'pointer' }, { activation: 1, phaseEpoch: 1 }),
    ],
  ]);
  const { events, on } = sink();

  const active = applyCondensation(
    history,
    policies,
    ctx({ phaseEnded: () => false, activationEnded: () => false }),
    on,
  );
  assert.is(active[1].toolResult!.content, big(), 'full while both phase and agent are live');
  assert.is(events.length, 0);

  const disposed = applyCondensation(
    history,
    policies,
    ctx({ phaseEnded: () => false, activationEnded: (a) => a === 1 }),
    on,
  );
  assert.match(disposed[1].toolResult!.content, /phase ended/, 'agentEnd backstop collapses it');
  assert.is(events.length, 1);
  assert.is(events[0].trigger, 'phaseEnd');
});

// ---------------------------------------------------------------------------
// multi-trigger (OR / first-wins)
// ---------------------------------------------------------------------------

suite('multi-trigger: collapses when ANY listed trigger fires', () => {
  // superseded OR turnEnd — only one call for key A (never superseded), but the turn ends.
  const history: ChatMessage[] = [asstCall('m1', 'read', { path: 'A' }), toolMsg('m1', big())];
  const policies = new Map<string, RegisteredCondensePolicy>([
    [
      'm1',
      reg(
        { on: [{ kind: 'superseded', by: 'A' }, { kind: 'turnEnd' }], response: 'pointer' },
        { turn: 1 },
      ),
    ],
  ]);
  const { events, on } = sink();

  assert.is(
    applyCondensation(history, policies, ctx({ turn: 1 }), on)[1].toolResult!.content,
    big(),
    'neither trigger has fired',
  );
  const later = applyCondensation(history, policies, ctx({ turn: 2 }), on);
  assert.match(later[1].toolResult!.content, /turn ended/, 'turnEnd fired via the OR');
  assert.is(events[0].trigger, 'turnEnd');
});

suite('multi-trigger first-wins: the earliest-listed firing trigger is the reason', () => {
  // m1 is superseded by m2 AND its turn has ended; m2 is the latest (not superseded)
  // but its turn has ended too. `superseded` is listed first.
  const history: ChatMessage[] = [
    asstCall('m1', 'read', { path: 'A' }),
    toolMsg('m1', big()),
    asstCall('m2', 'read', { path: 'A' }),
    toolMsg('m2', big()),
  ];
  const on0: CondensePolicy = {
    on: [{ kind: 'superseded', by: 'A' }, { kind: 'turnEnd' }],
    response: 'pointer',
  };
  const policies = new Map<string, RegisteredCondensePolicy>([
    ['m1', reg(on0, { turn: 1 })],
    ['m2', reg(on0, { turn: 1 })],
  ]);
  const { events, on } = sink();
  applyCondensation(history, policies, ctx({ turn: 2 }), on);

  assert.is(
    events.find((e) => e.toolCallId === 'm1')?.trigger,
    'superseded:A',
    'first-listed firing trigger wins as the reason',
  );
  assert.is(
    events.find((e) => e.toolCallId === 'm2')?.trigger,
    'turnEnd',
    'the supersession survivor still collapses via the next trigger',
  );
});

suite.run();
