/* @vitest-environment jsdom */

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

import type { PuzzleAgentSessionSnapshot } from '../../../../packages/shared/src/contracts/puzzleAgentSession';
import { puzzleAssetClient } from '../../../services/puzzle-works/puzzleAssetClient';
import { PuzzleCreationWorkspace } from './PuzzleCreationWorkspace';

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

vi.mock('../../../services/puzzle-works/puzzleAssetClient', () => ({
  puzzleAssetClient: {
    listHistoryAssets: vi.fn(),
    uploadReferenceImage: vi.fn(),
  },
}));

const baseSession: PuzzleAgentSessionSnapshot = {
  sessionId: 'puzzle-session-1',
  currentTurn: 3,
  progressPercent: 62,
  stage: 'collecting_anchors',
  anchorPack: {
    themePromise: {
      key: 'themePromise',
      label: '题材承诺',
      value: '雾港遗迹拼图',
      status: 'confirmed',
    },
    visualSubject: {
      key: 'visualSubject',
      label: '画面主体',
      value: '潮雾中的灯塔与断桥',
      status: 'confirmed',
    },
    visualMood: {
      key: 'visualMood',
      label: '视觉气质',
      value: '',
      status: 'missing',
    },
    compositionHooks: {
      key: 'compositionHooks',
      label: '拼图记忆点',
      value: '',
      status: 'missing',
    },
    tagsAndForbidden: {
      key: 'tagsAndForbidden',
      label: '标签与禁忌',
      value: '',
      status: 'missing',
    },
  },
  draft: null,
  messages: [
    {
      id: 'message-1',
      role: 'assistant',
      kind: 'chat',
      text: '旧会话消息不再渲染为聊天入口。',
      createdAt: '2026-04-24T10:00:00.000Z',
    },
  ],
  lastAssistantReply: '旧会话消息不再渲染为聊天入口。',
  publishedProfileId: null,
  suggestedActions: [],
  resultPreview: null,
  updatedAt: '2026-04-24T10:00:00.000Z',
};

beforeEach(() => {
  if (!Element.prototype.scrollIntoView) {
    Element.prototype.scrollIntoView = () => {};
  }
  vi.mocked(puzzleAssetClient.uploadReferenceImage).mockImplementation(
    async ({ file }) => ({
      assetObjectId: `asset-reference-${file.name}`,
      assetKind: 'puzzle_cover_image',
      objectKey: `generated-puzzle-assets/reference/${file.name}`,
      imageSrc: `/generated-puzzle-assets/reference/${file.name}`,
      ownerUserId: 'user-1',
      ownerLabel: '账号 user-1',
      profileId: null,
      entityId: null,
      createdAt: '1713686400.000000Z',
      updatedAt: '1713686400.000000Z',
    }),
  );
});

afterEach(() => {
  vi.useRealTimers();
  vi.unstubAllGlobals();
  vi.restoreAllMocks();
  vi.clearAllMocks();
});

function stubReferenceImageUpload(dataUrl: string, width = 512, height = 512) {
  class MockFileReader {
    result: string | null = null;
    onload: null | (() => void) = null;
    onerror: null | (() => void) = null;

    readAsDataURL() {
      this.result = dataUrl;
      this.onload?.();
    }
  }

  class MockImage {
    onload: null | (() => void) = null;
    onerror: null | (() => void) = null;
    naturalWidth = width;
    naturalHeight = height;
    width = width;
    height = height;

    set src(_value: string) {
      this.onload?.();
    }
  }

  vi.stubGlobal('FileReader', MockFileReader as unknown as typeof FileReader);
  vi.stubGlobal('Image', MockImage as unknown as typeof Image);
}

function stubCanvas(dataUrl: string, drawImage = vi.fn()) {
  const originalCreateElement = document.createElement.bind(document);
  vi.spyOn(document, 'createElement').mockImplementation((tagName) => {
    if (tagName !== 'canvas') {
      return originalCreateElement(tagName);
    }
    return {
      width: 0,
      height: 0,
      getContext: () => ({
        drawImage,
        fillRect: vi.fn(),
        fillStyle: '',
        imageSmoothingEnabled: false,
        imageSmoothingQuality: 'low',
      }),
      toDataURL: vi.fn(() => dataUrl),
    } as unknown as HTMLCanvasElement;
  });
  return drawImage;
}

