/* @vitest-environment jsdom */

import { render, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import type { ContextType } from 'react';
import { afterEach, describe, expect, it, vi } from 'vitest';

import { ApiClientError } from '../../services/apiClient';
import { AuthUiContext } from '../auth/AuthUiContext';
import { ProjectGalleryView } from './ProjectGalleryView';

const listEditorProjectsMock = vi.hoisted(() => vi.fn());
const createEditorProjectMock = vi.hoisted(() => vi.fn());
const renameEditorProjectMock = vi.hoisted(() => vi.fn());
const deleteEditorProjectMock = vi.hoisted(() => vi.fn());

type AuthValue = NonNullable<ContextType<typeof AuthUiContext>>;

function createAuthValue(overrides: Partial<AuthValue> = {}): AuthValue {
  return {
    user: null,
    canAccessProtectedData: false,
    openLoginModal: vi.fn(),
    requireAuth: vi.fn((action: () => void) => action()),
    openSettingsModal: vi.fn(),
    openAccountModal: vi.fn(),
    setCurrentUser: vi.fn(),
    logout: vi.fn(),
    musicVolume: 0.5,
    setMusicVolume: vi.fn(),
    platformTheme: 'light' as const,
    setPlatformTheme: vi.fn(),
    isHydratingSettings: false,
    isPersistingSettings: false,
    settingsError: null,
    ...overrides,
  };
}

vi.mock('../../services/image-editor/editorProjectClient', () => ({
  listEditorProjects: listEditorProjectsMock,
  createEditorProject: createEditorProjectMock,
  renameEditorProject: renameEditorProjectMock,
  deleteEditorProject: deleteEditorProjectMock,
}));

const projectItems = [
  {
    projectId: 'editor-project-1',
    title: '角色设定板',
    viewport: { x: 0, y: 0, scale: 1 },
    layers: [{ layerId: 'layer-1', resourceId: 'resource-1' }],
    resources: [
      {
        resourceId: 'resource-1',
        projectId: 'editor-project-1',
        imageSrc: 'data:image/png;base64,one',
        width: 1024,
        height: 1024,
        sourceType: 'uploaded',
      },
    ],
    updatedAt: '2026-06-12T08:00:00.000Z',
  },
  {
    projectId: 'editor-project-2',
    title: '场景草图',
    viewport: { x: 0, y: 0, scale: 1 },
    layers: [],
    resources: [],
    updatedAt: '2026-06-12T07:00:00.000Z',
  },
];

describe('ProjectGalleryView', () => {
  afterEach(() => {
    listEditorProjectsMock.mockReset();
    createEditorProjectMock.mockReset();
    renameEditorProjectMock.mockReset();
    deleteEditorProjectMock.mockReset();
  });

  it('opens a project from the gallery card', async () => {
    const onOpenProject = vi.fn();
    listEditorProjectsMock.mockResolvedValueOnce(projectItems);
    const user = userEvent.setup();

    render(<ProjectGalleryView onOpenProject={onOpenProject} />);

    await screen.findByText('角色设定板');
    await user.click(screen.getByRole('button', { name: '打开项目角色设定板' }));

    expect(onOpenProject).toHaveBeenCalledWith('editor-project-1');
  });

  it('uses canvas-center layer composition as the project cover', async () => {
    listEditorProjectsMock.mockResolvedValueOnce([
      {
        projectId: 'editor-project-cover',
        title: '封面项目',
        viewport: { x: 120, y: -60, scale: 0.8 },
        layers: [
          {
            layerId: 'layer-cover-back',
            resourceId: 'resource-back',
            title: '背景图',
            x: 200,
            y: 160,
            width: 300,
            height: 180,
            zIndex: 1,
          },
          {
            layerId: 'layer-cover-front',
            resourceId: 'resource-front',
            title: '前景图',
            x: 420,
            y: 260,
            width: 160,
            height: 120,
            zIndex: 2,
          },
        ],
        resources: [
          {
            resourceId: 'resource-front',
            projectId: 'editor-project-cover',
            imageSrc: 'data:image/png;base64,front',
            width: 160,
            height: 120,
            sourceType: 'uploaded',
          },
          {
            resourceId: 'resource-back',
            projectId: 'editor-project-cover',
            imageSrc: 'data:image/png;base64,back',
            width: 300,
            height: 180,
            sourceType: 'uploaded',
          },
        ],
        updatedAt: '2026-06-12T08:00:00.000Z',
      },
    ]);

    render(<ProjectGalleryView onOpenProject={vi.fn()} />);

    await screen.findByText('封面项目');
    const cover = document.querySelector('.project-gallery__canvas-cover');
    const coverLayers = document.querySelectorAll(
      '.project-gallery__canvas-cover-layer',
    );

    expect(cover).toBeTruthy();
    expect(coverLayers).toHaveLength(2);
    expect(
      document.querySelector('.project-gallery__preview img')?.getAttribute('src'),
    ).toBe('data:image/png;base64,back');
    expect((coverLayers[0] as HTMLElement).style.zIndex).toBe('1');
    expect((coverLayers[1] as HTMLElement).style.zIndex).toBe('2');
  });

  it('opens the login modal when project list loading is unauthorized', async () => {
    const openLoginModal = vi.fn();
    listEditorProjectsMock.mockRejectedValueOnce(
      new ApiClientError({
        message: '未授权访问',
        status: 401,
        code: 'UNAUTHORIZED',
      }),
    );

    render(
      <AuthUiContext.Provider value={createAuthValue({ openLoginModal })}>
        <ProjectGalleryView onOpenProject={vi.fn()} />
      </AuthUiContext.Provider>,
    );

    await waitFor(() => {
      expect(openLoginModal).toHaveBeenCalledTimes(1);
    });
    expect(openLoginModal).toHaveBeenCalledWith(expect.any(Function));
    expect(screen.queryByRole('alert')).toBeNull();
  });

  it('requires login before opening a project card while logged out', async () => {
    const onOpenProject = vi.fn();
    const requireAuth = vi.fn();
    listEditorProjectsMock.mockResolvedValueOnce(projectItems);
    const user = userEvent.setup();

    render(
      <AuthUiContext.Provider value={createAuthValue({ requireAuth })}>
        <ProjectGalleryView onOpenProject={onOpenProject} />
      </AuthUiContext.Provider>,
    );

    await screen.findByText('角色设定板');
    await user.click(screen.getByRole('button', { name: '打开项目角色设定板' }));

    expect(requireAuth).toHaveBeenCalledWith(expect.any(Function));
    expect(onOpenProject).not.toHaveBeenCalled();
  });

  it('renders project loading errors through the shared status message', async () => {
    listEditorProjectsMock.mockRejectedValueOnce(new Error('读取项目失败'));

    render(<ProjectGalleryView onOpenProject={vi.fn()} />);

    const alert = await screen.findByRole('alert');

    expect(alert.textContent).toContain('读取项目失败');
    expect(alert.className).toContain('platform-status-message');
    expect(alert.className).toContain('project-gallery__error');
  });

  it('renames and deletes a project from the hover menu', async () => {
    listEditorProjectsMock.mockResolvedValueOnce(projectItems);
    renameEditorProjectMock.mockResolvedValueOnce({
      ...projectItems[0],
      title: '新角色设定板',
    });
    deleteEditorProjectMock.mockResolvedValueOnce('editor-project-2');
    const user = userEvent.setup();

    render(<ProjectGalleryView onOpenProject={vi.fn()} />);

    await screen.findByText('角色设定板');
    await user.click(screen.getByRole('button', { name: '打开项目角色设定板菜单' }));
    await user.click(screen.getByRole('menuitem', { name: /重命名/u }));
    await user.clear(screen.getByLabelText('项目名称'));
    await user.type(screen.getByLabelText('项目名称'), '新角色设定板');
    await user.click(screen.getByRole('button', { name: '保存' }));

    expect(renameEditorProjectMock).toHaveBeenCalledWith(
      'editor-project-1',
      '新角色设定板',
    );
    await screen.findByText('新角色设定板');

    await user.click(screen.getByRole('button', { name: '打开项目场景草图菜单' }));
    await user.click(screen.getByRole('menuitem', { name: /删除/u }));

    expect(deleteEditorProjectMock).toHaveBeenCalledWith('editor-project-2');
    await waitFor(() => {
      expect(screen.queryByText('场景草图')).toBeNull();
    });
  });

  it('supports batch selection actions from the bottom toolbar', async () => {
    listEditorProjectsMock.mockResolvedValueOnce(projectItems);
    deleteEditorProjectMock.mockResolvedValue('deleted');
    const user = userEvent.setup();

    render(<ProjectGalleryView onOpenProject={vi.fn()} />);

    await screen.findByText('角色设定板');
    await user.click(screen.getByRole('button', { name: '选择' }));
    const toolbar = screen.getByRole('toolbar', { name: '批量操作' });

    await user.click(within(toolbar).getByRole('button', { name: '全选' }));

    expect(
      within(toolbar).getByRole('button', { name: '取消全选 · 已选 2' }),
    ).toBeTruthy();

    await user.click(within(toolbar).getByRole('button', { name: '删除' }));

    expect(deleteEditorProjectMock).toHaveBeenCalledWith('editor-project-1');
    expect(deleteEditorProjectMock).toHaveBeenCalledWith('editor-project-2');
  });

  it('renders the empty project action with shared empty state chrome', async () => {
    const onOpenProject = vi.fn();
    listEditorProjectsMock.mockResolvedValueOnce([]);
    createEditorProjectMock.mockResolvedValueOnce({
      ...projectItems[0],
      projectId: 'editor-project-new',
    });
    const user = userEvent.setup();

    render(<ProjectGalleryView onOpenProject={onOpenProject} />);

    const newProjectButton = await screen.findByRole('button', {
      name: '新建项目',
    });

    expect(newProjectButton.className).toContain('platform-empty-state');
    expect(newProjectButton.className).toContain('project-gallery__new-card');

    await user.click(newProjectButton);

    expect(createEditorProjectMock).toHaveBeenCalledTimes(1);
    expect(onOpenProject).toHaveBeenCalledWith('editor-project-new');
  });
});
