import { describe, it, expect } from 'vitest';
import { WsFrameSchema, KNOWN_FRAME_TYPES } from '../ws-frames.js';

const VALID_UUID_A = '00000000-0000-0000-0000-000000000001';
const VALID_UUID_B = '00000000-0000-0000-0000-000000000002';
const VALID_UUID_C = '00000000-0000-0000-0000-000000000003';
const VALID_TIMESTAMP = '2026-05-22T14:30:00.000Z';

describe('WsFrameSchema (v1.13.11-a)', () => {
  it('accepts a well-formed chat_status frame', () => {
    const result = WsFrameSchema.safeParse({
      type: 'chat_status',
      chat_id: VALID_UUID_A,
      status: 'streaming',
      at: VALID_TIMESTAMP,
    });
    expect(result.success).toBe(true);
  });

  it('rejects an unknown frame type', () => {
    const result = WsFrameSchema.safeParse({
      type: 'cosmic_ray_strike',
      chat_id: VALID_UUID_A,
    });
    expect(result.success).toBe(false);
  });

  it('rejects a chat_status frame with invalid status enum', () => {
    // v1.12.1 dropped the legacy 'working' status. Any frame still emitting it
    // should fail validation — that's a drift catcher.
    const result = WsFrameSchema.safeParse({
      type: 'chat_status',
      chat_id: VALID_UUID_A,
      status: 'working',
      at: VALID_TIMESTAMP,
    });
    expect(result.success).toBe(false);
  });

  it('rejects a UUID field with a non-UUID string', () => {
    const result = WsFrameSchema.safeParse({
      type: 'chat_status',
      chat_id: 'not-a-uuid',
      status: 'idle',
      at: VALID_TIMESTAMP,
    });
    expect(result.success).toBe(false);
  });

  it('rejects negative token counts in usage frame', () => {
    const result = WsFrameSchema.safeParse({
      type: 'usage',
      message_id: VALID_UUID_A,
      chat_id: VALID_UUID_B,
      completion_tokens: -1,
      ctx_used: 100,
      ctx_max: 1000,
    });
    expect(result.success).toBe(false);
  });

  it('accepts a usage frame with nullable token counts (pre-v1.13.7 history)', () => {
    const result = WsFrameSchema.safeParse({
      type: 'usage',
      message_id: VALID_UUID_A,
      chat_id: VALID_UUID_B,
      completion_tokens: null,
      ctx_used: null,
      ctx_max: null,
    });
    expect(result.success).toBe(true);
  });

  it('accepts a tool_result frame with non-UUID tool_call_id (model-emitted)', () => {
    // Model-emitted tool_call_ids look like "call_abc123", not UUIDs.
    const result = WsFrameSchema.safeParse({
      type: 'tool_result',
      tool_message_id: VALID_UUID_A,
      chat_id: VALID_UUID_B,
      tool_call_id: 'call_abc123',
      output: { whatever: true },
      truncated: false,
    });
    expect(result.success).toBe(true);
  });

  it('accepts a compacted frame', () => {
    const result = WsFrameSchema.safeParse({
      type: 'compacted',
      session_id: VALID_UUID_A,
      chat_id: VALID_UUID_B,
      summary_message_id: VALID_UUID_C,
    });
    expect(result.success).toBe(true);
  });

  it('accepts a session_workspace_updated frame', () => {
    const result = WsFrameSchema.safeParse({
      type: 'session_workspace_updated',
      session_id: VALID_UUID_A,
      workspace_panes: [{ id: 'p1', kind: 'chat', chatIds: [], activeChatIdx: 0 }],
    });
    expect(result.success).toBe(true);
  });

  it('accepts a message_complete frame with a null model (external coder, no model selected)', () => {
    // Regression guard: the dispatcher publishes `model: task.model` (string |
    // null). When null, this MUST validate or publishFrame fail-closes and drops
    // the whole frame, incl. the status:'complete' transition.
    const result = WsFrameSchema.safeParse({
      type: 'message_complete',
      message_id: VALID_UUID_A,
      chat_id: VALID_UUID_B,
      model: null,
    });
    expect(result.success).toBe(true);
  });

  it('every KNOWN_FRAME_TYPES entry has a discriminated branch', () => {
    // Probe each known type by attempting a minimal valid construction.
    // Failure here means the union and the KNOWN_FRAME_TYPES list drifted.
    for (const type of KNOWN_FRAME_TYPES) {
      const probe = WsFrameSchema.safeParse({ type, __dummy__: true });
      // We expect FAILURE on every type because we're missing required fields,
      // but the failure must be ABOUT the missing fields, not about an unknown
      // type. A "Invalid discriminator value" error means the type isn't in
      // the union — that's a drift.
      if (probe.success) continue;
      const issues = probe.error.issues;
      const hasInvalidDiscriminator = issues.some(
        (i) => i.code === 'invalid_union_discriminator',
      );
      expect(hasInvalidDiscriminator, `frame type '${type}' is missing from the discriminated union`).toBe(false);
    }
  });
});