function confirmPuzzlePointCost() {
  const confirmDialog = screen.getByRole('dialog', {
    name: '确认消耗泥点',
  });
  expect(within(confirmDialog).getByText('消耗 2 泥点')).toBeTruthy();
  fireEvent.click(within(confirmDialog).getByRole('button', { name: '确定' }));
}

function confirmPuzzlePointCostText(text: string) {
  const confirmDialog = screen.getByRole('dialog', {
    name: '确认消耗泥点',
  });
  expect(within(confirmDialog).getByText(text)).toBeTruthy();
}

test('puzzle workspace submits the work form instead of agent chat', () => {
  const onCreateFromForm = vi.fn();

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  expect(screen.queryByLabelText('作品名称')).toBeNull();
  expect(screen.queryByLabelText('作品描述')).toBeNull();
  expect(screen.getByText('拼图')).toBeTruthy();
  expect(screen.queryByText('try')).toBeNull();
  expect(screen.queryByText('Template')).toBeNull();

  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '一只猫在雨夜灯牌下回头。' },
  });
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));

  expect(onCreateFromForm).not.toHaveBeenCalled();
  confirmPuzzlePointCost();

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: '一只猫在雨夜灯牌下回头。',
    workDescription: '一只猫在雨夜灯牌下回头。',
    pictureDescription: '一只猫在雨夜灯牌下回头。',
    referenceImageSrc: null,
    referenceImageSrcs: [],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
  });
  expect(screen.getByText('消耗2泥点')).toBeTruthy();
  expect(screen.queryByRole('button', { name: '补充剩余设定' })).toBeNull();
  expect(screen.queryByText('旧会话消息不再渲染为聊天入口。')).toBeNull();
});

test('puzzle workspace shows configured mud point cost', () => {
  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={() => {}}
      mudPointCost={8}
    />,
  );

  expect(screen.getByText('消耗8泥点')).toBeTruthy();
  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '一座雨后的陶泥小镇。' },
  });
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));

  confirmPuzzlePointCostText('消耗 8 泥点');
});

test('puzzle workspace can defer visible chrome to the unified creation page', () => {
  const { container } = render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={() => {}}
      unifiedChrome
      title={null}
    />,
  );

  const workspace = container.querySelector('.puzzle-agent-workspace');
  const imagePanel = container.querySelector('.creative-image-input-panel');
  expect(workspace?.getAttribute('data-unified-chrome')).toBe('true');
  expect(workspace?.className).toContain('max-w-none');
  expect(workspace?.className).not.toContain('h-full');
  expect(workspace?.className).not.toContain('overflow-hidden');
  expect(workspace?.className).not.toContain('platform-remap-surface');
  expect(imagePanel?.className).toContain('flex-none');
  expect(imagePanel?.className).not.toContain('flex-1');
  expect(screen.queryByRole('heading', { name: '拼图' })).toBeNull();
  expect(screen.getByLabelText('画面描述')).toBeTruthy();
});

test('puzzle workspace keeps the reference image upload as a primary panel', () => {
  const onCreateFromForm = vi.fn();
  const { container } = render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  const uploadInput = screen.getByLabelText('上传拼图图片', {
    selector: 'input',
  });
  const uploadCard = uploadInput.closest('.puzzle-image-upload-card');
  expect(uploadCard).not.toBeNull();
  expect(uploadCard?.closest('.platform-subpanel')).toBeNull();
  expect(container.querySelector('.puzzle-image-upload-card')).toBeTruthy();
  expect(container.querySelector('.puzzle-creation-form-body')?.className).toContain(
    'overflow-hidden',
  );
  expect(container.querySelector('.puzzle-image-field')?.className).toContain(
    'flex-1',
  );

  expect(screen.getByText('拼图画面')).toBeTruthy();
  expect(
    screen.queryByText('若没有合适的图片可以通过填写画面描述生成画面'),
  ).toBeNull();
  expect(
    screen
      .getByText('上传图片/填写画面描述')
      .closest('.puzzle-image-upload-card'),
  ).toBeTruthy();
  expect(screen.queryByRole('switch', { name: 'AI重绘' })).toBeNull();
  expect(screen.queryByLabelText('拼图创作模板')).toBeNull();
  expect(
    (screen.getByLabelText('画面描述') as HTMLTextAreaElement).value,
  ).toBe('');
  expect(
    (screen.getByLabelText('画面描述') as HTMLTextAreaElement).placeholder,
  ).toBe('');
  expect(screen.queryByText(/一只猫在雨夜灯牌下回头/u)).toBeNull();
  expect(screen.getByLabelText('画面描述').className).toContain('h-[6rem]');
  expect(uploadCard?.className).toContain('aspect-square');
  expect(uploadCard?.className).toContain('h-full');
  expect(
    screen
      .getByRole('button', { name: /生成拼图游戏草稿/u })
      .parentElement?.className,
  ).toContain('justify-center');

  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '一只猫在阳光窗台上看着毛线球。' },
  });
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();
  expect(onCreateFromForm).toHaveBeenCalledWith(
    expect.objectContaining({
      pictureDescription: '一只猫在阳光窗台上看着毛线球。',
    }),
  );
});

