﻿/* @vitest-environment jsdom */

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

import * as creationAgentServices from '../../services/creation-agent';
import { createCreationAgentChatQuickActions } from '../../services/creation-agent';
import {
  type CreationAgentTheme,
  CreationAgentWorkspace,
} from './CreationAgentWorkspace';

const testTheme: CreationAgentTheme = {
  accentTextClass: 'text-emerald-100',
  accentBgClass: 'bg-emerald-300',
  accentButtonClass: 'bg-emerald-200',
  userBubbleClass: 'bg-emerald-600 text-white',
  heroClass: 'border border-emerald-100/20 bg-slate-900',
};

afterEach(() => {
  vi.restoreAllMocks();
});

function ensureScrollApis() {
  if (!Element.prototype.scrollIntoView) {
    Element.prototype.scrollIntoView = () => {};
  }

  if (!HTMLElement.prototype.scrollTo) {
    HTMLElement.prototype.scrollTo = () => {};
  }
}

test('creation agent workspace keeps initial chat progress at zero percent', () => {
  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: null,
        currentTurn: 0,
        progressPercent: 0,
        anchors: [],
        messages: [],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  const progressbar = screen.getByRole('progressbar');

  expect(progressbar.getAttribute('aria-valuenow')).toBe('0');
  expect(
    (progressbar.firstElementChild as HTMLElement | null)?.style.width,
  ).toBe('0%');
});

