// @vitest-environment jsdom

import { act, cleanup, fireEvent, render, screen } from '@testing-library/react';
import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
import { SelectionQuoteActionSurface } from '../../components/selection/SelectionQuoteActionSurface';
import { useStore } from '../../stores';

describe('SelectionQuoteActionSurface', () => {
  beforeEach(() => {
    window.t = ((key: string) => key) as typeof window.t;
    vi.useFakeTimers();
    useStore.getState().clearQuoteCandidate();
    useStore.getState().clearQuotedSelections();
  });

  afterEach(() => {
    cleanup();
    vi.useRealTimers();
  });

  it('adds the current selection candidate as an independent quote chip source', () => {
    useStore.getState().setQuoteCandidate({
      text: '第一段引用',
      sourceTitle: 'Assistant message',
      sourceKind: 'chat',
      sourceSessionPath: '/session/a.jsonl',
      sourceMessageId: 'assistant-1',
      sourceRole: 'assistant',
      charCount: 5,
      anchorRect: { left: 100, right: 180, top: 120, bottom: 140, width: 80, height: 20 },
    });
    render(<SelectionQuoteActionSurface />);

    fireEvent.click(screen.getByRole('button', { name: 'selection.quoteToChat' }));

    expect(useStore.getState().quotedSelections).toHaveLength(1);
    expect(useStore.getState().quotedSelections[0]).toMatchObject({ text: '第一段引用' });
    expect(useStore.getState().quoteCandidate).toBeNull();
  });

  it('keeps the readable action label visible without rendering a duplicate tooltip', () => {
    useStore.getState().setQuoteCandidate({
      text: '第一段引用',
      sourceTitle: 'Assistant message',
      sourceKind: 'chat',
      charCount: 5,
      anchorRect: { left: 100, right: 180, top: 120, bottom: 140, width: 80, height: 20 },
    });
    render(<SelectionQuoteActionSurface />);

    const button = screen.getByRole('button', { name: 'selection.quoteToChat' });
    expect(button.textContent).toContain('selection.quoteToChat');
    fireEvent.mouseEnter(button);
    expect(screen.queryByRole('tooltip')).toBeNull();

    act(() => { vi.advanceTimersByTime(500); });
    expect(screen.queryByRole('tooltip')).toBeNull();
  });

  it('renders a labeled SVG quote action above the selection anchor with breathing room', () => {
    useStore.getState().setQuoteCandidate({
      text: '第一段引用',
      sourceTitle: 'Assistant message',
      sourceKind: 'chat',
      charCount: 5,
      anchorRect: { left: 100, right: 180, top: 120, bottom: 140, width: 80, height: 20 },
    });
    render(<SelectionQuoteActionSurface />);

    const button = screen.getByRole('button', { name: 'selection.quoteToChat' });
    const surface = button.closest('[data-selection-ignore="true"]') as HTMLElement;
    const icon = button.querySelector('svg');

    expect(surface.getAttribute('data-selection-quote-action')).toBe('true');
    expect(icon).not.toBeNull();
    expect(icon?.getAttribute('fill')).toBe('currentColor');
    expect(icon?.hasAttribute('stroke')).toBe(false);
    expect(button.textContent).toContain('selection.quoteToChat');
    expect(surface.style.left).toBe('94px');
    expect(surface.style.top).toBe('76px');
  });

  it('follows the live native selection rect when the transcript scrolls', () => {
    let liveRect = { left: 100, right: 180, top: 120, bottom: 140, width: 80, height: 20 };
    const getSelection = vi.spyOn(window, 'getSelection').mockReturnValue({
      rangeCount: 1,
      toString: () => '第一段引用',
      getRangeAt: () => ({
        getBoundingClientRect: () => liveRect,
      }),
    } as unknown as Selection);
    const requestAnimationFrame = vi
      .spyOn(window, 'requestAnimationFrame')
      .mockImplementation((callback: FrameRequestCallback) => {
        callback(0);
        return 1;
      });
    const cancelAnimationFrame = vi.spyOn(window, 'cancelAnimationFrame').mockImplementation(() => {});

    useStore.getState().setQuoteCandidate({
      text: '第一段引用',
      sourceTitle: 'Assistant message',
      sourceKind: 'chat',
      charCount: 5,
      anchorRect: { left: 100, right: 180, top: 120, bottom: 140, width: 80, height: 20 },
    });
    render(<SelectionQuoteActionSurface />);

    const surface = screen.getByRole('button', { name: 'selection.quoteToChat' }).closest('[data-selection-ignore="true"]') as HTMLElement;
    expect(surface.style.top).toBe('76px');

    liveRect = { left: 100, right: 180, top: 70, bottom: 90, width: 80, height: 20 };
    act(() => {
      document.dispatchEvent(new Event('scroll'));
    });

    expect(surface.style.top).toBe('26px');

    getSelection.mockRestore();
    requestAnimationFrame.mockRestore();
    cancelAnimationFrame.mockRestore();
  });

  it('keeps a CodeMirror candidate anchored to the editor-owned rect', () => {
    const getSelection = vi.spyOn(window, 'getSelection').mockReturnValue({
      rangeCount: 1,
      toString: () => '第一段引用',
      getRangeAt: () => ({
        getBoundingClientRect: () => ({ left: 100, right: 180, top: 360, bottom: 380, width: 80, height: 20 }),
      }),
    } as unknown as Selection);

    useStore.getState().setQuoteCandidate({
      text: '第一段引用',
      sourceTitle: 'note.md',
      sourceKind: 'preview',
      sourceFilePath: '/notes/note.md',
      selectionAnchorKind: 'codemirror',
      charCount: 5,
      anchorRect: { left: 100, right: 180, top: 120, bottom: 140, width: 80, height: 20 },
    });
    render(<SelectionQuoteActionSurface />);

    const surface = screen.getByRole('button', { name: 'selection.quoteToChat' }).closest('[data-selection-ignore="true"]') as HTMLElement;
    expect(surface.style.left).toBe('94px');
    expect(surface.style.top).toBe('76px');

    getSelection.mockRestore();
  });
});