test('puzzle workspace selects a history image from the upload card', async () => {
  const onCreateFromForm = vi.fn();
  vi.mocked(puzzleAssetClient.listHistoryAssets).mockResolvedValue([
    {
      assetObjectId: 'asset-history-1',
      assetKind: 'puzzle_cover_image',
      imageSrc: '/generated-puzzle-assets/history/image.png',
      ownerUserId: 'user-1',
      ownerLabel: '账号 user-1',
      profileId: null,
      entityId: 'puzzle-session-1',
      createdAt: '1713686400.000000Z',
      updatedAt: '1713686400.000000Z',
    },
  ]);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  const historyButton = screen.getByRole('button', { name: '选择历史图片' });
  expect(historyButton.closest('.puzzle-image-upload-card')).toBeTruthy();
  expect(historyButton.className).toContain('top-3');
  expect(historyButton.className).toContain('right-3');
  expect(historyButton.className).not.toContain('bottom-3');
  expect(screen.getByText('历史').closest('.puzzle-image-upload-card')).toBeTruthy();
  fireEvent.click(historyButton);

  const picker = await screen.findByRole('dialog', {
    name: '选择历史图片',
  });
  expect(await within(picker).findByText(/2024\/04\/21/u)).toBeTruthy();
  expect(within(picker).queryByText('image.png')).toBeNull();
  expect(within(picker).queryByText('账号 user-1')).toBeNull();
  fireEvent.click(
    await within(picker).findByRole('button', {
      name: /选择2024\/04\/21.*的历史图片/u,
    }),
  );

  await waitFor(() => {
    expect(screen.queryByRole('dialog', { name: '选择历史图片' })).toBeNull();
  });
  expect(screen.getByAltText('拼图图片')).toHaveProperty(
    'src',
    expect.stringContaining('/generated-puzzle-assets/history/image.png'),
  );
  expect(screen.getByLabelText('画面AI重绘要求（提示词）')).toBeTruthy();

  fireEvent.change(screen.getByLabelText('画面AI重绘要求（提示词）'), {
    target: { value: '保留历史图里的主体，改成晴天花园。' },
  });
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: '保留历史图里的主体，改成晴天花园。',
    workDescription: '保留历史图里的主体，改成晴天花园。',
    pictureDescription: '保留历史图里的主体，改成晴天花园。',
    referenceImageSrc: null,
    referenceImageSrcs: [],
    referenceImageAssetObjectId: 'asset-history-1',
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
  });
});

test('puzzle upload card stays light in light theme', () => {
  const onCreateFromForm = vi.fn();
  const { container } = render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  expect(container.querySelector('.puzzle-image-upload-card')).toBeTruthy();
  const uploadLabel = screen.getByText('上传图片/填写画面描述');
  expect(uploadLabel).toBeTruthy();
  expect(uploadLabel.closest('.puzzle-image-upload-card')).toBeTruthy();
  expect(uploadLabel.className).not.toContain('rounded-full');
  expect(uploadLabel.className).not.toContain('bg-white/94');
  expect(uploadLabel.className).not.toContain('border');
  expect(screen.queryByText('AI重绘')).toBeNull();
  expect(container.querySelector('.puzzle-image-upload-card')?.className).toContain(
    'bg-white/90',
  );
  expect(container.querySelector('.puzzle-image-upload-card')?.className).not.toContain(
    'bg-slate-950',
  );
});

