/**
 * Dashboard feature UI tests.
 * Tests: page title (REQ-003), copy buttons (REQ-011), auto-link URLs (REQ-014),
 * copy tmux button (REQ-007), watch panel keys (REQ-009).
 * Browser-dependent tests skip gracefully when no probe is connected.
 */

import { describe, it, before, after, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { createTestContext, type TestContext } from '../runner.ts';

describe('Dashboard Features', () => {
  let ctx: TestContext;
  let probeConnected = false;

  before(async () => {
    ctx = await createTestContext();
    try {
      await ctx.waitForProbe(3000);
      probeConnected = true;
    } catch {
      console.log('[dashboard-features] No browser connected — skipping browser tests. Open:', ctx.url);
    }
  });

  after(async () => {
    await ctx.close();
  });

  beforeEach(async () => {
    await ctx.reset();
  });

  // ── REQ-003: Dynamic Page Title ──

  describe('page title (REQ-003)', () => {
    it('initial title is "Dashboard — Agentic Collab"', { skip: !probeConnected ? 'no browser' : false }, async () => {
      // Wait for init to complete
      await ctx.waitFor('[data-agent]');
      const { descriptor } = await ctx.snapshot('dash-page-title-initial');
      assert.equal(descriptor['title'], 'Dashboard — Agentic Collab');
    });

    it('title updates when agent is selected', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      await ctx.click('[data-agent="test-claude"]');
      // Small delay for title update
      await new Promise(r => setTimeout(r, 200));
      const { descriptor } = await ctx.snapshot('dash-page-title-selected');
      assert.equal(descriptor['title'], 'test-claude — Agentic Collab');
    });

    it('title shows unread count when messages arrive', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      // Select one agent, then send a message to a different one
      await ctx.click('[data-agent="test-claude"]');
      await new Promise(r => setTimeout(r, 200));
      await ctx.sendMessage('test-codex', 'hello from codex', { direction: 'from_agent' });
      await new Promise(r => setTimeout(r, 500));
      const { descriptor } = await ctx.snapshot('dash-page-title-unread');
      const title = descriptor['title'] as string;
      assert.ok(title.startsWith('(1)'), `title should start with (1), got: ${title}`);
    });
  });

  // ── REQ-011: Copy Buttons on Messages ──

  describe('copy buttons (REQ-011)', () => {
    it('messages have copy buttons', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      await ctx.click('[data-agent="test-claude"]');
      await ctx.sendMessage('test-claude', 'hello world', { direction: 'from_agent' });
      await new Promise(r => setTimeout(r, 300));
      const { descriptor } = await ctx.snapshot('dash-copy-buttons');
      assert.ok((descriptor['messageCopyButtons'] as number) >= 1, 'should have at least one copy button');
    });

    it('copy buttons exist for each message', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      await ctx.click('[data-agent="test-claude"]');
      await ctx.sendMessage('test-claude', 'message one', { direction: 'from_agent' });
      await ctx.sendMessage('test-claude', 'message two', { direction: 'from_agent' });
      await new Promise(r => setTimeout(r, 300));
      const copyCount = await ctx.count('.msg-copy');
      const msgCount = await ctx.count('.msg');
      assert.equal(copyCount, msgCount, 'each message should have a copy button');
    });
  });

  // ── REQ-014: Auto-Link URLs ──

  describe('auto-link URLs (REQ-014)', () => {
    it('bare URLs in messages become clickable links', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      await ctx.click('[data-agent="test-claude"]');
      await ctx.sendMessage('test-claude', 'check https://example.com for details', { direction: 'from_agent' });
      await new Promise(r => setTimeout(r, 300));
      const { descriptor } = await ctx.snapshot('dash-auto-link');
      const links = descriptor['messageLinks'] as Array<{ href: string; text: string }>;
      const exampleLink = links.find(l => l.href === 'https://example.com');
      assert.ok(exampleLink, 'should have a clickable link for https://example.com');
    });

    it('markdown links are not doubled', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      await ctx.click('[data-agent="test-claude"]');
      await ctx.sendMessage('test-claude', '[click here](https://example.com)', { direction: 'from_agent' });
      await new Promise(r => setTimeout(r, 300));
      const linkCount = await ctx.count('.msg-body a[href="https://example.com"]');
      assert.equal(linkCount, 1, 'should have exactly one link, not doubled');
    });
  });

  // ── REQ-007: Copy tmux Button ──

  describe('copy tmux button (REQ-007)', () => {
    it('active agents show copy tmux button with session name', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      const { descriptor } = await ctx.snapshot('dash-tmux-copy');
      const cards = descriptor['agentCards'] as Array<Record<string, unknown>>;
      // test-claude is active with tmuxSession: 'agent-test-claude'
      const claude = cards.find(c => c['name'] === 'test-claude');
      assert.ok(claude, 'test-claude card should exist');
      assert.ok(claude['hasTmuxCopy'], 'active agent should have tmux copy button');
      assert.equal(claude['tmuxCommand'], 'tmux attach -t agent-test-claude');
    });

    it('failed agents do not show copy tmux button', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      const { descriptor } = await ctx.snapshot('dash-tmux-no-failed');
      const cards = descriptor['agentCards'] as Array<Record<string, unknown>>;
      const failed = cards.find(c => c['name'] === 'test-failed');
      assert.ok(failed, 'test-failed card should exist');
      assert.ok(!failed['hasTmuxCopy'], 'failed agent should not have tmux copy button');
    });
  });

  // ── Progressive Message Loading (Performance Regression Guard) ──

  describe('progressive message loading', () => {
    it('renders at most 30 messages on agent switch', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      // Send 50 messages to test-claude
      for (let i = 0; i < 50; i++) {
        await ctx.sendMessage('test-claude', `message ${i}`, { direction: 'from_agent' });
      }
      // Select the agent — should only render last 30
      await ctx.click('[data-agent="test-claude"]');
      await new Promise(r => setTimeout(r, 300));
      const msgCount = await ctx.count('.msg');
      assert.ok(msgCount <= 35, `should render at most ~30 messages initially, got ${msgCount}`);
      assert.ok(msgCount >= 25, `should render at least 25 messages, got ${msgCount}`);
    });

    it('new message appends without full re-render', { skip: !probeConnected ? 'no browser' : false }, async () => {
      await ctx.waitFor('[data-agent]');
      await ctx.click('[data-agent="test-claude"]');
      await new Promise(r => setTimeout(r, 200));
      const before = await ctx.count('.msg');
      await ctx.sendMessage('test-claude', 'one more message', { direction: 'from_agent' });
      await new Promise(r => setTimeout(r, 300));
      const after = await ctx.count('.msg');
      assert.equal(after, before + 1, 'should append exactly one message');
    });
  });

  // ── REQ-009: Watch Panel Keys ──
  // These are server-side API tests (no browser needed)

  describe('watch panel key API (REQ-009)', () => {
    it('keys endpoint accepts S-Tab', async () => {
      // The mock server should accept the keys endpoint
      const res = await fetch(`${ctx.baseUrl}/api/agents/test-claude/keys`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ keys: 'S-Tab' }),
      });
      // Mock server returns 200 for known endpoints (or 404 if not mocked)
      // Either way, the key name should be accepted by the request parser
      assert.ok(res.status === 200 || res.status === 404, `unexpected status: ${res.status}`);
    });

    it('keys endpoint accepts C-c', async () => {
      const res = await fetch(`${ctx.baseUrl}/api/agents/test-claude/keys`, {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({ keys: 'C-c' }),
      });
      assert.ok(res.status === 200 || res.status === 404, `unexpected status: ${res.status}`);
    });
  });
});
