/* @vitest-environment jsdom */

import { fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { afterEach, expect, test, vi } from 'vitest';

import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type { CustomWorldWorkSummary } from '../../../packages/shared/src/contracts/customWorldAgent';
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import type { SquareHoleWorkSummary } from '../../../packages/shared/src/contracts/squareHoleWorks';
import type { CreationEntryConfig } from '../../services/creationEntryConfigService';
import { derivePlatformCreationTypes } from '../platform-entry/platformEntryCreationTypes';
import { CustomWorldCreationHub } from './CustomWorldCreationHub';

const noopCreateType = () => {};

const testEntryConfig = {
  startCard: {
    title: '新建作品',
    description: '选择模板后进入对应的创作表单。',
    idleBadge: '模板 Tab',
    busyBadge: '正在开启',
  },
  typeModal: {
    title: '选择创作类型',
    description: '先选玩法类型，再进入对应创作工作台。',
  },
  eventBanner: {
    title: '泥点挑战',
    description: '创作活动测试横幅。',
    coverImageSrc: '/creation-type-references/puzzle.webp',
    prizePoolMudPoints: 1000,
    startsAtText: '2026-05-01',
    endsAtText: '2026-05-31',
  },
  creationTypes: [
    {
      id: 'rpg',
      title: '文字冒险',
      subtitle: '经典 RPG 体验',
      badge: '可创建',
      imageSrc: '/creation-type-references/rpg.webp',
      visible: true,
      open: true,
      sortOrder: 10,
      categoryId: 'recent',
      categoryLabel: '最近创作',
      categorySortOrder: 10,
      updatedAtMicros: 1,
    },
    {
      id: 'puzzle',
      title: '拼图',
      subtitle: '拼图关卡创作',
      badge: '可创建',
      imageSrc: '/creation-type-references/puzzle.webp',
      visible: true,
      open: true,
      sortOrder: 30,
      categoryId: 'recent',
      categoryLabel: '最近创作',
      categorySortOrder: 10,
      updatedAtMicros: 1,
    },
    {
      id: 'match3d',
      title: '抓大鹅',
      subtitle: '3D 消除关卡',
      badge: '可创建',
      imageSrc: '/creation-type-references/match3d.webp',
      visible: true,
      open: true,
      sortOrder: 40,
      categoryId: 'recent',
      categoryLabel: '最近创作',
      categorySortOrder: 10,
      updatedAtMicros: 1,
    },
    {
      id: 'square-hole',
      title: '方洞',
      subtitle: '形状投放挑战',
      badge: '可创建',
      imageSrc: '/creation-type-references/square-hole.webp',
      visible: false,
      open: true,
      sortOrder: 50,
      categoryId: 'recent',
      categoryLabel: '最近创作',
      categorySortOrder: 10,
      updatedAtMicros: 1,
    },
    {
      id: 'visual-novel',
      title: '视觉小说',
      subtitle: '分支叙事体验',
      badge: '敬请期待',
      imageSrc: '/creation-type-references/visual-novel.webp',
      visible: false,
      open: false,
      sortOrder: 60,
      categoryId: 'recent',
      categoryLabel: '最近创作',
      categorySortOrder: 10,
      updatedAtMicros: 1,
    },
    {
      id: 'airp',
      title: 'AI RPG',
      subtitle: '原生角色扮演',
      badge: '即将开放',
      imageSrc: '/creation-type-references/airp.webp',
      visible: true,
      open: false,
      sortOrder: 70,
      categoryId: 'recent',
      categoryLabel: '最近创作',
      categorySortOrder: 10,
      updatedAtMicros: 1,
    },
  ],
} satisfies CreationEntryConfig;

const testCreationTypes = derivePlatformCreationTypes(
  testEntryConfig.creationTypes,
);

const originalClipboard = navigator.clipboard;

afterEach(() => {
  window.sessionStorage.clear();
  Object.defineProperty(navigator, 'clipboard', {
    configurable: true,
    value: originalClipboard,
  });
});

test('creation hub shows published metric growth from cached page snapshot', async () => {
  window.sessionStorage.setItem(
    'genarrative.creationHub.publishedMetrics.v1',
    JSON.stringify({
      'puzzle:puzzle:work-growth': {
        'play-count': 7,
        'remix-count': 1,
        'like-count': 2,
      },
    }),
  );

  render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:work-growth',
          profileId: 'puzzle-profile-growth',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '涨潮拼图',
          summary: '公开指标会从缓存快照涨到最新值。',
          themeTags: ['涨潮'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
          playCount: 10,
          remixCount: 4,
          likeCount: 2,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
      onOpenPuzzleDetail={() => {}}
    />,
  );

  expect(screen.getByLabelText('游玩 10次')).toBeTruthy();
  expect(screen.getByLabelText('改造 4次')).toBeTruthy();
  expect(await screen.findAllByText('↑')).toHaveLength(2);
});

const baseDraftItem: CustomWorldWorkSummary = {
  workId: 'draft:session-1',
  sourceType: 'agent_session',
  status: 'draft',
  title: '潮雾列岛',
  subtitle: '补齐关键锚点',
  summary: '玩家是失职返乡的守灯人。',
  coverImageSrc: null,
  updatedAt: new Date('2026-04-14T10:00:00.000Z').toISOString(),
  publishedAt: null,
  stage: 'object_refining',
  stageLabel: '待完善草稿',
  playableNpcCount: 3,
  landmarkCount: 4,
  sessionId: 'session-1',
  profileId: null,
  canResume: true,
  canEnterWorld: false,
};

const hiddenSquareHoleItem: SquareHoleWorkSummary = {
  workId: 'square-hole:work-hidden',
  profileId: 'square-hole-profile-hidden',
  ownerUserId: 'user-1',
  gameName: '隐藏方洞挑战',
  themeText: '方洞',
  twistRule: '隐藏入口',
  summary: '入口隐藏后，这条作品不应出现在创作页作品架。',
  tags: ['方洞'],
  coverImageSrc: null,
  backgroundPrompt: '',
  backgroundImageSrc: null,
  shapeOptions: [],
  holeOptions: [],
  shapeCount: 0,
  difficulty: 1,
  publicationStatus: 'draft',
  playCount: 0,
  updatedAt: new Date('2026-05-10T10:00:00.000Z').toISOString(),
  publishedAt: null,
  publishReady: false,
  sourceSessionId: 'square-hole-session-hidden',
};

const babyObjectMatchDraftItem: BabyObjectMatchDraft = {
  draftId: 'baby-object-draft-delete',
  profileId: 'baby-object-profile-delete',
  templateId: 'baby-object-match',
  templateName: '宝贝识物',
  workTitle: '宝贝识物删除测试',
  workDescription: '苹果和香蕉识物分类',
  itemNames: ['苹果', '香蕉'],
  itemAssets: [
    {
      itemId: 'baby-object-item-1',
      itemName: '苹果',
      imageSrc: '/apple.png',
      assetObjectId: null,
      generationProvider: 'placeholder',
      prompt: '苹果',
    },
    {
      itemId: 'baby-object-item-2',
      itemName: '香蕉',
      imageSrc: '/banana.png',
      assetObjectId: null,
      generationProvider: 'placeholder',
      prompt: '香蕉',
    },
  ],
  visualPackage: null,
  themeTags: ['寓教于乐'],
  publicationStatus: 'draft',
  createdAt: new Date('2026-05-11T10:00:00.000Z').toISOString(),
  updatedAt: new Date('2026-05-11T10:00:00.000Z').toISOString(),
  publishedAt: null,
};

const barkBattleDraftItem: BarkBattleWorkSummary = {
  workId: 'bark-battle-work-draft-visible',
  draftId: 'bark-battle-draft-visible',
  ownerUserId: 'user-1',
  authorDisplayName: '声浪作者',
  title: '竖屏声浪草稿',
  summary: '生成完成后也必须留在我的草稿里。',
  themeDescription: '霓虹竖屏擂台',
  playerImageDescription: '红围巾选手',
  opponentImageDescription: '蓝头带对手',
  onomatopoeia: ['炸场', '破阵'],
  playerCharacterImageSrc: '/bark/player.png',
  opponentCharacterImageSrc: '/bark/opponent.png',
  uiBackgroundImageSrc: '/bark/background.png',
  difficultyPreset: 'normal',
  status: 'draft',
  generationStatus: 'ready',
  publishReady: true,
  playCount: 0,
  updatedAt: '2026-05-21T10:00:00.000Z',
  publishedAt: null,
};

const barkBattlePublishedItem: BarkBattleWorkSummary = {
  ...barkBattleDraftItem,
  workId: 'bark-battle-work-published-visible',
  draftId: 'bark-battle-draft-published-visible',
  title: '竖屏声浪已发布',
  summary: '发布完成后必须留在已发布作品里。',
  authorDisplayName: '发布作者',
  status: 'published',
  playCount: 9,
  updatedAt: '2026-05-21T10:10:00.000Z',
  publishedAt: '2026-05-21T10:10:00.000Z',
};

test('creation hub reflects updated draft title summary and counts after rerender', async () => {
  const user = userEvent.setup();
  const onCreateType = vi.fn();
  const { rerender } = render(
    <CustomWorldCreationHub
      items={[baseDraftItem]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={onCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByText('潮雾列岛')).toBeTruthy();
  expect(screen.getByText('玩家是失职返乡的守灯人。')).toBeTruthy();
  expect(screen.queryByText('角色 3')).toBeNull();
  expect(screen.queryByText('地点 4')).toBeNull();
  const puzzleButton = screen.getByRole('button', {
    name: /拼图.*拼图关卡创作/u,
  });
  const match3dButton = screen.getByRole('button', {
    name: /抓大鹅.*3D 消除关卡/u,
  });
  const rpgButton = screen.getByRole('button', {
    name: /文字冒险.*经典 RPG 体验/u,
  });
  expect(puzzleButton).toBeTruthy();
  expect(match3dButton).toBeTruthy();
  expect(rpgButton).toBeTruthy();
  expect((puzzleButton as HTMLButtonElement).disabled).toBe(false);
  expect((match3dButton as HTMLButtonElement).disabled).toBe(false);
  expect((rpgButton as HTMLButtonElement).disabled).toBe(false);
  expect(screen.queryByRole('button', { name: /方洞挑战/u })).toBeNull();
  expect(screen.queryByText('反直觉形状分拣')).toBeNull();
  expect(screen.queryByRole('button', { name: /智能创作/u })).toBeNull();
  expect(screen.queryByRole('button', { name: /大鱼吃小鱼/u })).toBeNull();

  await user.click(match3dButton);
  expect(onCreateType).toHaveBeenCalledWith('match3d');

  rerender(
    <CustomWorldCreationHub
      items={[
        {
          ...baseDraftItem,
          title: '潮雾列岛·回潮版',
          summary: '世界总卡和角色网已经继续长出了新的支线。',
          playableNpcCount: 5,
          landmarkCount: 6,
          updatedAt: new Date('2026-04-14T10:10:00.000Z').toISOString(),
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByText('潮雾列岛·回潮版')).toBeTruthy();
  expect(
    screen.getByText('世界总卡和角色网已经继续长出了新的支线。'),
  ).toBeTruthy();
  expect(screen.queryByText('角色 5')).toBeNull();
  expect(screen.queryByText('地点 6')).toBeNull();
});

test('creation hub hides square hole works when the creation type is hidden', () => {
  const onOpenSquareHoleDetail = vi.fn();

  render(
    <CustomWorldCreationHub
      items={[]}
      squareHoleItems={[hiddenSquareHoleItem]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
      onOpenSquareHoleDetail={onOpenSquareHoleDetail}
    />,
  );

  expect(screen.queryByText('隐藏方洞挑战')).toBeNull();
  expect(
    screen.queryByText('入口隐藏后，这条作品不应出现在创作页作品架。'),
  ).toBeNull();
});

test('creation hub mixes puzzle works into the same grid and uses puzzle tag to distinguish', () => {
  render(
    <CustomWorldCreationHub
      items={[baseDraftItem]}
      puzzleItems={[
        {
          workId: 'puzzle:work-1',
          profileId: 'puzzle-profile-1',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '沉钟拼图',
          summary: '拼图作品会与其他创作作品一起展示。',
          themeTags: ['潮雾', '沉钟'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
          playCount: 8,
          remixCount: 2,
          likeCount: 3,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
      onOpenPuzzleDetail={() => {}}
    />,
  );

  expect(screen.getByText('潮雾列岛')).toBeTruthy();
  expect(screen.getByText('沉钟拼图')).toBeTruthy();
  expect(screen.getAllByText('拼图').length).toBeGreaterThan(0);
  expect(screen.getByLabelText('游玩 8次')).toBeTruthy();
  expect(screen.getByLabelText('改造 2次')).toBeTruthy();
  expect(screen.getByLabelText('点赞 3赞')).toBeTruthy();
  expect(screen.queryByText('Remix')).toBeNull();
  expect(screen.queryByText('PZ-PROFILE1')).toBeNull();
  expect(screen.queryByText('潮雾')).toBeNull();
  expect(screen.queryByText('沉钟')).toBeNull();
  expect(screen.queryByText('我的拼图作品')).toBeNull();
});

test('creation hub shows puzzle point incentive and claims without opening card', async () => {
  const user = userEvent.setup();
  const onClaimPuzzlePointIncentive = vi.fn();
  const onOpenPuzzleDetail = vi.fn();

  render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:work-incentive',
          profileId: 'puzzle-profile-incentive',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '陶泥儿灯塔',
          summary: '拼图作品会展示积分激励。',
          themeTags: ['灯塔', '陶泥儿'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-05-01T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-05-01T12:10:00.000Z').toISOString(),
          playCount: 8,
          remixCount: 2,
          likeCount: 3,
          pointIncentiveTotalHalfPoints: 5,
          pointIncentiveClaimedPoints: 1,
          pointIncentiveTotalPoints: 2.5,
          pointIncentiveClaimablePoints: 1,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenPuzzleDetail={onOpenPuzzleDetail}
      onClaimPuzzlePointIncentive={onClaimPuzzlePointIncentive}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByLabelText('积分激励总数 2.5 泥点')).toBeTruthy();
  expect(screen.getByLabelText('待领取积分 1 泥点')).toBeTruthy();

  await user.click(screen.getByRole('button', { name: '领取积分' }));

  expect(onClaimPuzzlePointIncentive).toHaveBeenCalledWith(
    expect.objectContaining({ profileId: 'puzzle-profile-incentive' }),
  );
  expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
});

test('creation hub shows RPG public work code from published library entry', () => {
  render(
    <CustomWorldCreationHub
      items={[
        {
          ...baseDraftItem,
          workId: 'published:world-public-1',
          sourceType: 'published_profile',
          status: 'published',
          title: '潮雾列岛已发布版',
          profileId: 'world-public-1',
          canResume: false,
          canEnterWorld: true,
        },
      ]}
      rpgLibraryEntries={[
        {
          ownerUserId: 'user-1',
          profileId: 'world-public-1',
          publicWorkCode: 'CW-00000001',
          authorPublicUserCode: 'SY-00000001',
          profile: {} as never,
          visibility: 'published',
          publishedAt: '2026-04-20T10:00:00.000Z',
          updatedAt: '2026-04-20T10:00:00.000Z',
          authorDisplayName: '测试玩家',
          worldName: '潮雾列岛已发布版',
          subtitle: '旧灯塔与失控航路',
          summaryText: '已经发布的群岛世界作品。',
          coverImageSrc: null,
          themeMode: 'tide',
          playableNpcCount: 3,
          landmarkCount: 4,
          playCount: 12,
          remixCount: 4,
          likeCount: 5,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByText('潮雾列岛已发布版')).toBeTruthy();
  expect(screen.getByLabelText('游玩 12次')).toBeTruthy();
  expect(screen.getByLabelText('改造 4次')).toBeTruthy();
  expect(screen.getByLabelText('点赞 5赞')).toBeTruthy();
  expect(screen.queryByText('Remix')).toBeNull();
  expect(screen.queryByText('CW-00000001')).toBeNull();
});

test('creation hub keeps persisted draft delete action off the card header', () => {
  const { container } = render(
    <CustomWorldCreationHub
      items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onDeletePublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(
    container.querySelector('.creation-work-card__swipe-underlay'),
  ).toBeTruthy();
  expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
});

test('creation hub reveals persisted draft delete action from left swipe', () => {
  const { container } = render(
    <CustomWorldCreationHub
      items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onDeletePublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  const card = screen.getByRole('button', { name: /继续完善《潮雾列岛》/u });
  fireEvent.touchStart(card, {
    touches: [{ clientX: 180, clientY: 20 }],
  });
  fireEvent.touchMove(card, {
    touches: [{ clientX: 92, clientY: 22 }],
  });
  fireEvent.touchEnd(card);

  expect(
    container.querySelector('.creation-work-card__swipe-button--danger'),
  ).toBeTruthy();
  expect(
    container.querySelector('.creation-work-card-shell--actions-visible'),
  ).toBeTruthy();
});

test('creation hub reveals persisted draft delete action from keyboard', async () => {
  const user = userEvent.setup();
  const { container } = render(
    <CustomWorldCreationHub
      items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onDeletePublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  screen.getByRole('button', { name: /继续完善《潮雾列岛》/u }).focus();
  await user.keyboard('{ArrowLeft}');

  expect(
    container.querySelector('.creation-work-card__swipe-button--danger'),
  ).toBeTruthy();
  expect(screen.queryByRole('button', { name: '分享' })).toBeNull();
});

test('creation hub reveals persisted draft delete action from long press menu', () => {
  const { container } = render(
    <CustomWorldCreationHub
      items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onDeletePublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  const card = screen.getByRole('button', { name: /继续完善《潮雾列岛》/u });
  fireEvent.contextMenu(card);

  expect(
    container.querySelector('.creation-work-card-shell--actions-visible'),
  ).toBeTruthy();
  expect(screen.getByRole('button', { name: '删除' })).toBeTruthy();
});

test('creation hub gives every deletable work card a side delete action', () => {
  const { container } = render(
    <CustomWorldCreationHub
      items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
      babyObjectMatchItems={[babyObjectMatchDraftItem]}
      puzzleItems={[
        {
          workId: 'puzzle:side-delete',
          profileId: 'puzzle-profile-side-delete',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '侧边删除拼图',
          summary: '不同来源都应有侧边删除。',
          themeTags: ['灯塔'],
          coverImageSrc: null,
          publicationStatus: 'draft',
          updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
          publishedAt: null,
          playCount: 0,
          remixCount: 0,
          likeCount: 0,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onDeletePublished={() => {}}
      onDeleteBabyObjectMatch={() => {}}
      onDeletePuzzle={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
  expect(
    container.querySelectorAll('.creation-work-card__swipe-underlay'),
  ).toHaveLength(3);
});

test('creation hub shows delete action for baby object match drafts', async () => {
  const user = userEvent.setup();
  const onDeleteBabyObjectMatch = vi.fn();
  const onOpenBabyObjectMatchDetail = vi.fn();

  const { container } = render(
    <CustomWorldCreationHub
      items={[]}
      babyObjectMatchItems={[babyObjectMatchDraftItem]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenBabyObjectMatchDetail={onOpenBabyObjectMatchDetail}
      onDeleteBabyObjectMatch={onDeleteBabyObjectMatch}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  screen.getByRole('button', { name: /继续创作《宝贝识物删除测试》/u }).focus();
  await user.keyboard('{ArrowLeft}');

  await user.click(
    container.querySelector(
      '.creation-work-card__swipe-button--danger',
    ) as HTMLButtonElement,
  );

  expect(onDeleteBabyObjectMatch).toHaveBeenCalledWith(
    babyObjectMatchDraftItem,
  );
  expect(onOpenBabyObjectMatchDetail).not.toHaveBeenCalled();
});

test('creation hub works-only tab filters bark battle draft and published works', async () => {
  const user = userEvent.setup();
  const onOpenBarkBattleDetail = vi.fn();

  render(
    <CustomWorldCreationHub
      mode="works-only"
      items={[]}
      barkBattleItems={[barkBattleDraftItem, barkBattlePublishedItem]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenBarkBattleDetail={onOpenBarkBattleDetail}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByRole('tab', { name: '全部 2' })).toBeTruthy();
  expect(screen.getByRole('tab', { name: '草稿 1' })).toBeTruthy();
  expect(screen.getByRole('tab', { name: '已发布 1' })).toBeTruthy();
  expect(screen.getByText('竖屏声浪草稿')).toBeTruthy();
  expect(screen.getByText('竖屏声浪已发布')).toBeTruthy();

  await user.click(screen.getByRole('tab', { name: '草稿 1' }));
  expect(screen.getByText('竖屏声浪草稿')).toBeTruthy();
  expect(screen.queryByText('竖屏声浪已发布')).toBeNull();

  await user.click(screen.getByRole('tab', { name: '已发布 1' }));
  expect(screen.queryByText('竖屏声浪草稿')).toBeNull();
  expect(screen.getByText('竖屏声浪已发布')).toBeTruthy();

  await user.click(
    screen.getByRole('button', { name: /查看详情《竖屏声浪已发布》/u }),
  );
  expect(onOpenBarkBattleDetail).toHaveBeenCalledWith(barkBattlePublishedItem);
});

test('creation hub published work delete action stays in revealed side actions', async () => {
  const user = userEvent.setup();
  const onDeletePuzzle = vi.fn();
  const onOpenPuzzleDetail = vi.fn();

  render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:work-delete',
          profileId: 'puzzle-profile-delete',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '待删拼图',
          summary: '已发布作品也可以从创作页删除。',
          themeTags: ['灯塔'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-05-02T12:10:00.000Z').toISOString(),
          playCount: 8,
          remixCount: 2,
          likeCount: 1,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenPuzzleDetail={onOpenPuzzleDetail}
      onDeletePuzzle={onDeletePuzzle}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
  expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();

  screen.getByRole('button', { name: /查看详情《待删拼图》/u }).focus();
  await user.keyboard('{ArrowLeft}');
  await user.click(screen.getByRole('button', { name: '删除' }));

  expect(onDeletePuzzle).toHaveBeenCalledWith(
    expect.objectContaining({ profileId: 'puzzle-profile-delete' }),
  );
  expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
});

test('creation hub reveals draft work delete action from keyboard', async () => {
  const user = userEvent.setup();
  const onDeletePuzzle = vi.fn();
  const onOpenPuzzleDetail = vi.fn();

  render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:direct-delete',
          profileId: 'puzzle-profile-direct-delete',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '直接删除拼图',
          summary: '作品卡片直接开放删除入口。',
          themeTags: ['灯塔'],
          coverImageSrc: null,
          publicationStatus: 'draft',
          updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
          publishedAt: null,
          playCount: 0,
          remixCount: 0,
          likeCount: 0,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenPuzzleDetail={onOpenPuzzleDetail}
      onDeletePuzzle={onDeletePuzzle}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.queryByRole('button', { name: '删除' })).toBeNull();

  screen.getByRole('button', { name: /继续创作《直接删除拼图》/u }).focus();
  await user.keyboard('{ArrowLeft}');
  await user.click(screen.getByRole('button', { name: '删除' }));

  expect(onDeletePuzzle).toHaveBeenCalledWith(
    expect.objectContaining({ profileId: 'puzzle-profile-direct-delete' }),
  );
  expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
});

test('creation hub keeps swipe delete action available', async () => {
  const user = userEvent.setup();
  const onDeletePuzzle = vi.fn();
  const onOpenPuzzleDetail = vi.fn();

  const { container } = render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:swipe-delete',
          profileId: 'puzzle-profile-swipe-delete',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '左滑删除拼图',
          summary: '左滑仍然保留辅助删除入口。',
          themeTags: ['灯塔'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-05-02T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-05-02T12:10:00.000Z').toISOString(),
          playCount: 8,
          remixCount: 2,
          likeCount: 1,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenPuzzleDetail={onOpenPuzzleDetail}
      onDeletePuzzle={onDeletePuzzle}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  const card = screen.getByRole('button', {
    name: /查看详情《左滑删除拼图》/u,
  });
  fireEvent.touchStart(card, {
    touches: [{ clientX: 180, clientY: 20 }],
  });
  fireEvent.touchMove(card, {
    touches: [{ clientX: 80, clientY: 22 }],
  });
  fireEvent.touchEnd(card);

  const swipeDeleteButton = container.querySelector(
    '.creation-work-card__swipe-button--danger',
  ) as HTMLButtonElement | null;
  expect(swipeDeleteButton).toBeTruthy();
  await user.click(swipeDeleteButton!);

  expect(onDeletePuzzle).toHaveBeenCalledWith(
    expect.objectContaining({ profileId: 'puzzle-profile-swipe-delete' }),
  );
  expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
});

test('creation hub opens persisted rpg drafts by card click', async () => {
  const user = userEvent.setup();
  const openedItems: CustomWorldWorkSummary[] = [];
  const persistedDraft = {
    ...baseDraftItem,
    workId: 'draft:profile-1',
    sourceType: 'published_profile' as const,
    sessionId: null,
    profileId: 'profile-1',
    title: '可继续整理的草稿',
  };

  render(
    <CustomWorldCreationHub
      items={[persistedDraft]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={(item) => {
        openedItems.push(item);
      }}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  await user.click(
    screen.getByRole('button', { name: /继续完善《可继续整理的草稿》/u }),
  );

  expect(openedItems).toEqual([persistedDraft]);
});

test('creation hub published share icon copies share text without opening the card', async () => {
  const user = userEvent.setup();
  const writeText = vi.fn(async () => undefined);
  const onOpenPuzzleDetail = vi.fn();
  Object.defineProperty(navigator, 'clipboard', {
    configurable: true,
    value: { writeText },
  });

  render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:work-1',
          profileId: 'puzzle-profile-1',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '沉钟拼图',
          summary: '拼图作品会与其他创作作品一起展示。',
          themeTags: ['潮雾', '沉钟'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
          playCount: 8,
          remixCount: 2,
          likeCount: 0,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenPuzzleDetail={onOpenPuzzleDetail}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  const shareButton = screen.getByRole('button', { name: '分享' });
  expect(shareButton).toBeTruthy();
  expect(screen.queryByText('删除')).toBeNull();

  await user.click(shareButton);

  expect(writeText).toHaveBeenCalledWith(
    expect.stringContaining('邀请你来玩《沉钟拼图》'),
  );
  expect(writeText).toHaveBeenCalledWith(
    expect.stringContaining('作品号：PZ-PROFILE1'),
  );
  expect(writeText).toHaveBeenCalledWith(
    expect.stringContaining('/gallery/puzzle/detail?work=PZ-PROFILE1'),
  );
  expect(onOpenPuzzleDetail).not.toHaveBeenCalled();
  expect(
    await screen.findByRole('button', { name: '分享内容已复制' }),
  ).toBeTruthy();
});

test('creation hub published share icon is shown directly on the card header', () => {
  render(
    <CustomWorldCreationHub
      items={[]}
      puzzleItems={[
        {
          workId: 'puzzle:work-share-icon',
          profileId: 'puzzle-profile-share-icon',
          ownerUserId: 'user-1',
          authorDisplayName: '拼图作者',
          levelName: '沉钟拼图',
          summary: '分享入口应直接露出在卡片右上角。',
          themeTags: ['潮雾'],
          coverImageSrc: null,
          publicationStatus: 'published',
          updatedAt: new Date('2026-04-22T12:00:00.000Z').toISOString(),
          publishedAt: new Date('2026-04-22T12:10:00.000Z').toISOString(),
          playCount: 8,
          remixCount: 2,
          likeCount: 0,
          publishReady: true,
        },
      ]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      onOpenPuzzleDetail={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
  expect(screen.queryByRole('button', { name: '删除' })).toBeNull();
});

test('creation hub shows RPG published share icon without library entry', () => {
  render(
    <CustomWorldCreationHub
      items={[
        {
          ...baseDraftItem,
          workId: 'published:world-public-1',
          sourceType: 'published_profile',
          status: 'published',
          title: '潮雾列岛已发布版',
          profileId: 'world-public-1',
          canResume: false,
          canEnterWorld: true,
        },
      ]}
      rpgLibraryEntries={[]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={() => {}}
      onEnterPublished={() => {}}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  expect(screen.getByText('潮雾列岛已发布版')).toBeTruthy();
  expect(screen.getByRole('button', { name: '分享' })).toBeTruthy();
  expect(screen.queryByText('作者：玩家')).toBeNull();
});

test('creation hub left swipe draft reveals delete without opening card', () => {
  const onDeletePublished = vi.fn();
  const onOpenDraft = vi.fn();

  const { container } = render(
    <CustomWorldCreationHub
      items={[{ ...baseDraftItem, profileId: 'profile-1' }]}
      loading={false}
      error={null}
      onRetry={() => {}}
      onCreateType={noopCreateType}
      onOpenDraft={onOpenDraft}
      onEnterPublished={() => {}}
      onDeletePublished={onDeletePublished}
      entryConfig={testEntryConfig}
      creationTypes={testCreationTypes}
    />,
  );

  const card = screen.getByRole('button', { name: /继续完善《潮雾列岛》/u });
  fireEvent.touchStart(card, {
    touches: [{ clientX: 180, clientY: 20 }],
  });
  fireEvent.touchMove(card, {
    touches: [{ clientX: 88, clientY: 22 }],
  });
  fireEvent.touchEnd(card);

  expect(
    container.querySelector('.creation-work-card__swipe-button--danger'),
  ).toBeTruthy();
  expect(onOpenDraft).not.toHaveBeenCalled();
});