test('puzzle workspace falls back to compile action for restored sessions', () => {
  const onExecuteAction = vi.fn();
  const onCreateFromForm = vi.fn();

  render(
    <PuzzleCreationWorkspace
      session={baseSession}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={onExecuteAction}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();

  expect(onCreateFromForm).not.toHaveBeenCalled();
  expect(onExecuteAction).toHaveBeenCalledWith({
    action: 'compile_puzzle_draft',
    workDescription: '潮雾中的灯塔与断桥',
    pictureDescription: '潮雾中的灯塔与断桥',
    promptText: '潮雾中的灯塔与断桥',
    referenceImageSrc: null,
    referenceImageSrcs: [],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
    candidateCount: 1,
  });
});

test('puzzle workspace switches image mode without exposing model names', () => {
  const onCreateFromForm = vi.fn();

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '一只猫在雨夜灯牌下回头。' },
  });
  fireEvent.click(screen.getByRole('button', { name: '图片生成模式' }));
  expect(screen.queryByText(/gpt|nanobanana|gemini/u)).toBeNull();
  fireEvent.click(screen.getByRole('menuitemradio', { name: '创意模式' }));
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();

  expect(onCreateFromForm).toHaveBeenCalledWith(
    expect.objectContaining({
      imageModel: 'gemini-3.1-flash-image-preview',
    }),
  );
});

test('puzzle workspace restores form draft fields and autosaves edits', () => {
  vi.useFakeTimers();
  const onAutoSaveForm = vi.fn();
  const formDraftSession: PuzzleAgentSessionSnapshot = {
    ...baseSession,
    seedText: '画面描述：旧街灯牌下的猫。',
    draft: {
      workTitle: '旧街拼图',
      workDescription: '旧街雨夜的拼图草稿。',
      levelName: '旧街灯牌',
      summary: '旧街雨夜的拼图草稿。',
      themeTags: ['旧街', '雨夜', '猫'],
      forbiddenDirectives: [],
      creatorIntent: null,
      anchorPack: baseSession.anchorPack,
      candidates: [],
      selectedCandidateId: null,
      coverImageSrc: null,
      coverAssetId: null,
      generationStatus: 'idle',
      levels: [
        {
          levelId: 'puzzle-level-1',
          levelName: '旧街灯牌',
          pictureDescription: '旧街灯牌下的猫。',
          candidates: [],
          selectedCandidateId: null,
          coverImageSrc: null,
          coverAssetId: null,
          generationStatus: 'idle',
        },
      ],
      formDraft: {
        pictureDescription: '旧街灯牌下的猫。',
      },
    },
  };

  render(
    <PuzzleCreationWorkspace
      session={formDraftSession}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onAutoSaveForm={onAutoSaveForm}
    />,
  );

  expect(
    (screen.getByLabelText('画面描述') as HTMLTextAreaElement).value,
  ).toBe('旧街灯牌下的猫。');

  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '旧街灯牌下的猫和发光雨伞。' },
  });

  act(() => {
    vi.advanceTimersByTime(700);
  });

  expect(onAutoSaveForm).toHaveBeenCalledWith({
    seedText: '旧街灯牌下的猫和发光雨伞。',
    workDescription: '旧街灯牌下的猫和发光雨伞。',
    pictureDescription: '旧街灯牌下的猫和发光雨伞。',
    referenceImageSrc: null,
    referenceImageSrcs: [],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
  });
});

test('puzzle workspace hides prompt and cost when AI redraw is off', async () => {
  const onCreateFromForm = vi.fn();
  const uploadedDataUrl = 'data:image/png;base64,uploaded-square';
  stubReferenceImageUpload(uploadedDataUrl);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  const input = screen.getByLabelText('上传拼图图片', {
    selector: 'input',
  });
  fireEvent.change(input, {
    target: {
      files: [new File(['x'], 'first-level.png', { type: 'image/png' })],
    },
  });

  await waitFor(() => {
    expect(screen.getByLabelText('画面AI重绘要求（提示词）')).toBeTruthy();
  });
  expect(screen.queryByText('first-level.png')).toBeNull();
  const aiRedrawSwitch = screen.getByRole('switch', { name: 'AI重绘' });
  expect((aiRedrawSwitch as HTMLInputElement).checked).toBe(true);
  fireEvent.click(aiRedrawSwitch);
  expect(screen.queryByLabelText('画面AI重绘要求（提示词）')).toBeNull();
  expect(screen.queryByText('消耗2泥点')).toBeNull();

  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: 'first-level.png',
    workDescription: 'first-level.png',
    pictureDescription: 'first-level.png',
    referenceImageSrc: 'data:image/png;base64,uploaded-square',
    referenceImageSrcs: [],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: false,
  });
});

