/// <reference types="jest" />
/**
 * Tests for Computer Tool
 */

import { createMockSessionManager, createMockRefIdManager } from '../utils/mock-session';
import { keyNormalizationMap } from '../utils/test-helpers';
import { DEFAULT_SCREENSHOT_RACE_TIMEOUT_MS, MAX_INLINE_IMAGE_PAYLOAD_BYTES } from '../../src/config/defaults';

const PNG_BYTES = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00]);
const PNG_BASE64 = PNG_BYTES.toString('base64');

// Mock the session manager and ref-id-manager modules
jest.mock('../../src/session-manager', () => ({
  getSessionManager: jest.fn(),
}));

jest.mock('../../src/utils/ref-id-manager', () => ({
  getRefIdManager: jest.fn(),
  makeStaleRefError: (refId: string) => ({ code: 'STALE_REF', ref_id: refId, hint: "call read_page (mode='ax') to get fresh refs" }),
  formatStaleRefError: (refId: string) => `STALE_REF: ref_id="${refId}" — call read_page (mode='ax') to get fresh refs`,
}));

import { getSessionManager } from '../../src/session-manager';
import { getRefIdManager } from '../../src/utils/ref-id-manager';

describe('ComputerTool', () => {
  const mockDetectPagination = jest.fn();
  let mockSessionManager: ReturnType<typeof createMockSessionManager>;
  let mockRefIdManager: ReturnType<typeof createMockRefIdManager>;
  let testSessionId: string;
  let testTargetId: string;

  const getComputerHandler = async () => {
    jest.resetModules();
    jest.doMock('../../src/session-manager', () => ({
      getSessionManager: () => mockSessionManager,
    }));
    jest.doMock('../../src/utils/ref-id-manager', () => ({
      getRefIdManager: () => mockRefIdManager,
      makeStaleRefError: (refId: string) => ({ code: 'STALE_REF', ref_id: refId, hint: "call read_page (mode='ax') to get fresh refs" }),
      formatStaleRefError: (refId: string) => `STALE_REF: ref_id="${refId}" — call read_page (mode='ax') to get fresh refs`,
    }));
    jest.doMock('../../src/utils/pagination-detector', () => ({
      detectPagination: mockDetectPagination,
    }));

    const { registerComputerTool } = await import('../../src/tools/computer');

    const tools: Map<string, { handler: (sessionId: string, args: Record<string, unknown>) => Promise<unknown> }> = new Map();
    const mockServer = {
      registerTool: (name: string, handler: unknown) => {
        tools.set(name, { handler: handler as (sessionId: string, args: Record<string, unknown>) => Promise<unknown> });
      },
    };

    registerComputerTool(mockServer as unknown as Parameters<typeof registerComputerTool>[0]);
    return tools.get('computer')!.handler;
  };

  beforeEach(async () => {
    mockDetectPagination.mockReset();
    mockDetectPagination.mockResolvedValue({
      type: 'none',
      hasNext: false,
      hasPrev: false,
      suggestedStrategy: 'No pagination detected',
    });
    mockSessionManager = createMockSessionManager();
    mockRefIdManager = createMockRefIdManager();
    (getSessionManager as jest.Mock).mockReturnValue(mockSessionManager);
    (getRefIdManager as jest.Mock).mockReturnValue(mockRefIdManager);

    testSessionId = 'test-session-123';
    const { targetId } = await mockSessionManager.createTarget(testSessionId, 'about:blank');
    testTargetId = targetId;
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  describe('Click Actions', () => {
    test('left_click at coordinates', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [100, 200],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.click).toHaveBeenCalledWith(100, 200);
      expect(result.content[0].text).toContain('Clicked at (100, 200)');
    });

    test('right_click at coordinates', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'right_click',
        coordinate: [150, 250],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.click).toHaveBeenCalledWith(150, 250, { button: 'right' });
      expect(result.content[0].text).toContain('Right-clicked');
    });

    test('double_click at coordinates', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'double_click',
        coordinate: [200, 300],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.click).toHaveBeenCalledWith(200, 300, { clickCount: 2 });
      expect(result.content[0].text).toContain('Double-clicked');
    });

    test('triple_click at coordinates', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'triple_click',
        coordinate: [250, 350],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.click).toHaveBeenCalledWith(250, 350, { clickCount: 3 });
      expect(result.content[0].text).toContain('Triple-clicked');
    });

    test('rejects left_click without coordinates', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('coordinate is required');
    });

    test('rejects right_click without coordinates', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'right_click',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('coordinate is required');
    });

    test('rejects double_click without coordinates', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'double_click',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('coordinate is required');
    });

    test('handles click at origin (0, 0)', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [0, 0],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.click).toHaveBeenCalledWith(0, 0);
      expect(result.content[0].text).toContain('(0, 0)');
    });

    test('handles large coordinate values', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [10000, 20000],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.click).toHaveBeenCalledWith(10000, 20000);
    });
  });

  describe('Hover Action', () => {
    test('hover at coordinates', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'hover',
        coordinate: [100, 200],
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.mouse.move).toHaveBeenCalledWith(100, 200);
      expect(result.content[0].text).toContain('Hovered at (100, 200)');
    });

    test('rejects hover without coordinates', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'hover',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('coordinate is required');
    });
  });

  describe('Keyboard Actions', () => {
    test('type text input', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'type',
        text: 'Hello World',
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.keyboard.type).toHaveBeenCalledWith('Hello World');
      expect(result.content[0].text).toContain('Typed: Hello World');
    });

    test('press single key', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'Enter',
      }) as { content: Array<{ type: string; text: string }> };

      expect(page.keyboard.press).toHaveBeenCalledWith('Enter');
      expect(result.content[0].text).toContain('Pressed: Enter');
    });

    test('press key combination (ctrl+a)', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'ctrl+a',
      });

      expect(page.keyboard.down).toHaveBeenCalledWith('Control');
      expect(page.keyboard.press).toHaveBeenCalledWith('a');
      expect(page.keyboard.up).toHaveBeenCalledWith('Control');
    });

    test('press key combination with multiple modifiers (ctrl+shift+s)', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'ctrl+shift+s',
      });

      expect(page.keyboard.down).toHaveBeenCalledWith('Control');
      expect(page.keyboard.down).toHaveBeenCalledWith('Shift');
      expect(page.keyboard.press).toHaveBeenCalledWith('s');
    });

    test('press multiple keys separated by space', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'Tab Tab Enter',
      });

      expect(page.keyboard.press).toHaveBeenCalledWith('Tab');
      expect(page.keyboard.press).toHaveBeenCalledWith('Enter');
    });

    test.each(Object.entries(keyNormalizationMap))('normalizes key: %s -> %s', async (input, expected) => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: input,
      });

      expect(page.keyboard.press).toHaveBeenCalledWith(expected);
    });

    test('single character keys pass through directly', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'a',
      });
      expect(page.keyboard.press).toHaveBeenCalledWith('a');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'A',
      });
      expect(page.keyboard.press).toHaveBeenCalledWith('A');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: '0',
      });
      expect(page.keyboard.press).toHaveBeenCalledWith('0');
    });

    test('unknown multi-character keys pass through as-is (e.g., F13, NumpadAdd)', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'F13',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBeFalsy();
      expect(page.keyboard.press).toHaveBeenCalledWith('F13');
    });

    test.each([
      ['ENTER', 'Enter'],
      ['Enter', 'Enter'],
      ['enter', 'Enter'],
      ['ESCAPE', 'Escape'],
      ['Escape', 'Escape'],
      ['TAB', 'Tab'],
    ])('case-insensitive normalization: %s -> %s', async (input, expected) => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: input,
      });

      expect(page.keyboard.press).toHaveBeenCalledWith(expected);
    });

    test('macOS aliases: Return, Option, Command', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'Return',
      });
      expect(page.keyboard.press).toHaveBeenCalledWith('Enter');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'command+a',
      });
      expect(page.keyboard.down).toHaveBeenCalledWith('Meta');
      expect(page.keyboard.press).toHaveBeenCalledWith('a');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'option+a',
      });
      expect(page.keyboard.down).toHaveBeenCalledWith('Alt');
    });

    test('Space key alias works', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'space',
      });
      expect(page.keyboard.press).toHaveBeenCalledWith('Space');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'spacebar',
      });
      expect(page.keyboard.press).toHaveBeenCalledWith('Space');
    });

    test('Windows/Linux aliases: super, win', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'super+a',
      });
      expect(page.keyboard.down).toHaveBeenCalledWith('Meta');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
        text: 'win+a',
      });
      expect(page.keyboard.down).toHaveBeenCalledWith('Meta');
    });

    test('rejects type without text', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'type',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('text is required');
    });

    test('rejects key without text', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'key',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('text is required');
    });
  });

  describe('Screenshot', () => {
    test('returns base64 WebP image via CDP', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; data?: string; mimeType?: string }> };

      expect(result.content[0].type).toBe('image');
      expect(result.content[0].data).toBe('base64-screenshot-data');
    });

    test('returns correct mime type', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; mimeType?: string }> };

      expect(result.content[0].mimeType).toBe('image/webp');
    });

    test('appends batch_paginate guidance when screenshot detects pagination', async () => {
      mockDetectPagination.mockResolvedValueOnce({
        type: 'infinite_scroll',
        hasNext: true,
        hasPrev: false,
        suggestedStrategy: 'Use batch_paginate',
      });
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; data?: string; text?: string }> };

      expect(result.content[0]).toMatchObject({ type: 'image', data: 'base64-screenshot-data' });
      expect(result.content[1]).toMatchObject({ type: 'text' });
      expect(result.content[1].text).toContain('[Pagination Detected]');
      expect(result.content[1].text).toContain('batch_paginate');
      expect(result.content[1].text).toContain('manual next/scroll loops');
    });

    test('ignores pagination detection failures during screenshot', async () => {
      mockDetectPagination.mockRejectedValueOnce(new Error('detector failed'));
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; data?: string }> };

      expect(result.content).toHaveLength(1);
      expect(result.content[0]).toMatchObject({ type: 'image', data: 'base64-screenshot-data' });
    });

    test('uses CDP Page.captureScreenshot', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      });

      const cdpSession = await (page as any).createCDPSession();
      expect(cdpSession.send).toHaveBeenCalledWith('Page.captureScreenshot', expect.objectContaining({
        format: 'webp',
        optimizeForSpeed: true,
      }));
    });

    test('normalizes MIME when a WebP-requested CDP screenshot returns PNG bytes', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      const cdpSession = await (page as any).createCDPSession();
      (cdpSession.send as jest.Mock).mockResolvedValueOnce({ data: PNG_BASE64 });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; data?: string; mimeType?: string }> };

      expect(result.content[0]).toMatchObject({
        type: 'image',
        data: PNG_BASE64,
        mimeType: 'image/png',
      });
    });

    test('returns image/png and omits quality when screenshotFormat="png"', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
        screenshotFormat: 'png',
      }) as { content: Array<{ type: string; data?: string; mimeType?: string }> };

      expect(result.content[0].type).toBe('image');
      expect(result.content[0].mimeType).toBe('image/png');

      const cdpSession = await (page as any).createCDPSession();
      const captureCall = (cdpSession.send as jest.Mock).mock.calls.find(
        (c) => c[0] === 'Page.captureScreenshot',
      );
      expect(captureCall).toBeDefined();
      const params = captureCall![1] as Record<string, unknown>;
      expect(params.format).toBe('png');
      expect(params.quality).toBeUndefined();
      expect(params.optimizeForSpeed).toBeUndefined();
    });

    test('returns image/jpeg with quality when screenshotFormat="jpeg"', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
        screenshotFormat: 'jpeg',
      }) as { content: Array<{ type: string; mimeType?: string }> };

      expect(result.content[0].mimeType).toBe('image/jpeg');

      const cdpSession = await (page as any).createCDPSession();
      const captureCall = (cdpSession.send as jest.Mock).mock.calls.find(
        (c) => c[0] === 'Page.captureScreenshot',
      );
      const params = captureCall![1] as Record<string, unknown>;
      expect(params.format).toBe('jpeg');
      expect(typeof params.quality).toBe('number');
      expect(params.optimizeForSpeed).toBe(true);
    });

    test('falls back to webp when screenshotFormat is unrecognized', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
        screenshotFormat: 'tiff',
      }) as { content: Array<{ type: string; mimeType?: string }> };

      expect(result.content[0].mimeType).toBe('image/webp');

      const cdpSession = await (page as any).createCDPSession();
      const captureCall = (cdpSession.send as jest.Mock).mock.calls.find(
        (c) => c[0] === 'Page.captureScreenshot',
      );
      expect((captureCall![1] as Record<string, unknown>).format).toBe('webp');
    });

    test('rejects inline screenshot payloads over 10 MiB after encoding', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      const cdpSession = await (page as any).createCDPSession();
      (cdpSession.send as jest.Mock).mockResolvedValue({
        data: 'a'.repeat(MAX_INLINE_IMAGE_PAYLOAD_BYTES + 1),
      });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('exceeds the 10 MiB inline limit');
    });

    test('rejects oversized viewport area before CDP capture', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.viewport as jest.Mock).mockReturnValue({ width: 6000, height: 5000 });
      const cdpSession = await (page as any).createCDPSession();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Screenshot area 6000x5000');
      expect(result.content[0].text).toContain('Request viewport-only capture');
      expect(cdpSession.send).not.toHaveBeenCalledWith('Page.captureScreenshot', expect.anything());
    });

    test('reads live window dimensions when page.viewport() returns null', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.viewport as jest.Mock).mockReturnValue(null);
      (page.evaluate as jest.Mock).mockResolvedValue({ width: 1024, height: 768 });
      const cdpSession = await (page as any).createCDPSession();
      (cdpSession.send as jest.Mock).mockResolvedValue({ data: 'ZmFrZQ==' });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string }>; isError?: boolean };

      expect(result.isError).toBeFalsy();
      expect(result.content[0].type).toBe('image');
      expect(page.evaluate).toHaveBeenCalled();
      expect(cdpSession.send).toHaveBeenCalledWith('Page.captureScreenshot', expect.any(Object));
    });

    test('rejects oversized live window dimensions when page.viewport() is null', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.viewport as jest.Mock).mockReturnValue(null);
      (page.evaluate as jest.Mock).mockResolvedValue({ width: 6000, height: 5000 });
      const cdpSession = await (page as any).createCDPSession();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Screenshot area 6000x5000');
      expect(cdpSession.send).not.toHaveBeenCalledWith('Page.captureScreenshot', expect.anything());
    });

    test('does not start a duplicate screenshot capture after race timeout', async () => {
      jest.useFakeTimers();
      try {
        const handler = await getComputerHandler();
        const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
        const cdpSession = await (page as any).createCDPSession();
        (cdpSession.send as jest.Mock).mockImplementation(() => new Promise(() => {}));

        const pending = handler(testSessionId, {
          tabId: testTargetId,
          action: 'screenshot',
        }) as Promise<{ content: Array<{ text?: string }>; isError?: boolean }>;

        await jest.advanceTimersByTimeAsync(DEFAULT_SCREENSHOT_RACE_TIMEOUT_MS);
        await pending;

        const captureCalls = (cdpSession.send as jest.Mock).mock.calls.filter(
          (c) => c[0] === 'Page.captureScreenshot'
        );
        expect(captureCalls).toHaveLength(1);
      } finally {
        jest.useRealTimers();
      }
    });
  });

  describe('Scroll Actions', () => {
    test('scroll up', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
        scroll_direction: 'up',
      });

      expect(page.mouse.move).toHaveBeenCalledWith(500, 500);
      expect(page.mouse.wheel).toHaveBeenCalledWith({
        deltaX: 0,
        deltaY: -300, // 3 * 100 default
      });
    });

    test('scroll down', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
        scroll_direction: 'down',
      });

      expect(page.mouse.wheel).toHaveBeenCalledWith({
        deltaX: 0,
        deltaY: 300,
      });
    });

    test('scroll propagates error when mouse.wheel fails', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Mock mouse.wheel to throw timeout error on all attempts
      (page.mouse.wheel as jest.Mock).mockRejectedValue(
        new Error('Input.dispatchMouseEvent timed out')
      );
      // Also make the JS fallback (page.evaluate) fail so error propagates
      (page.evaluate as jest.Mock).mockRejectedValue(
        new Error('Input.dispatchMouseEvent timed out')
      );

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
        scroll_direction: 'down',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      // Error should be propagated
      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('error');
    });

    test('scroll left', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
        scroll_direction: 'left',
      });

      expect(page.mouse.wheel).toHaveBeenCalledWith({
        deltaX: -300,
        deltaY: 0,
      });
    });

    test('scroll right', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
        scroll_direction: 'right',
      });

      expect(page.mouse.wheel).toHaveBeenCalledWith({
        deltaX: 300,
        deltaY: 0,
      });
    });

    test('scroll with custom amount', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
        scroll_direction: 'down',
        scroll_amount: 5,
      });

      expect(page.mouse.wheel).toHaveBeenCalledWith({
        deltaX: 0,
        deltaY: 500, // 5 * 100
      });
    });

    test('scroll without coordinates falls back to viewport center', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        scroll_direction: 'down',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toContain('Scrolled down');
    });

    test('scroll without direction defaults to down', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll',
        coordinate: [500, 500],
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toContain('Scrolled down');
    });
  });

  describe('Scroll To Action', () => {
    test('scroll_to with valid ref', async () => {
      const handler = await getComputerHandler();

      // Set up a ref
      const refId = mockRefIdManager.generateRef(testSessionId, testTargetId, 12345, 'button', 'Submit');

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll_to',
        ref: refId,
      }) as { content: Array<{ type: string; text: string }> };

      expect(mockRefIdManager.resolveToBackendNodeId).toHaveBeenCalledWith(testSessionId, testTargetId, refId);
      expect(result.content[0].text).toContain('Scrolled to');
    });

    test('scroll_to with invalid ref', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll_to',
        ref: 'nonexistent_ref',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('not found');
    });

    test('rejects scroll_to without ref', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'scroll_to',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('ref is required');
    });
  });

  describe('Wait Action', () => {
    test('waits for specified duration', async () => {
      const handler = await getComputerHandler();
      const startTime = Date.now();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'wait',
        duration: 0.1, // 100ms
      }) as { content: Array<{ type: string; text: string }> };

      const elapsed = Date.now() - startTime;
      expect(elapsed).toBeGreaterThanOrEqual(90); // Allow some tolerance
      expect(result.content[0].text).toContain('0.1 seconds');
    });

    test('caps wait at 30 seconds', async () => {
      const handler = await getComputerHandler();

      // This test would take too long if it actually waited, so we just check the cap logic
      // The actual wait is capped at 30 seconds
      const startTime = Date.now();

      // Don't actually wait 30 seconds in tests - just verify the cap is applied
      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'wait',
        duration: 0.01, // Just a short wait to verify it works
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('Waited');
    });

    test('handles default duration', async () => {
      const handler = await getComputerHandler();
      const startTime = Date.now();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'wait',
      }) as { content: Array<{ type: string; text: string }> };

      // Default should be 1 second, but we'll just check it worked
      expect(result.content[0].text).toContain('Waited');
    });

    test('handles zero duration (defaults to 1 second)', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'wait',
        duration: 0,
      }) as { content: Array<{ type: string; text: string }> };

      // Note: Implementation uses (duration || 1) which treats 0 as falsy, defaulting to 1 second
      expect(result.content[0].text).toContain('Waited 1 seconds');
    });
  });

  describe('Error Handling', () => {
    test('returns error for missing tabId', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('tabId is required');
    });

    test('returns error for unknown action', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'unknown_action',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Unknown action');
    });

    test('returns error when tab not found', async () => {
      const handler = await getComputerHandler();
      mockSessionManager.getPage.mockResolvedValueOnce(null);

      const result = await handler(testSessionId, {
        tabId: 'non-existent-tab',
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('not found');
    });

    test('handles screenshot failure with DOM fallback when page is responsive', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Make CDP session fail both attempts (via target.createCDPSession)
      const target = (page as any).target();
      (target.createCDPSession as jest.Mock).mockRejectedValue(new Error('CDP unavailable'));
      // page.evaluate call order: readyState check, AdaptiveScreenshot scroll position, DOM fallback
      (page.evaluate as jest.Mock)
        .mockResolvedValueOnce('complete')
        .mockResolvedValueOnce({ scrollTop: 0, scrollLeft: 0 })
        .mockResolvedValueOnce({
          url: 'https://example.com',
          title: 'Test',
          readyState: 'complete',
          textPreview: 'page content',
        });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toContain('Screenshot failed');
      expect(result.content[0].text).toContain('DOM fallback');
    });

    test('handles click failure', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.mouse.click as jest.Mock).mockRejectedValue(new Error('Click failed'));

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [100, 100],
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('Click failed');
    });
  });

  describe('Session Isolation', () => {
    test('rejects actions on tab from another session', async () => {
      const handler = await getComputerHandler();

      // Create a second session with its own tab
      const session2Id = 'other-session';
      await mockSessionManager.createSession({ id: session2Id });
      const { targetId: session2TargetId } = await mockSessionManager.createTarget(session2Id);

      // Try to take screenshot of session2's tab from session1
      const result = await handler(testSessionId, {
        tabId: session2TargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('does not belong to session');
    });
  });

  describe('Screenshot Resilience', () => {
    test('screenshot retries once on CDP failure', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Access the target mock (page.target().createCDPSession is the actual code path)
      const target = (page as any).target();
      (target.createCDPSession as jest.Mock)
        .mockRejectedValueOnce(new Error('CDP timeout'))
        .mockResolvedValueOnce({
          send: jest.fn().mockResolvedValue({ data: 'retry-screenshot-data' }),
          detach: jest.fn().mockResolvedValue(undefined),
        });

      // readyState check
      (page.evaluate as jest.Mock).mockResolvedValueOnce('complete');

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; data?: string; mimeType?: string }> };

      expect(result.content[0].type).toBe('image');
      expect(result.content[0].data).toBe('retry-screenshot-data');
      expect(result.content[0].mimeType).toBe('image/webp');
    });

    test('screenshot returns DOM fallback when both attempts fail', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Both CDP attempts fail (via target.createCDPSession)
      const target = (page as any).target();
      (target.createCDPSession as jest.Mock).mockRejectedValue(new Error('CDP unavailable'));

      // page.evaluate call order: readyState check, AdaptiveScreenshot scroll position, DOM fallback
      (page.evaluate as jest.Mock)
        .mockResolvedValueOnce('complete')
        .mockResolvedValueOnce({ scrollTop: 0, scrollLeft: 0 })
        .mockResolvedValueOnce({
          url: 'https://example.com',
          title: 'Example',
          readyState: 'complete',
          textPreview: 'Hello world content',
        });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text?: string }>; isError?: boolean };

      expect(result.content[0].type).toBe('text');
      expect(result.content[0].text).toContain('Screenshot failed');
      expect(result.content[0].text).toContain('DOM fallback');
    });

    test('screenshot checks page readiness before capture', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // readyState returns 'complete' immediately
      (page.evaluate as jest.Mock).mockResolvedValueOnce('complete');

      await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      });

      // evaluate should have been called for readyState check
      expect(page.evaluate).toHaveBeenCalled();
    });

    test('DOM fallback does not set isError', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Both CDP attempts fail (via target.createCDPSession)
      const target = (page as any).target();
      (target.createCDPSession as jest.Mock).mockRejectedValue(new Error('CDP unavailable'));

      // page.evaluate call order: readyState check, AdaptiveScreenshot scroll position, DOM fallback
      (page.evaluate as jest.Mock)
        .mockResolvedValueOnce('complete')
        .mockResolvedValueOnce({ scrollTop: 0, scrollLeft: 0 })
        .mockResolvedValueOnce({
          url: 'https://example.com',
          title: 'Test Page',
          readyState: 'complete',
          textPreview: 'Some content',
        });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text?: string }>; isError?: boolean };

      // DOM fallback is usable content — NOT an error
      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toContain('Screenshot failed');
    });

    test('completely unresponsive page sets isError', async () => {
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Both CDP attempts fail (via target.createCDPSession)
      const target = (page as any).target();
      (target.createCDPSession as jest.Mock).mockRejectedValue(new Error('CDP unavailable'));

      // All page.evaluate calls fail (page is completely unresponsive)
      (page.evaluate as jest.Mock).mockRejectedValue(new Error('Execution context was destroyed'));
      (page.waitForFunction as jest.Mock).mockRejectedValue(new Error('Execution context was destroyed'));

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'screenshot',
      }) as { content: Array<{ type: string; text?: string }>; isError?: boolean };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('unresponsive');
    });
  });

  describe('Hit Element Detection', () => {
    // Helper to set up CDP responses for a button element hit
    function setupButtonHit() {
      const cdpClient = mockSessionManager.mockCDPClient;
      cdpClient.cdpResponses.set(
        `DOM.getNodeForLocation:${JSON.stringify({ x: 100, y: 200, includeUserAgentShadowDOM: false })}`,
        { backendNodeId: 42, nodeId: 10 }
      );
      cdpClient.cdpResponses.set(
        `DOM.describeNode:${JSON.stringify({ backendNodeId: 42 })}`,
        {
          node: {
            localName: 'button',
            attributes: ['id', 'submit-btn', 'class', 'btn-primary'],
            nodeType: 1,
          },
        }
      );
    }

    // Helper to set up CDP responses for a non-interactive div element hit
    function setupDivHit(x: number, y: number) {
      const cdpClient = mockSessionManager.mockCDPClient;
      cdpClient.cdpResponses.set(
        `DOM.getNodeForLocation:${JSON.stringify({ x, y, includeUserAgentShadowDOM: false })}`,
        { backendNodeId: 99, nodeId: 20 }
      );
      cdpClient.cdpResponses.set(
        `DOM.describeNode:${JSON.stringify({ backendNodeId: 99 })}`,
        {
          node: {
            localName: 'div',
            attributes: ['class', 'container'],
            nodeType: 1,
          },
        }
      );
    }

    test('left_click includes hit element info for interactive element', async () => {
      setupButtonHit();
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // Mock evaluate to return empty textContent
      (page.evaluate as jest.Mock).mockResolvedValueOnce('');

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [100, 200],
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('Clicked at (100, 200)');
      expect(result.content[0].text).toContain('Hit:');
      expect(result.content[0].text).toContain('<button');
      expect(result.content[0].text).toContain('[interactive]');
    });

    test('hit element shows [not interactive] flag for non-interactive element', async () => {
      setupDivHit(100, 200);
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // First evaluate: textContent fetch, second: nearest interactive search
      (page.evaluate as jest.Mock)
        .mockResolvedValueOnce('')    // textContent
        .mockResolvedValueOnce(null); // nearest interactive (none found)

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [100, 200],
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('[not interactive]');
      expect(result.content[0].text).not.toContain('[interactive]');
    });

    test('nearest interactive element reported when clicking empty space', async () => {
      setupDivHit(300, 400);
      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;

      // First evaluate: textContent fetch
      // Second evaluate: nearest interactive found
      (page.evaluate as jest.Mock)
        .mockResolvedValueOnce('')
        .mockResolvedValueOnce({
          tag: 'button',
          text: 'Submit',
          x: 300,
          y: 380,
          dx: 0,
          dy: -20,
        });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [300, 400],
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('Nearest interactive:');
      expect(result.content[0].text).toContain('<button>');
      expect(result.content[0].text).toContain('"Submit"');
      expect(result.content[0].text).toContain('above');
    });

    test('graceful fallback when CDP getNodeForLocation fails', async () => {
      // Make CDP throw on getNodeForLocation to simulate CDP failure
      const cdpClient = mockSessionManager.mockCDPClient;
      const originalSend = cdpClient.send as jest.Mock;
      originalSend.mockImplementationOnce(async (_page: unknown, method: string) => {
        if (method === 'DOM.getNodeForLocation') {
          throw new Error('CDP connection lost');
        }
        return {};
      });

      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        coordinate: [500, 600],
      }) as { content: Array<{ type: string; text: string }>; isError?: boolean };

      // Should still succeed (not an error) and return basic click message
      expect(result.isError).toBeUndefined();
      expect(result.content[0].text).toContain('Clicked at (500, 600)');
      // No hit info since CDP threw
      expect(result.content[0].text).not.toContain('Hit:');
    });

    test('ref-based left_click does NOT include hit detection', async () => {
      setupButtonHit();
      const handler = await getComputerHandler();

      // Generate a ref and set up box model response
      const refId = mockRefIdManager.generateRef(testSessionId, testTargetId, 42, 'button', 'Submit');
      mockSessionManager.mockCDPClient.cdpResponses.set(
        `DOM.scrollIntoViewIfNeeded:${JSON.stringify({ backendNodeId: 42 })}`,
        {}
      );
      mockSessionManager.mockCDPClient.cdpResponses.set(
        `DOM.getBoxModel:${JSON.stringify({ backendNodeId: 42 })}`,
        { model: { content: [90, 190, 110, 190, 110, 210, 90, 210] } }
      );

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        ref: refId,
      }) as { content: Array<{ type: string; text: string }> };

      // Ref-based click returns early before hit detection
      expect(result.content[0].text).toContain(`Clicked element ${refId}`);
      expect(result.content[0].text).not.toContain('Hit:');
    });

    test('ref-based left_click returns STALE_REF on explicit ref_N identity mismatch without relocation', async () => {
      const handler = await getComputerHandler();
      const refId = mockRefIdManager.generateRef(
        testSessionId,
        testTargetId,
        42,
        'button',
        'Submit',
        'button',
        'Submit'
      );

      mockSessionManager.mockCDPClient.cdpResponses.set(
        `DOM.describeNode:${JSON.stringify({ backendNodeId: 42, depth: 1 })}`,
        {
          node: {
            localName: 'button',
            attributes: ['aria-label', 'Cancel'],
            children: [{ nodeType: 3, nodeValue: 'Cancel' }],
          },
        }
      );
      mockRefIdManager.tryRelocateRef.mockResolvedValue({ backendNodeId: 99, newRef: 'ref_2' });

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        ref: refId,
      }) as {
        content: Array<{ type: string; text: string }>;
        isError?: boolean;
        error?: { code: string; ref_id: string };
      };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('STALE_REF');
      expect(result.error).toEqual(expect.objectContaining({ code: 'STALE_REF', ref_id: refId }));
      expect(mockRefIdManager.validateRef).toHaveBeenCalledWith(
        testSessionId,
        testTargetId,
        refId,
        'button',
        'Cancel',
        'Cancel',
      );
      expect(mockRefIdManager.tryRelocateRef).not.toHaveBeenCalled();
    });

    test('ref-based left_click returns STALE_REF when explicit ref_N entry is missing', async () => {
      const handler = await getComputerHandler();

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'left_click',
        ref: 'ref_404',
      }) as {
        content: Array<{ type: string; text: string }>;
        isError?: boolean;
        error?: { code: string; ref_id: string };
      };

      expect(result.isError).toBe(true);
      expect(result.content[0].text).toContain('STALE_REF');
      expect(result.error).toEqual(expect.objectContaining({ code: 'STALE_REF', ref_id: 'ref_404' }));
    });

    test('double_click includes hit element info', async () => {
      const cdpClient = mockSessionManager.mockCDPClient;
      cdpClient.cdpResponses.set(
        `DOM.getNodeForLocation:${JSON.stringify({ x: 200, y: 300, includeUserAgentShadowDOM: false })}`,
        { backendNodeId: 55, nodeId: 15 }
      );
      cdpClient.cdpResponses.set(
        `DOM.describeNode:${JSON.stringify({ backendNodeId: 55 })}`,
        {
          node: {
            localName: 'input',
            attributes: ['type', 'text', 'id', 'search'],
            nodeType: 1,
          },
        }
      );

      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.evaluate as jest.Mock).mockResolvedValueOnce('');

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'double_click',
        coordinate: [200, 300],
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('Double-clicked at (200, 300)');
      expect(result.content[0].text).toContain('Hit:');
      expect(result.content[0].text).toContain('<input');
      expect(result.content[0].text).toContain('[interactive]');
    });

    test('triple_click includes hit element info', async () => {
      const cdpClient = mockSessionManager.mockCDPClient;
      cdpClient.cdpResponses.set(
        `DOM.getNodeForLocation:${JSON.stringify({ x: 250, y: 350, includeUserAgentShadowDOM: false })}`,
        { backendNodeId: 77, nodeId: 25 }
      );
      cdpClient.cdpResponses.set(
        `DOM.describeNode:${JSON.stringify({ backendNodeId: 77 })}`,
        {
          node: {
            localName: 'a',
            attributes: ['href', '/page', 'class', 'nav-link'],
            nodeType: 1,
          },
        }
      );

      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.evaluate as jest.Mock).mockResolvedValueOnce('Page Link');

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'triple_click',
        coordinate: [250, 350],
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('Triple-clicked at (250, 350)');
      expect(result.content[0].text).toContain('Hit:');
      expect(result.content[0].text).toContain('<a');
      expect(result.content[0].text).toContain('[interactive]');
    });

    test('right_click includes hit element info', async () => {
      const cdpClient = mockSessionManager.mockCDPClient;
      cdpClient.cdpResponses.set(
        `DOM.getNodeForLocation:${JSON.stringify({ x: 150, y: 250, includeUserAgentShadowDOM: false })}`,
        { backendNodeId: 33, nodeId: 11 }
      );
      cdpClient.cdpResponses.set(
        `DOM.describeNode:${JSON.stringify({ backendNodeId: 33 })}`,
        {
          node: {
            localName: 'span',
            attributes: ['role', 'button', 'aria-label', 'Close'],
            nodeType: 1,
          },
        }
      );

      const handler = await getComputerHandler();
      const page = (await mockSessionManager.getPage(testSessionId, testTargetId))!;
      (page.evaluate as jest.Mock).mockResolvedValueOnce('');

      const result = await handler(testSessionId, {
        tabId: testTargetId,
        action: 'right_click',
        coordinate: [150, 250],
      }) as { content: Array<{ type: string; text: string }> };

      expect(result.content[0].text).toContain('Right-clicked at (150, 250)');
      expect(result.content[0].text).toContain('Hit:');
      expect(result.content[0].text).toContain('role="button"');
      expect(result.content[0].text).toContain('[interactive]');
    });
  });
});
