import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { beforeEach, describe, expect, it, vi } from 'vitest';

const navigationState = vi.hoisted(() => ({
  searchParams: new URLSearchParams(),
}));

vi.mock('next/navigation', () => ({
  useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
  useSearchParams: () => navigationState.searchParams,
}));

vi.mock('../../../lib/api', () => ({
  api: {
    get: vi.fn(),
  },
}));

vi.mock('../../../components/RecallStages', () => ({
  default: () => <div data-recall-stages="true" />,
}));

vi.mock('../../../components/UpdaterDisplay', () => ({
  ChannelAvatar: () => <span data-channel-avatar="true" />,
}));

vi.mock('../../../components/clientTypeMeta', () => ({
  KNOWN_CLIENT_TYPES: ['claudecode', 'codex'],
  clientTypeLabel: (value: unknown) => String(value || 'Legacy'),
}));

vi.mock('../../../lib/i18n', () => ({
  useT: () => ({ t: (key: string) => key }),
}));

vi.mock('../../../components/ui', () => ({
  PageCanvas: ({ children }: { children: React.ReactNode }) => <main>{children}</main>,
  PageTitle: ({ title, right }: { title: React.ReactNode; right?: React.ReactNode }) => <header>{title}{right}</header>,
  Card: ({ children }: { children: React.ReactNode }) => <section>{children}</section>,
  Section: ({ children, title }: { children: React.ReactNode; title: React.ReactNode }) => <section>{title}{children}</section>,
  Button: ({ children }: { children: React.ReactNode }) => <button>{children}</button>,
  TextButton: ({ children, ...props }: React.ButtonHTMLAttributes<HTMLButtonElement>) => <button data-text-button="true" {...props}>{children}</button>,
  Badge: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
  Notice: ({ children }: { children: React.ReactNode }) => <aside>{children}</aside>,
  StatCard: ({ label, value }: { label: React.ReactNode; value: React.ReactNode }) => <div data-stat-card="true">{label}{value}</div>,
  Disclosure: ({ children, trigger }: { children: React.ReactNode; trigger: React.ReactNode }) => <div>{trigger}{children}</div>,
  SegmentedTabs: () => <div data-segmented-tabs="true" />,
  AppInput: (props: React.InputHTMLAttributes<HTMLInputElement>) => <input data-app-input="true" {...props} />,
  FilterNumberField: (props: Record<string, unknown>) => <input data-filter-number-field="true" {...props} />,
  AppSelect: ({ options, placeholder }: { options: Array<{ value: string; label: React.ReactNode }>; placeholder?: React.ReactNode }) => (
    <select data-app-select="true">
      {placeholder && <option value="">{placeholder}</option>}
      {options.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
    </select>
  ),
  FilterPill: ({ children, as = 'div', active }: { children: React.ReactNode; as?: 'div' | 'label'; active?: boolean }) => (
    as === 'label'
      ? <label data-filter-pill="true" data-filter-pill-as="label" data-active={active ? 'true' : 'false'}>{children}</label>
      : <div data-filter-pill="true" data-filter-pill-as="div" data-active={active ? 'true' : 'false'}>{children}</div>
  ),
  Table: ({ columns, rows, empty }: { columns: Array<{ key: string; label: React.ReactNode; className?: string; render?: (value: unknown, row: Record<string, unknown>) => React.ReactNode }>; rows?: Record<string, unknown>[]; empty?: string }) => {
    const keys = columns.map((column) => column.key);
    if (new Set(keys).size !== keys.length) throw new Error(`duplicate column keys: ${keys.join(',')}`);
    return (
      <table>
        <thead><tr>{columns.map((column) => <th key={column.key} className={column.className}>{column.label}</th>)}</tr></thead>
        <tbody>{rows?.length ? rows.map((row, index) => <tr key={index}>{columns.map((column) => <td key={column.key} className={column.className}>{column.render ? column.render(row[column.key], row) : String(row[column.key] ?? '—')}</td>)}</tr>) : <tr><td>{empty}</td></tr>}</tbody>
      </table>
    );
  },
  surfaceCardClassName: 'rounded-2xl border border-separator-thin bg-bg-elevated shadow-card',
  fmt: (value: unknown) => value == null ? '—' : String(value),
  trunc: (value: unknown) => String(value ?? ''),
  asNumber: (value: unknown, fallback = 0) => Number(value) || fallback,
}));

import RecallDrilldown, { formatRecallDurationMs, resolveFilterNumberDisplayValue } from '../RecallDrilldown';

describe('RecallDrilldown overview', () => {
  beforeEach(() => {
    navigationState.searchParams = new URLSearchParams();
  });

  it('renders overview as the source table with memory event columns but without detail cards', () => {
    const html = renderToStaticMarkup(<RecallDrilldown />);

    const overviewSection = html.slice(html.indexOf('Overview'));
    expect(overviewSection).toContain('<table');
    expect(overviewSection).toContain('Memory created');
    expect(overviewSection).toContain('Memory updated');
    expect(overviewSection).toContain('Memory deleted');
    expect(overviewSection).not.toContain('Current threshold');
    expect(overviewSection).not.toContain('Current</div>');
    expect(overviewSection).not.toContain('Average shown');
    expect(overviewSection).not.toContain('Average used');
    expect(overviewSection).not.toContain('Basis');
    expect(overviewSection).not.toContain('Shown candidates');
    expect(html).not.toContain('Display threshold analysis');
  });

  it('renders only the day and source filters in the page header', () => {
    const html = renderToStaticMarkup(<RecallDrilldown />);

    expect((html.match(/data-filter-number-field="true"/g) || []).length).toBe(1);
    expect((html.match(/data-app-select="true"/g) || []).length).toBe(1);
    expect((html.match(/data-filter-pill="true"/g) || []).length).toBe(2);
    expect((html.match(/data-filter-pill-as="label"/g) || []).length).toBe(2);
    expect(html).toContain('Days');
    expect(html).toContain('All sources');
    expect(html).not.toContain('Adjust filters');
    expect(html).not.toContain('Limit');
    expect(html).not.toContain('Query text');
    expect(html).not.toContain('Node URI');
  });

  it('does not render appendix controls or content', () => {
    const html = renderToStaticMarkup(<RecallDrilldown />);

    expect(html).not.toContain('Show appendix');
    expect(html).not.toContain('Hide appendix');
    expect(html).not.toContain('By path');
    expect(html).not.toContain('By view');
    expect(html).not.toContain('Noisy nodes');
    expect(html).not.toContain('Raw events');
  });

  it('hides overview and threshold cards when a query detail is open', () => {
    navigationState.searchParams = new URLSearchParams('query_id=q1');
    const html = renderToStaticMarkup(<RecallDrilldown />);

    expect(html).not.toContain('data-stat-card="true"');
    expect(html).not.toContain('Overview');
    expect(html).not.toContain('Display threshold analysis');
    expect(html).toContain('Days');
    expect(html).toContain('All sources');
    expect(html).toContain('Refresh');
    expect(html).not.toContain('Loading…');
  });

  it('keeps the refresh button label stable while the icon spins during loading', () => {
    const html = renderToStaticMarkup(<RecallDrilldown />);

    expect(html).toContain('Refresh');
    expect(html).toContain('animate-spin');
    expect(html).not.toContain('Loading…');
  });

  it('keeps a changed number visible while URL params catch up', () => {
    expect(resolveFilterNumberDisplayValue(14, 15)).toBe(15);
    expect(resolveFilterNumberDisplayValue(15, null)).toBe(15);
  });

  it('adds duration to the recent queries table', () => {
    const html = renderToStaticMarkup(<RecallDrilldown />);

    expect(html).toContain('Duration');
    expect(html).toContain('<th class="text-center">Duration</th>');
    expect(formatRecallDurationMs(850)).toBe('850ms');
    expect(formatRecallDurationMs(1530)).toBe('1.5s');
  });

  it('caps the recent query text column at half the table width', () => {
    const html = renderToStaticMarkup(<RecallDrilldown />);

    expect(html).toContain('<th class="w-1/2 max-w-[50%]">Query</th>');
  });
});