test('puzzle workspace submits history image when AI redraw is off', async () => {
  const onCreateFromForm = vi.fn();
  vi.mocked(puzzleAssetClient.listHistoryAssets).mockResolvedValue([
    {
      assetObjectId: 'asset-history-1',
      assetKind: 'puzzle_cover_image',
      imageSrc: '/generated-puzzle-assets/history/image.png',
      ownerUserId: 'user-1',
      ownerLabel: '账号 user-1',
      profileId: null,
      entityId: 'puzzle-session-1',
      createdAt: '1713686400.000000Z',
      updatedAt: '1713686400.000000Z',
    },
  ]);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  fireEvent.click(screen.getByRole('button', { name: '选择历史图片' }));
  const picker = await screen.findByRole('dialog', {
    name: '选择历史图片',
  });
  fireEvent.click(
    await within(picker).findByRole('button', {
      name: /选择2024\/04\/21.*的历史图片/u,
    }),
  );
  await waitFor(() => {
    expect(screen.queryByRole('dialog', { name: '选择历史图片' })).toBeNull();
  });

  const aiRedrawSwitch = screen.getByRole('switch', { name: 'AI重绘' });
  fireEvent.click(aiRedrawSwitch);
  expect(screen.queryByLabelText('画面AI重绘要求（提示词）')).toBeNull();
  expect(screen.queryByText('消耗2泥点')).toBeNull();

  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: '历史素材',
    workDescription: '历史素材',
    pictureDescription: '历史素材',
    referenceImageSrc: '/generated-puzzle-assets/history/image.png',
    referenceImageSrcs: [],
    referenceImageAssetObjectId: 'asset-history-1',
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: false,
  });
});

test('puzzle workspace submits uploaded reference image as data URL when AI redraw is on', async () => {
  const onCreateFromForm = vi.fn();
  const uploadedDataUrl = 'data:image/png;base64,uploaded-square';
  stubReferenceImageUpload(uploadedDataUrl);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  fireEvent.change(screen.getByLabelText('上传拼图图片', { selector: 'input' }), {
    target: {
      files: [new File(['x'], 'first-level.png', { type: 'image/png' })],
    },
  });
  await waitFor(() => {
    expect(screen.getByAltText('拼图图片')).toBeTruthy();
  });
  expect(puzzleAssetClient.uploadReferenceImage).not.toHaveBeenCalled();
  fireEvent.change(screen.getByLabelText('画面AI重绘要求（提示词）'), {
    target: { value: '保留上传画面的主体和构图，改成雨夜灯街。' },
  });
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: '保留上传画面的主体和构图，改成雨夜灯街。',
    workDescription: '保留上传画面的主体和构图，改成雨夜灯街。',
    pictureDescription: '保留上传画面的主体和构图，改成雨夜灯街。',
    referenceImageSrc: 'data:image/png;base64,uploaded-square',
    referenceImageSrcs: [],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
  });
});

test('puzzle workspace uploads prompt references as asset object ids', async () => {
  const onCreateFromForm = vi.fn();
  const uploadedSources = [
    'data:image/png;base64,reference-1',
    'data:image/png;base64,reference-2',
  ];
  let readIndex = 0;
  stubReferenceImageUpload(uploadedSources[0] ?? 'data:image/png;base64,reference-1');
  class MockFileReader {
    result: string | null = null;
    onload: null | (() => void) = null;
    onerror: null | (() => void) = null;

    readAsDataURL() {
      this.result = uploadedSources[readIndex] ?? uploadedSources[0] ?? '';
      readIndex += 1;
      this.onload?.();
    }
  }
  vi.stubGlobal('FileReader', MockFileReader as unknown as typeof FileReader);
  vi.mocked(puzzleAssetClient.uploadReferenceImage)
    .mockResolvedValueOnce({
      assetObjectId: 'asset-reference-prompt-1',
      assetKind: 'puzzle_cover_image',
      objectKey: 'generated-puzzle-assets/reference/prompt-1.png',
      imageSrc: '/generated-puzzle-assets/reference/prompt-1.png',
      ownerUserId: 'user-1',
      ownerLabel: '账号 user-1',
      profileId: null,
      entityId: null,
      createdAt: '1713686400.000000Z',
      updatedAt: '1713686400.000000Z',
    })
    .mockResolvedValueOnce({
      assetObjectId: 'asset-reference-prompt-2',
      assetKind: 'puzzle_cover_image',
      objectKey: 'generated-puzzle-assets/reference/prompt-2.png',
      imageSrc: '/generated-puzzle-assets/reference/prompt-2.png',
      ownerUserId: 'user-1',
      ownerLabel: '账号 user-1',
      profileId: null,
      entityId: null,
      createdAt: '1713686400.000000Z',
      updatedAt: '1713686400.000000Z',
    });

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '一只猫在雨夜灯牌下回头。' },
  });
  fireEvent.change(screen.getByLabelText('上传参考图', { selector: 'input' }), {
    target: {
      files: uploadedSources.map(
        (_source, index) =>
          new File(['x'], `reference-${index + 1}.png`, {
            type: 'image/png',
          }),
      ),
    },
  });

  await waitFor(() => {
    expect(screen.getAllByRole('button', { name: /预览参考图/u })).toHaveLength(
      2,
    );
  });
  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: '一只猫在雨夜灯牌下回头。',
    workDescription: '一只猫在雨夜灯牌下回头。',
    pictureDescription: '一只猫在雨夜灯牌下回头。',
    referenceImageSrc: null,
    referenceImageSrcs: [
      'data:image/png;base64,reference-1',
      'data:image/png;base64,reference-2',
    ],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
  });
});

