import { describe, it, expect } from 'vitest';
import { resolveChatId } from '../chat-resolve.js';
import type { Sql } from '../../db.js';

// Mock the porsager/postgres surface that chat-resolve.ts uses: a tagged-template
// `tx` (dispatched by query substring), `tx.json`, and `sql.begin(fn)` which just
// runs fn(tx). Captures the value written back to workspace_panes so we can assert
// the WorkspaceState envelope survives the UPDATE.
interface MockState {
  stored: unknown; // initial sessions.workspace_panes value
  existingChatOpen: boolean; // whether `SELECT id FROM chats ...` finds the active chat
  newChatId: string;
  written?: unknown; // captured tx.json(...) payload from `UPDATE sessions`
  inserted: boolean; // whether INSERT INTO chats ran
}

interface MockTx {
  (strings: TemplateStringsArray): Promise<unknown>;
  json: (v: unknown) => unknown;
}

function mockSql(state: MockState): Sql {
  const tx = ((strings: TemplateStringsArray) => {
    const q = strings.join('');
    if (q.includes('SELECT workspace_panes FROM sessions')) {
      return Promise.resolve([{ workspace_panes: state.stored }]);
    }
    if (q.includes('FROM chats')) {
      return Promise.resolve(state.existingChatOpen ? [{ id: 'placeholder' }] : []);
    }
    if (q.includes('INSERT INTO chats')) {
      state.inserted = true;
      return Promise.resolve([{ id: state.newChatId }]);
    }
    if (q.includes('UPDATE sessions')) {
      return Promise.resolve([]);
    }
    return Promise.resolve([]);
  }) as unknown as MockTx;
  tx.json = (v: unknown) => {
    state.written = v;
    return v;
  };
  const sql = {
    begin: (fn: (t: Sql) => Promise<unknown>) => fn(tx as unknown as Sql),
  };
  return sql as unknown as Sql;
}

const ENVELOPE = () => ({
  panes: [{ id: 'pane-1', kind: 'coder', chatIds: [] as string[], activeChatIdx: 0 }],
  tabNumbers: { 'chat-x': 3 },
  nextTabNumber: 7,
  closedPaneStack: [{ kind: 'coder', chatIds: ['old'], activeChatIdx: 0 }],
});

describe('resolveChatId — v2.6.5 WorkspaceState envelope', () => {
  it('reads panes from the envelope without crashing (regression: panes.findIndex is not a function)', async () => {
    const state: MockState = {
      stored: ENVELOPE(),
      existingChatOpen: false,
      newChatId: 'new-chat-1',
      inserted: false,
    };
    const chatId = await resolveChatId(mockSql(state), 'session-1', 'pane-1');
    expect(chatId).toBe('new-chat-1');
    expect(state.inserted).toBe(true);
  });

  it('preserves the envelope (tabNumbers/nextTabNumber/closedPaneStack) on write-back', async () => {
    const state: MockState = {
      stored: ENVELOPE(),
      existingChatOpen: false,
      newChatId: 'new-chat-1',
      inserted: false,
    };
    await resolveChatId(mockSql(state), 'session-1', 'pane-1');
    const w = state.written as Record<string, unknown>;
    expect(Array.isArray(w.panes)).toBe(true); // envelope, not a bare array
    expect(w.tabNumbers).toEqual({ 'chat-x': 3 });
    expect(w.nextTabNumber).toBe(7);
    expect(w.closedPaneStack).toEqual([{ kind: 'coder', chatIds: ['old'], activeChatIdx: 0 }]);
  });

  it('returns the existing open chat when the pane already has one', async () => {
    const env = ENVELOPE();
    env.panes[0]!.chatIds = ['existing-1'];
    const state: MockState = {
      stored: env,
      existingChatOpen: true,
      newChatId: 'should-not-be-used',
      inserted: false,
    };
    const chatId = await resolveChatId(mockSql(state), 'session-1', 'pane-1');
    expect(chatId).toBe('existing-1');
    expect(state.inserted).toBe(false);
  });

  it('still accepts a legacy bare WorkspacePane[] array', async () => {
    const state: MockState = {
      stored: [{ id: 'pane-1', kind: 'coder', chatId: 'legacy-1', chatIds: ['legacy-1'], activeChatIdx: 0 }],
      existingChatOpen: true,
      newChatId: 'should-not-be-used',
      inserted: false,
    };
    const chatId = await resolveChatId(mockSql(state), 'session-1', 'pane-1');
    expect(chatId).toBe('legacy-1');
    expect(state.inserted).toBe(false);
  });
});
