// @vitest-environment jsdom

import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
import { describe, expect, test, vi } from 'vitest';

import type { BigFishRuntimeSnapshotResponse } from '../../../packages/shared/src/contracts/bigFish';
import { BigFishRuntimeShell } from './BigFishRuntimeShell';

vi.mock('../ResolvedAssetImage', () => ({
  ResolvedAssetImage: ({
    src,
    alt,
    className,
  }: {
    src?: string | null;
    alt?: string;
    className?: string;
  }) => (src ? <img src={src} alt={alt} className={className} /> : null),
}));

function createRun(
  status: BigFishRuntimeSnapshotResponse['status'],
): BigFishRuntimeSnapshotResponse {
  return {
    runId: 'big-fish-run-1',
    sessionId: 'big-fish-session-1',
    status,
    tick: 18,
    playerLevel: 2,
    winLevel: 5,
    leaderEntityId: null,
    ownedEntities: [],
    wildEntities: [],
    cameraCenter: { x: 0, y: 0 },
    lastInput: { x: 0, y: 0 },
    eventLog: ['己方鱼群已经耗尽'],
    updatedAt: '2026-04-26T12:00:00.000Z',
  };
}

function dispatchPointerEvent(
  target: HTMLElement,
  type: string,
  options: { pointerId: number; clientX: number; clientY: number },
) {
  const event = new Event(type, { bubbles: true, cancelable: true });
  Object.assign(event, options);
  target.dispatchEvent(event);
}

describe('BigFishRuntimeShell', () => {
  test('renders restart and exit actions after a failed run', () => {
    const onBack = vi.fn();
    const onRestart = vi.fn();

    render(
      <BigFishRuntimeShell
        run={createRun('failed')}
        onBack={onBack}
        onRestart={onRestart}
        onSubmitInput={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '重来' }));
    fireEvent.click(screen.getByRole('button', { name: '退出' }));

    expect(screen.getByText('本轮失败')).toBeTruthy();
    expect(onRestart).toHaveBeenCalledTimes(1);
    expect(onBack).toHaveBeenCalledTimes(1);
  });

  test('keeps an exit action after a won run', () => {
    const onBack = vi.fn();

    render(
      <BigFishRuntimeShell
        run={createRun('won')}
        onBack={onBack}
        onSubmitInput={() => {}}
      />,
    );

    expect(screen.getByText('通关完成')).toBeTruthy();
    expect(screen.queryByRole('button', { name: '重来' })).toBeNull();

    fireEvent.click(screen.getByRole('button', { name: '退出' }));
    expect(onBack).toHaveBeenCalledTimes(1);
  });

  test('opens and closes the runtime rule modal', () => {
    render(
      <BigFishRuntimeShell
        run={createRun('running')}
        onBack={() => {}}
        onSubmitInput={() => {}}
      />,
    );

    fireEvent.click(screen.getByRole('button', { name: '查看规则' }));

    expect(screen.getByRole('dialog', { name: '玩法规则' })).toBeTruthy();
    expect(screen.getByText('低级或同级野生实体会被收编。')).toBeTruthy();

    fireEvent.click(screen.getByRole('button', { name: '关闭' }));

    expect(screen.queryByRole('dialog', { name: '玩法规则' })).toBeNull();
  });

  test('keeps moving in the last sampled direction after drag ends', async () => {
    const onSubmitInput = vi.fn();

    const { container } = render(
      <BigFishRuntimeShell
        run={createRun('running')}
        onBack={() => {}}
        onSubmitInput={onSubmitInput}
      />,
    );
    const stage = container.querySelector('.touch-none');
    if (!(stage instanceof HTMLElement)) {
      throw new Error('Missing big fish stage');
    }

    act(() => {
      dispatchPointerEvent(stage, 'pointerdown', {
        pointerId: 1,
        clientX: 100,
        clientY: 100,
      });
    });
    act(() => {
      dispatchPointerEvent(stage, 'pointermove', {
        pointerId: 1,
        clientX: 140,
        clientY: 100,
      });
    });
    await waitFor(() => {
      expect(onSubmitInput).toHaveBeenCalledWith({ x: 1, y: 0 });
    });
    const callCountAfterDrag = onSubmitInput.mock.calls.length;
    act(() => {
      dispatchPointerEvent(stage, 'pointerup', {
        pointerId: 1,
        clientX: 140,
        clientY: 100,
      });
    });

    await waitFor(() => {
      expect(onSubmitInput.mock.calls.length).toBeGreaterThan(callCountAfterDrag);
    });

    expect(onSubmitInput).toHaveBeenLastCalledWith({ x: 1, y: 0 });
  });
});