test('puzzle workspace uploads prompt reference images from the description box', async () => {
  const onCreateFromForm = vi.fn();
  const uploadedSources = [
    'data:image/png;base64,reference-1',
    'data:image/png;base64,reference-2',
    'data:image/png;base64,reference-3',
    'data:image/png;base64,reference-4',
    'data:image/png;base64,reference-5',
    'data:image/png;base64,reference-6',
  ];
  let readIndex = 0;
  const firstUploadedSource = uploadedSources[0] || 'data:image/png;base64,reference-1';
  stubReferenceImageUpload(firstUploadedSource);
  class MockFileReader {
    result: string | null = null;
    onload: null | (() => void) = null;
    onerror: null | (() => void) = null;

    readAsDataURL() {
      this.result =
        uploadedSources[Math.min(readIndex, uploadedSources.length - 1)] ||
        firstUploadedSource;
      readIndex += 1;
      this.onload?.();
    }
  }
  vi.stubGlobal('FileReader', MockFileReader as unknown as typeof FileReader);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={onCreateFromForm}
    />,
  );

  fireEvent.change(screen.getByLabelText('画面描述'), {
    target: { value: '一只猫在雨夜灯牌下回头。' },
  });
  fireEvent.change(screen.getByLabelText('上传参考图', { selector: 'input' }), {
    target: {
      files: uploadedSources.map(
        (_source, index) =>
          new File(['x'], `reference-${index + 1}.png`, {
            type: 'image/png',
          }),
      ),
    },
  });

  await waitFor(() => {
    expect(screen.getAllByRole('button', { name: /预览参考图/u })).toHaveLength(
      5,
    );
  });
  expect(screen.getByText('参考图最多上传 5 张。')).toBeTruthy();
  fireEvent.click(
    screen.getByRole('button', { name: /预览参考图 reference-1\.png/u }),
  );
  expect(
    await screen.findByRole('dialog', { name: 'reference-1.png' }),
  ).toBeTruthy();
  expect(screen.getByAltText('参考图预览')).toHaveProperty(
    'src',
    expect.stringContaining('reference-1'),
  );
  fireEvent.click(screen.getByRole('button', { name: '关闭参考图预览' }));

  fireEvent.click(screen.getByRole('button', { name: /生成拼图游戏草稿/u }));
  confirmPuzzlePointCost();

  expect(onCreateFromForm).toHaveBeenCalledWith({
    seedText: '一只猫在雨夜灯牌下回头。',
    workDescription: '一只猫在雨夜灯牌下回头。',
    pictureDescription: '一只猫在雨夜灯牌下回头。',
    referenceImageSrc: null,
    referenceImageSrcs: [
      'data:image/png;base64,reference-1',
      'data:image/png;base64,reference-2',
      'data:image/png;base64,reference-3',
      'data:image/png;base64,reference-4',
      'data:image/png;base64,reference-5',
    ],
    referenceImageAssetObjectId: null,
    referenceImageAssetObjectIds: [],
    imageModel: 'gpt-image-2',
    aiRedraw: true,
  });
});