test('creation agent workspace filters duplicate recommended replies', () => {
  const consoleErrorSpy = vi
    .spyOn(console, 'error')
    .mockImplementation(() => undefined);

  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 2,
        progressPercent: 40,
        anchors: [],
        messages: [
          {
            id: '',
            role: 'assistant',
            kind: 'chat',
            text: '先把方向收一下。',
          },
        ],
        recommendedReplies: [
          '',
          '继续补充冲突',
          '继续补充冲突',
          ' 先确定玩家身份 ',
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(screen.getByRole('button', { name: '继续补充冲突' })).toBeTruthy();
  expect(screen.getByRole('button', { name: '先确定玩家身份' })).toBeTruthy();
  expect(screen.queryByRole('button', { name: /^\s*$/u })).toBeNull();

  const duplicateKeyCalls = consoleErrorSpy.mock.calls.filter((call) =>
    call.some(
      (arg) =>
        typeof arg === 'string' &&
        arg.includes('Encountered two children with the same key'),
    ),
  );

  expect(duplicateKeyCalls).toHaveLength(0);
});

test('creation agent workspace renders streaming assistant text', () => {
  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 1,
        progressPercent: 20,
        anchors: [],
        messages: [
          {
            id: 'message-1',
            role: 'user',
            kind: 'chat',
            text: '我想做一个潮湿压抑的海上世界。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      streamingReplyText="那我先顺着这个方向收一下，开场时你更想让玩家撞上什么麻烦"
      isStreamingReply
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(screen.getByText(/那我先顺着这个方向收一下/u)).toBeTruthy();
});

test('creation agent workspace renders waiting dots before first streamed token', () => {
  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 1,
        progressPercent: 20,
        anchors: [],
        messages: [
          {
            id: 'message-1',
            role: 'user',
            kind: 'chat',
            text: '我想做一个潮湿压抑的海上世界。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      streamingReplyText=""
      isStreamingReply
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(screen.getByTestId('creation-agent-waiting-dots')).toBeTruthy();
});

test('creation agent workspace appends streaming assistant message after stable message list', () => {
  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 2,
        progressPercent: 40,
        anchors: [],
        messages: [
          {
            id: 'message-user-1',
            role: 'user',
            kind: 'chat',
            text: '我想做一个潮湿压抑的海上世界。',
          },
          {
            id: 'message-assistant-1',
            role: 'assistant',
            kind: 'chat',
            text: '我先接住这个方向。',
          },
          {
            id: 'message-user-2',
            role: 'user',
            kind: 'chat',
            text: '开场我想先撞上一场假航灯事故。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      streamingReplyText="那我就把开场事故往沉船旧案上收。"
      isStreamingReply
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  const bubbles = screen
    .getByTestId('creation-agent-message-list')
    .querySelectorAll('.whitespace-pre-wrap');
  const bubbleTexts = Array.from(bubbles).map((node) =>
    node.textContent?.trim(),
  );

  expect(bubbleTexts).toEqual([
    '我想做一个潮湿压抑的海上世界。',
    '我先接住这个方向。',
    '开场我想先撞上一场假航灯事故。',
    '那我就把开场事故往沉船旧案上收。',
  ]);
});

test('creation agent workspace hides anchors and primary action before completed progress', () => {
  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 2,
        progressPercent: 99,
        anchors: [
          {
            key: 'worldPromise',
            label: '世界承诺',
            value: '一个被潮雾改写航线秩序的群岛世界。',
            status: 'confirmed',
          },
        ],
        messages: [
          {
            id: 'message-1',
            role: 'assistant',
            kind: 'chat',
            text: '我们继续把设定收住。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(screen.queryByRole('button', { name: '生成结果页' })).toBeNull();
  expect(screen.queryByText('世界承诺')).toBeNull();
  expect(screen.queryByText('一个被潮雾改写航线秩序的群岛世界。')).toBeNull();
});

test('creation agent workspace shows primary and progress actions at completed progress', () => {
  ensureScrollApis();

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 2,
        progressPercent: 100,
        anchors: [],
        messages: [
          {
            id: 'message-1',
            role: 'assistant',
            kind: 'chat',
            text: '设定已经可以进入生成。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      quickActions={createCreationAgentChatQuickActions()}
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(screen.getByRole('button', { name: '生成结果页' })).toBeTruthy();
  expect(screen.getByRole('button', { name: '总结当前设定' })).toBeTruthy();
  expect(screen.getByRole('button', { name: '补充剩余设定' })).toBeTruthy();
});

test('creation agent workspace hides hero copy area when title and summary are absent', () => {
  if (!Element.prototype.scrollIntoView) {
    Element.prototype.scrollIntoView = () => {};
  }

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: null,
        assistantSummary: null,
        currentTurn: 2,
        progressPercent: 60,
        anchors: [],
        messages: [
          {
            id: 'message-1',
            role: 'assistant',
            kind: 'chat',
            text: '继续把设定收束到可生成状态。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(screen.queryByText('统一共创')).toBeNull();
  expect(screen.getByText('创作进度')).toBeTruthy();
});

test('creation agent workspace stops auto-follow when user scrolls away from bottom', () => {
  ensureScrollApis();

  const scrollToSpy = vi.fn();
  HTMLElement.prototype.scrollTo = scrollToSpy;

  const { rerender } = render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 1,
        progressPercent: 20,
        anchors: [],
        messages: [
          {
            id: 'message-1',
            role: 'assistant',
            kind: 'chat',
            text: '先确定一下世界方向。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  const messageList = screen.getByTestId('creation-agent-message-list');
  let scrollTop = 120;

  Object.defineProperty(messageList, 'scrollHeight', {
    configurable: true,
    value: 640,
  });
  Object.defineProperty(messageList, 'clientHeight', {
    configurable: true,
    value: 240,
  });
  Object.defineProperty(messageList, 'scrollTop', {
    configurable: true,
    get: () => scrollTop,
    set: (value) => {
      scrollTop = Number(value);
    },
  });

  fireEvent.scroll(messageList);
  scrollToSpy.mockClear();

  rerender(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: '统一共创',
        currentTurn: 1,
        progressPercent: 20,
        anchors: [],
        messages: [
          {
            id: 'message-1',
            role: 'assistant',
            kind: 'chat',
            text: '先确定一下世界方向。',
          },
        ],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      streamingReplyText="继续往下收束开场冲突。"
      isStreamingReply
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  expect(scrollToSpy).not.toHaveBeenCalled();
});

test('creation agent workspace appends parsed document text into composer', async () => {
  ensureScrollApis();

  vi.spyOn(
    creationAgentServices,
    'parseCreationAgentDocumentInput',
  ).mockResolvedValue({
    document: {
      fileName: '世界设定.md',
      contentType: 'text/markdown',
      sizeBytes: 24,
      text: '第一章：潮湿的港口',
    },
  });

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: null,
        currentTurn: 0,
        progressPercent: 0,
        anchors: [],
        messages: [],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  fireEvent.change(screen.getByPlaceholderText('输入消息'), {
    target: {
      value: '已有方向',
    },
  });

  const input = document.querySelector<HTMLInputElement>('input[type="file"]');
  expect(input).toBeTruthy();

  fireEvent.change(input!, {
    target: {
      files: [new File(['unused'], '世界设定.md', { type: 'text/markdown' })],
    },
  });

  await waitFor(() => {
    expect(
      (screen.getByPlaceholderText('输入消息') as HTMLTextAreaElement).value,
    ).toBe('已有方向\n\n第一章：潮湿的港口');
  });
});

test('creation agent workspace shows document parse error near composer', async () => {
  ensureScrollApis();

  vi.spyOn(
    creationAgentServices,
    'parseCreationAgentDocumentInput',
  ).mockRejectedValue(new Error('暂时只支持 txt、md、csv、json 文本文档。'));

  render(
    <CreationAgentWorkspace
      session={{
        sessionId: 'creation-agent-session-1',
        title: null,
        currentTurn: 0,
        progressPercent: 0,
        anchors: [],
        messages: [],
      }}
      theme={testTheme}
      loadingText="正在准备"
      composerPlaceholder="输入消息"
      primaryActionLabel="生成结果页"
      onBack={() => {}}
      onSubmitText={() => {}}
      onPrimaryAction={() => {}}
    />,
  );

  const input = document.querySelector<HTMLInputElement>('input[type="file"]');
  expect(input).toBeTruthy();

  fireEvent.change(input!, {
    target: {
      files: [new File(['unused'], '世界设定.docx')],
    },
  });

  await waitFor(() => {
    expect(
      screen.getByText('暂时只支持 txt、md、csv、json 文本文档。'),
    ).toBeTruthy();
  });
});