test('puzzle workspace shows AI redraw switch only after upload', async () => {
  const uploadedDataUrl = 'data:image/png;base64,uploaded-square';
  stubReferenceImageUpload(uploadedDataUrl);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={() => {}}
    />,
  );

  expect(screen.queryByRole('switch', { name: 'AI重绘' })).toBeNull();
  fireEvent.change(screen.getByLabelText('上传拼图图片', { selector: 'input' }), {
    target: {
      files: [new File(['x'], 'first-level.png', { type: 'image/png' })],
    },
  });

  await waitFor(() => {
    expect(screen.getByRole('switch', { name: 'AI重绘' })).toBeTruthy();
  });
  expect(
    screen.getByRole('switch', { name: 'AI重绘' }).closest('.puzzle-image-upload-card'),
  ).toBeTruthy();
  expect(screen.getByRole('button', { name: '移除拼图图片' })).toBeTruthy();
  expect(screen.getByRole('button', { name: '移除拼图图片' }).className).toContain(
    'left-3',
  );
  expect(screen.getByRole('button', { name: '选择历史图片' }).className).toContain(
    'right-3',
  );
  expect(screen.queryByText('上传图片/填写画面描述')).toBeNull();
});

test('puzzle workspace confirms before removing uploaded image', async () => {
  const uploadedDataUrl = 'data:image/png;base64,uploaded-square';
  stubReferenceImageUpload(uploadedDataUrl);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={() => {}}
    />,
  );

  fireEvent.change(screen.getByLabelText('上传拼图图片', { selector: 'input' }), {
    target: {
      files: [new File(['x'], 'first-level.png', { type: 'image/png' })],
    },
  });

  await waitFor(() => {
    expect(screen.getByAltText('拼图图片')).toBeTruthy();
  });
  fireEvent.click(screen.getByRole('button', { name: '移除拼图图片' }));
  expect(
    screen.getByRole('dialog', { name: '移除拼图图片？' }),
  ).toBeTruthy();
  expect(screen.getByAltText('拼图图片')).toBeTruthy();

  fireEvent.click(screen.getByRole('button', { name: '取消' }));
  expect(screen.queryByRole('dialog', { name: '移除拼图图片？' })).toBeNull();
  expect(screen.getByAltText('拼图图片')).toBeTruthy();

  fireEvent.click(screen.getByRole('button', { name: '移除拼图图片' }));
  fireEvent.click(screen.getByRole('button', { name: '移除' }));
  expect(screen.queryByAltText('拼图图片')).toBeNull();
  expect(screen.queryByRole('switch', { name: 'AI重绘' })).toBeNull();
  expect(screen.getByText('上传图片/填写画面描述')).toBeTruthy();
});

test('puzzle workspace opens crop tool for non-square uploads', async () => {
  const sourceDataUrl = 'data:image/png;base64,wide-source';
  const croppedDataUrl = 'data:image/jpeg;base64,Y3JvcHBlZC1zcXVhcmU=';
  stubReferenceImageUpload(sourceDataUrl, 800, 600);
  const drawImage = stubCanvas(croppedDataUrl);

  render(
    <PuzzleCreationWorkspace
      session={null}
      onBack={() => {}}
      onSubmitMessage={() => {}}
      onExecuteAction={() => {}}
      onCreateFromForm={() => {}}
    />,
  );

  fireEvent.change(
    screen.getByLabelText('上传拼图图片', { selector: 'input' }),
    {
      target: {
        files: [new File(['x'], 'wide.png', { type: 'image/png' })],
      },
    },
  );

  await waitFor(() => {
    expect(screen.getByRole('dialog', { name: '裁剪拼图图片' })).toBeTruthy();
  });
  expect(
    screen.getByRole('button', { name: '拖拽右下角裁剪边界' }),
  ).toBeTruthy();
  expect(screen.queryByText('缩放')).toBeNull();
  expect(screen.queryByText('横向')).toBeNull();
  expect(screen.queryByText('纵向')).toBeNull();
  fireEvent.click(screen.getByRole('button', { name: '应用' }));

  await waitFor(() => {
    expect(screen.queryByRole('dialog', { name: '裁剪拼图图片' })).toBeNull();
  });
  expect(screen.queryByText('wide.png')).toBeNull();
  expect(screen.getByAltText('拼图图片')).toBeTruthy();
  expect(drawImage).toHaveBeenCalledWith(
    expect.anything(),
    100,
    0,
    600,
    600,
    0,
    0,
    600,
    600,
  );
});
