import {
  useEffect,
  useMemo,
  useRef,
  useState,
  type ChangeEvent,
  type KeyboardEvent,
} from 'react';
import {
  Bot,
  Check,
  Clock3,
  Cloud,
  Download,
  Film,
  ImageIcon,
  Loader2,
  Maximize2,
  Plus,
  RefreshCw,
  Send,
  Sparkles,
  Upload,
  WandSparkles,
  X,
} from 'lucide-react';
import { toast } from 'sonner';
import { Button } from '@/components/ui/button';
import {
  Dialog,
  DialogClose,
  DialogContent,
  DialogDescription,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog';
import { Textarea } from '@/components/ui/textarea';
import {
  IMAGE_WORKSPACE_CREATE_PROJECT_EVENT,
  resolveImageWorkspaceAssetUrl,
  saveImageWorkspaceAsset,
  uploadImageWorkspaceAsset,
} from '@/lib/image-workspace';
import { cn } from '@/lib/utils';
import { useImageWorkspaceStore } from '@/stores/image-workspace';
import { useAuthStore } from '@/stores/auth';
import type {
  DesignAsset,
  DesignGenerationQuote,
  DesignGenerationTask,
  DesignMessage,
  DesignTaskStatus,
} from '../../../shared/image-workspace';

const ACTIVE_TASK_STATUSES = new Set<DesignTaskStatus>(['queued', 'running']);
const TASK_FAILURE_MESSAGES: Record<string, string> = {
  policy_blocked: '内容可能涉及版权或安全风险，请调整设计后重试',
  budget_denied: '当前设计点不足，无法开始生成',
  quote_expired: '生成方案已过期，请重新确认',
  asset_rejected: '生成结果未通过媒体检查，请调整设计后重试',
  dependency_unavailable: '生成服务暂时不可用，请稍后重试',
  submission_unknown: '正在确认生成请求状态，请稍后查看',
  submission_not_accepted: '生成请求未被服务接受，请重新确认后再试',
  generation_failed: '生成未完成，请调整设计或稍后重试',
  result_persist_failed: '生成结果保存失败，请稍后重试',
  cancelled: '生成任务已取消',
  expired: '生成任务已过期，请重新确认',
};
const CONFIRMATION_UNAVAILABLE_MESSAGE = (
  '当前没有可确认的生成报价，请先让设计 Agent 完成方案与报价。'
);
const GENERATION_CONFIRMATION_INTENTS = new Set([
  '确认生成',
  '确认开始生成',
  '确认并开始生成',
  '确认开始制作',
  '确认并开始制作',
]);
const VIDEO_FIRST_FRAME_PICKER_REPLY = '从作品列表选择图片';

function isGenerationConfirmationIntent(message: string): boolean {
  const normalized = message.trim().replace(/[\s，,。.!！?？、]/g, '');
  return GENERATION_CONFIRMATION_INTENTS.has(normalized);
}

function taskStatusLabel(status: DesignTaskStatus): string {
  if (status === 'queued') return '排队中';
  if (status === 'running') return '生成中';
  if (status === 'succeeded') return '已完成';
  if (status === 'cancelled') return '已取消';
  return '失败';
}

function taskFailureMessage(code: string): string {
  return TASK_FAILURE_MESSAGES[code] ?? '生成未完成，请稍后重试';
}

function mediumLabel(medium: 'image' | 'video'): string {
  return medium === 'video' ? '视频' : '图片';
}

function activeQuote(messages: DesignMessage[]): DesignGenerationQuote | null {
  for (let index = messages.length - 1; index >= 0; index -= 1) {
    const quote = messages[index].generationQuote;
    if (quote?.status === 'active') return quote;
  }
  return null;
}

type RenderedDesignMessage = DesignMessage & { streaming?: boolean };

function AssetPreview({ asset }: { asset: DesignAsset }) {
  const [url, setUrl] = useState<string | null>(null);
  const [previewOpen, setPreviewOpen] = useState(false);
  const [downloading, setDownloading] = useState(false);

  useEffect(() => {
    let cancelled = false;
    void resolveImageWorkspaceAssetUrl(asset.contentPath).then((nextUrl) => {
      if (!cancelled) setUrl(nextUrl);
    });
    return () => {
      cancelled = true;
    };
  }, [asset.contentPath]);

  if (!url) {
    return (
      <div role="status" className="flex aspect-video items-center justify-center bg-surface-subtle">
        <Loader2 className="h-5 w-5 animate-spin text-brand" />
      </div>
    );
  }

  if (asset.mimeType.startsWith('video/')) {
    return (
      <video
        controls
        preload="metadata"
        src={url}
        className="aspect-video w-full bg-black object-contain"
      />
    );
  }

  const download = async () => {
    if (downloading) return;
    setDownloading(true);
    try {
      const result = await saveImageWorkspaceAsset(asset);
      if (result.status === 'saved') {
        toast.success('图片已保存到本地');
      }
    } catch (error) {
      toast.error('图片下载失败', {
        description: error instanceof Error ? error.message : String(error),
      });
    } finally {
      setDownloading(false);
    }
  };

  return (
    <>
      <div className="bg-surface-subtle">
        <button
          type="button"
          aria-label="放大查看生成图片"
          className="group relative block w-full cursor-zoom-in overflow-hidden focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand/40 focus-visible:ring-inset"
          onClick={() => setPreviewOpen(true)}
        >
          <img
            src={url}
            alt="AI 设计生成结果"
            className="aspect-video w-full bg-surface-subtle object-contain outline outline-1 -outline-offset-1 outline-black/10 transition-transform duration-200 group-hover:scale-[1.015] group-focus-visible:scale-[1.015]"
          />
          <span className="pointer-events-none absolute inset-x-0 bottom-0 flex items-center justify-center gap-1.5 bg-gradient-to-t from-black/55 to-transparent px-3 pb-2.5 pt-8 text-[11px] font-semibold text-white opacity-0 transition-opacity duration-200 group-hover:opacity-100 group-focus-visible:opacity-100">
            <Maximize2 className="h-3.5 w-3.5" />
            查看大图
          </span>
        </button>
        <div className="grid grid-cols-2 gap-2 border-t border-border/60 bg-background/95 p-2">
          <Button
            type="button"
            size="sm"
            variant="ghost"
            className="h-10 rounded-xl text-xs font-semibold transition-transform active:scale-[0.96]"
            onClick={() => setPreviewOpen(true)}
          >
            <Maximize2 className="mr-1.5 h-3.5 w-3.5" />
            查看大图
          </Button>
          <Button
            type="button"
            size="sm"
            variant="ghost"
            aria-label={downloading ? '正在保存生成图片' : '下载生成图片'}
            aria-busy={downloading}
            className="h-10 rounded-xl text-xs font-semibold transition-transform active:scale-[0.96]"
            disabled={downloading}
            onClick={() => void download()}
          >
            {downloading
              ? <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" />
              : <Download className="mr-1.5 h-3.5 w-3.5" />}
            {downloading ? '保存中' : '下载图片'}
          </Button>
        </div>
      </div>

      <Dialog open={previewOpen} onOpenChange={setPreviewOpen}>
        <DialogContent className="max-h-[92vh] max-w-6xl gap-0 overflow-hidden p-0">
          <DialogHeader className="flex-row items-center justify-between space-y-0 border-b border-border/70 px-4 py-3 sm:px-5">
            <div className="min-w-0 text-left">
              <DialogTitle className="text-base">图片预览</DialogTitle>
              <DialogDescription className="mt-0.5 text-xs tabular-nums">
                {asset.width} × {asset.height} · 原始生成结果
              </DialogDescription>
            </div>
            <div className="flex shrink-0 items-center gap-2">
              <Button
                type="button"
                size="sm"
                aria-busy={downloading}
                className="h-10 rounded-xl bg-brand px-3 text-xs font-semibold text-primary-foreground transition-transform active:scale-[0.96]"
                disabled={downloading}
                onClick={() => void download()}
              >
                {downloading
                  ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
                  : <Download className="mr-1.5 h-4 w-4" />}
                {downloading ? '正在保存' : '下载原图'}
              </Button>
              <DialogClose asChild>
                <Button
                  type="button"
                  size="icon"
                  variant="ghost"
                  aria-label="关闭图片预览"
                  className="h-10 w-10 rounded-xl transition-transform active:scale-[0.96]"
                >
                  <X className="h-4 w-4" />
                </Button>
              </DialogClose>
            </div>
          </DialogHeader>
          <div className="flex min-h-0 items-center justify-center bg-black/90 p-3 sm:p-5">
            <img
              src={url}
              alt="AI 设计大图预览"
              className="max-h-[calc(92vh-76px)] max-w-full rounded-lg object-contain outline outline-1 outline-white/10"
            />
          </div>
        </DialogContent>
      </Dialog>
    </>
  );
}

function FirstFrameAssetOption({
  asset,
  busy,
  onSelect,
}: {
  asset: DesignAsset;
  busy: boolean;
  onSelect(): void;
}) {
  const [url, setUrl] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    void resolveImageWorkspaceAssetUrl(asset.contentPath)
      .then((nextUrl) => {
        if (!cancelled) setUrl(nextUrl);
      })
      .catch(() => {
        if (!cancelled) setUrl(null);
      });
    return () => {
      cancelled = true;
    };
  }, [asset.contentPath]);

  return (
    <article className="overflow-hidden rounded-2xl bg-background shadow-soft ring-1 ring-black/10 dark:ring-white/10">
      <div className="flex aspect-video items-center justify-center bg-surface-subtle">
        {url ? (
          <img
            src={url}
            alt="可选作品图片"
            className="h-full w-full object-contain outline outline-1 -outline-offset-1 outline-black/10 dark:outline-white/10"
          />
        ) : <Loader2 className="h-5 w-5 animate-spin text-muted-foreground" />}
      </div>
      <div className="flex items-center justify-between gap-3 p-3">
        <span className="text-xs font-medium tabular-nums text-muted-foreground">
          {asset.width} × {asset.height}
        </span>
        <Button
          type="button"
          size="sm"
          className="h-10 rounded-xl px-3 text-xs font-semibold transition-transform active:scale-[0.96]"
          disabled={busy || !url}
          onClick={onSelect}
        >
          {busy ? <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" /> : null}
          选择此图片
        </Button>
      </div>
    </article>
  );
}

function TaskCard({ task }: { task: DesignGenerationTask }) {
  const active = ACTIVE_TASK_STATUSES.has(task.status);
  return (
    <article
      data-testid={`design-task-${task.taskId}`}
      className="overflow-hidden rounded-2xl border border-border/70 bg-background shadow-soft"
    >
      {task.resultAssets.map((asset) => (
        <AssetPreview key={asset.assetId} asset={asset} />
      ))}
      <div className="p-3">
        <div className="flex items-center justify-between gap-2">
          <span className="inline-flex items-center gap-1.5 text-xs font-semibold">
            {task.medium === 'video'
              ? <Film className="h-3.5 w-3.5 text-brand" />
              : <ImageIcon className="h-3.5 w-3.5 text-brand" />}
            {mediumLabel(task.medium)}任务
          </span>
          <span
            className={cn(
              'inline-flex items-center gap-1.5 rounded-full px-2 py-1 text-[10px] font-semibold',
              task.status === 'succeeded' && 'bg-emerald-50 text-emerald-700',
              task.status === 'failed' && 'bg-destructive/10 text-destructive',
              active && 'bg-brand-soft text-brand',
              task.status === 'cancelled' && 'bg-surface-tertiary text-muted-foreground',
            )}
          >
            {active ? <Loader2 className="h-3 w-3 animate-spin" /> : null}
            {task.status === 'succeeded' ? <Check className="h-3 w-3" /> : null}
            {taskStatusLabel(task.status)}
          </span>
        </div>
        <p className="mt-2 line-clamp-3 text-xs font-medium leading-5 text-muted-foreground">
          {task.briefSummary}
        </p>
        {task.failureCode ? (
          <p role="alert" className="mt-2 rounded-lg bg-destructive/10 px-2 py-1.5 text-[11px] font-semibold text-destructive">
            {taskFailureMessage(task.failureCode)}
          </p>
        ) : null}
      </div>
    </article>
  );
}

function QuoteCard({
  quote,
  busy,
  onConfirm,
}: {
  quote: DesignGenerationQuote;
  busy: boolean;
  onConfirm: () => void;
}) {
  return (
    <div
      data-testid={`design-quote-${quote.quoteId}`}
      className="mt-3 rounded-2xl border border-brand/25 bg-brand-soft/60 p-3"
    >
      <div className="flex flex-wrap items-center justify-between gap-2">
        <span className="inline-flex items-center gap-1.5 text-xs font-semibold text-foreground">
          <WandSparkles className="h-4 w-4 text-brand" />
          {mediumLabel(quote.medium)}生成方案
        </span>
        <span className="rounded-full bg-background px-2 py-1 text-[10px] font-semibold text-muted-foreground">
          {quote.quotedDesignPoints} 设计点
        </span>
      </div>
      <p className="mt-2 text-xs font-medium leading-5 text-muted-foreground">
        {quote.briefSummary}
      </p>
      <Button
        type="button"
        size="sm"
        className="mt-3 h-8 rounded-full bg-brand px-3 text-xs font-semibold text-primary-foreground"
        onClick={onConfirm}
        disabled={busy}
      >
        {busy ? <Loader2 className="mr-1.5 h-3.5 w-3.5 animate-spin" /> : <Sparkles className="mr-1.5 h-3.5 w-3.5" />}
        确认并开始生成
      </Button>
    </div>
  );
}

export function ImageCanvas() {
  const authenticated = useAuthStore((state) => state.isAuthenticated());
  const status = useImageWorkspaceStore((state) => state.status);
  const bootstrap = useImageWorkspaceStore((state) => state.bootstrap);
  const workspace = useImageWorkspaceStore((state) => state.workspace);
  const tasks = useImageWorkspaceStore((state) => state.tasks);
  const pendingTurn = useImageWorkspaceStore((state) => state.pendingTurn);
  const workspaceError = useImageWorkspaceStore((state) => state.error);
  const load = useImageWorkspaceStore((state) => state.load);
  const refreshWorkspace = useImageWorkspaceStore((state) => state.refreshWorkspace);
  const refreshTasks = useImageWorkspaceStore((state) => state.refreshTasks);
  const connectTaskStream = useImageWorkspaceStore((state) => state.connectTaskStream);
  const disconnectTaskStream = useImageWorkspaceStore((state) => state.disconnectTaskStream);
  const sendMessage = useImageWorkspaceStore((state) => state.sendMessage);
  const confirmGeneration = useImageWorkspaceStore((state) => state.confirmGeneration);
  const [prompt, setPrompt] = useState('');
  const [submitting, setSubmitting] = useState(false);
  const [confirmingQuoteId, setConfirmingQuoteId] = useState<string | null>(null);
  const [actionError, setActionError] = useState<string | null>(null);
  const [firstFramePickerOpen, setFirstFramePickerOpen] = useState(false);
  const [firstFrameBusyKey, setFirstFrameBusyKey] = useState<string | null>(null);
  const [firstFrameError, setFirstFrameError] = useState<string | null>(null);
  const [uploadedFirstFrameAsset, setUploadedFirstFrameAsset] = useState<DesignAsset | null>(null);
  const conversationEndRef = useRef<HTMLDivElement | null>(null);

  const quote = useMemo(
    () => workspace ? activeQuote(workspace.messages) : null,
    [workspace],
  );
  const generationAvailable = bootstrap?.capabilities.generation ?? false;
  const firstFrameAssets = useMemo(
    () => {
      const assets = tasks
        .filter((task) => task.status === 'succeeded')
        .flatMap((task) => task.resultAssets)
        .filter((asset) => asset.mediaType === 'image' && asset.mimeType === 'image/png');
      if (uploadedFirstFrameAsset
        && uploadedFirstFrameAsset.workspaceId === workspace?.workspaceId) {
        assets.unshift(uploadedFirstFrameAsset);
      }
      return [...new Map(assets.map((asset) => [asset.assetId, asset])).values()];
    },
    [tasks, uploadedFirstFrameAsset, workspace?.workspaceId],
  );
  const conversationMessages = useMemo<RenderedDesignMessage[]>(() => {
    if (!workspace) return [];
    const messages: RenderedDesignMessage[] = [...workspace.messages];
    if (!pendingTurn || pendingTurn.workspaceId !== workspace.workspaceId) return messages;
    if (pendingTurn.userText) {
      messages.push({
        id: `${pendingTurn.clientTurnId}:user`,
        role: 'user',
        kind: 'user',
        text: pendingTurn.userText,
        quickReplies: [],
        generationQuote: null,
        turnRevision: pendingTurn.turnRevision,
        createdAt: pendingTurn.createdAt,
      });
    }
    messages.push({
      id: `${pendingTurn.clientTurnId}:assistant`,
      role: 'assistant',
      kind: 'reply',
      text: pendingTurn.assistantText,
      quickReplies: [],
      generationQuote: null,
      turnRevision: pendingTurn.turnRevision,
      createdAt: pendingTurn.createdAt,
      streaming: true,
    });
    return messages;
  }, [pendingTurn, workspace]);
  const taskWorkspaceId = workspace?.workspaceId ?? null;
  const hasActiveTasks = tasks.some((task) => ACTIVE_TASK_STATUSES.has(task.status));

  useEffect(() => {
    if (status === 'idle' || (status === 'auth-required' && authenticated)) void load();
  }, [authenticated, load, status]);

  useEffect(() => {
    if (!taskWorkspaceId) return;
    connectTaskStream();
    return disconnectTaskStream;
  }, [connectTaskStream, disconnectTaskStream, taskWorkspaceId]);

  useEffect(() => {
    if (typeof conversationEndRef.current?.scrollIntoView === 'function') {
      conversationEndRef.current.scrollIntoView({ block: 'end' });
    }
  }, [conversationMessages.length, pendingTurn?.assistantText]);

  const handleConfirm = async (quoteId: string) => {
    if (!workspace || confirmingQuoteId) return;
    if (!generationAvailable) {
      setActionError(CONFIRMATION_UNAVAILABLE_MESSAGE);
      return;
    }
    const requestedWorkspaceId = workspace.workspaceId;
    setConfirmingQuoteId(quoteId);
    setActionError(null);
    try {
      await confirmGeneration(quoteId);
    } catch (error) {
      if (useImageWorkspaceStore.getState().activeWorkspaceId !== requestedWorkspaceId) return;
      setActionError(error instanceof Error ? error.message : String(error));
    } finally {
      setConfirmingQuoteId(null);
    }
  };

  const handleSend = async (override?: string) => {
    const message = (override ?? prompt).trim();
    if (!workspace || !message || submitting) return;
    if (isGenerationConfirmationIntent(message)) {
      setPrompt('');
      if (quote && generationAvailable) {
        await handleConfirm(quote.quoteId);
      } else {
        setActionError(CONFIRMATION_UNAVAILABLE_MESSAGE);
      }
      return;
    }
    const requestedWorkspaceId = workspace.workspaceId;
    setSubmitting(true);
    setActionError(null);
    setPrompt('');
    try {
      await sendMessage(message);
    } catch (error) {
      if (useImageWorkspaceStore.getState().activeWorkspaceId !== requestedWorkspaceId) return;
      setPrompt((current) => current.trim() ? current : message);
      setActionError(error instanceof Error ? error.message : String(error));
    } finally {
      setSubmitting(false);
    }
  };

  const handleComposerKeyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
    if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
      event.preventDefault();
      void handleSend();
    }
  };

  const handleFirstFrameSelect = async (asset: DesignAsset) => {
    if (!workspace || submitting || firstFrameBusyKey) return;
    const requestedWorkspaceId = workspace.workspaceId;
    setFirstFrameBusyKey(asset.assetId);
    setFirstFrameError(null);
    setActionError(null);
    setSubmitting(true);
    setFirstFramePickerOpen(false);
    try {
      await sendMessage('使用作品图片作为视频首帧', [asset.assetId]);
    } catch (error) {
      if (useImageWorkspaceStore.getState().activeWorkspaceId === requestedWorkspaceId) {
        setActionError(error instanceof Error ? error.message : String(error));
      }
    } finally {
      setFirstFrameBusyKey(null);
      setSubmitting(false);
    }
  };

  const handleFirstFrameUpload = async (event: ChangeEvent<HTMLInputElement>) => {
    const file = event.currentTarget.files?.[0];
    event.currentTarget.value = '';
    if (!workspace || !file || submitting || firstFrameBusyKey) return;
    const requestedWorkspaceId = workspace.workspaceId;
    setFirstFrameBusyKey('upload');
    setFirstFrameError(null);
    setSubmitting(true);
    try {
      const asset = await uploadImageWorkspaceAsset(requestedWorkspaceId, file);
      setUploadedFirstFrameAsset(asset);
      await sendMessage('使用上传的图片作为视频首帧', [asset.assetId]);
      if (useImageWorkspaceStore.getState().activeWorkspaceId === requestedWorkspaceId) {
        setFirstFramePickerOpen(false);
      }
    } catch (error) {
      if (useImageWorkspaceStore.getState().activeWorkspaceId === requestedWorkspaceId) {
        setFirstFrameError(error instanceof Error ? error.message : String(error));
      }
    } finally {
      setFirstFrameBusyKey(null);
      setSubmitting(false);
    }
  };

  const openCreateProject = () => {
    window.dispatchEvent(new Event(IMAGE_WORKSPACE_CREATE_PROJECT_EVENT));
  };

  if (status === 'idle' || status === 'loading') {
    return (
      <div data-testid="image-canvas-page" className="-m-5 flex min-h-full items-center justify-center bg-background p-6 text-foreground sm:-m-6">
        <div role="status" className="surface-card flex items-center gap-3 rounded-2xl border border-border/70 bg-background px-5 py-4 text-sm font-semibold shadow-float">
          <Loader2 className="h-5 w-5 animate-spin text-brand" />
          <span>正在读取 AI 设计项目</span>
        </div>
      </div>
    );
  }

  if (status === 'unavailable' || status === 'error' || !bootstrap) {
    return (
      <div data-testid="image-canvas-page" className="-m-5 flex min-h-full items-center justify-center bg-background p-6 text-foreground sm:-m-6">
        <section data-testid="image-workspace-unavailable" className="surface-card w-full max-w-md rounded-2xl border border-border/70 bg-background p-8 text-center shadow-float">
          <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-2xl bg-surface-tertiary text-foreground">
            <Cloud className="h-6 w-6" />
          </div>
          <h1 className="mt-5 text-2xl font-semibold tracking-[-0.025em]">AI 设计暂不可用</h1>
          <p className="mx-auto mt-3 max-w-sm text-sm font-medium leading-6 text-muted-foreground">
            暂时无法连接设计服务，请检查登录状态或稍后重试。
          </p>
          {workspaceError ? <p role="alert" className="mt-3 rounded-xl bg-destructive/10 px-3 py-2 text-xs font-medium text-destructive">{workspaceError}</p> : null}
          <Button type="button" variant="outline" className="mt-5" onClick={() => void load()}>
            <RefreshCw className="mr-2 h-4 w-4" />
            重新连接
          </Button>
        </section>
      </div>
    );
  }

  if (bootstrap.workspaces.length === 0) {
    return (
      <div data-testid="image-canvas-page" className="-m-5 flex min-h-full items-center justify-center bg-background p-6 text-foreground sm:-m-6">
        <section className="surface-card w-full max-w-md rounded-2xl border border-border/70 bg-background p-8 text-center shadow-float">
          <div className="mx-auto flex h-12 w-12 items-center justify-center rounded-2xl bg-brand-soft text-brand">
            <WandSparkles className="h-6 w-6" />
          </div>
          <h1 className="mt-5 text-2xl font-semibold tracking-[-0.025em]">创建第一个设计项目</h1>
          <p className="mx-auto mt-3 max-w-sm text-sm font-medium leading-6 text-muted-foreground">
            每个项目会持续保留与设计 Agent 的对话、确认过的方向，以及图片和视频生成任务。
          </p>
          <Button type="button" className="mt-5 bg-brand text-primary-foreground" onClick={openCreateProject}>
            <Plus className="mr-2 h-4 w-4" />
            新建设计项目
          </Button>
        </section>
      </div>
    );
  }

  if (!workspace) {
    return (
      <div data-testid="image-canvas-page" className="-m-5 flex min-h-full items-center justify-center bg-background p-6 text-foreground sm:-m-6">
        <Loader2 role="status" className="h-6 w-6 animate-spin text-brand" />
      </div>
    );
  }

  return (
    <div data-testid="image-canvas-page" className="-m-5 flex h-full min-h-0 min-w-0 flex-col overflow-hidden bg-background text-foreground sm:-m-6">
      <header className="glass-surface flex shrink-0 items-center justify-between gap-3 border-b border-border/70 bg-background/80 px-4 py-3 sm:px-6">
        <div className="flex min-w-0 items-center gap-3">
          <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-brand-soft text-brand">
            <WandSparkles className="h-4 w-4" />
          </div>
          <div className="min-w-0">
            <div className="flex min-w-0 items-center gap-2">
              <h1 className="truncate text-base font-semibold tracking-[-0.015em]">{workspace.title}</h1>
              <span className="hidden rounded-md bg-surface-tertiary px-1.5 py-0.5 text-[10px] font-semibold text-muted-foreground sm:inline-flex">
                AI Design
              </span>
            </div>
            <p className="mt-0.5 flex min-w-0 items-center gap-1.5 text-xs font-medium text-muted-foreground">
              <Bot className="h-3.5 w-3.5 shrink-0" />
              <span className="truncate">
                {workspace.brief.ready ? workspace.brief.summary : '设计 Agent 正在与你确认创作方向'}
              </span>
            </p>
          </div>
        </div>
        <Button
          type="button"
          variant="ghost"
          size="icon"
          aria-label="刷新设计项目"
          className="h-9 w-9 rounded-full"
          onClick={() => {
            void refreshWorkspace()
              .then(() => refreshTasks())
              .catch(() => undefined);
          }}
        >
          <RefreshCw className="h-4 w-4" />
        </Button>
      </header>

      <div className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden lg:flex-row">
        <section className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden lg:h-full">
          <main data-testid="image-workspace-conversation" className="min-h-0 flex-1 overflow-y-auto overscroll-contain px-4 py-5 sm:px-6">
            <div className="mx-auto flex min-h-full w-full max-w-3xl flex-col pb-4">
              {conversationMessages.length === 0 ? (
                <div data-testid="image-workspace-empty-state" className="flex flex-1 items-center justify-center py-10 text-center">
                  <div className="max-w-xl">
                    <div className="mx-auto flex h-14 w-14 items-center justify-center rounded-2xl bg-brand-soft text-brand">
                      <Sparkles className="h-6 w-6" />
                    </div>
                    <h2 className="mt-5 text-2xl font-medium tracking-[-0.025em]">先聊清楚，再开始创作</h2>
                    <p className="mx-auto mt-3 text-sm font-medium leading-6 text-muted-foreground">
                      告诉设计 Agent 你的用途、受众和想表达的感觉。它会帮助你收敛方向，确认后再创建生图或生视频任务。
                    </p>
                  </div>
                </div>
              ) : (
                <div className="flex flex-col gap-7">
                  {conversationMessages.map((message) => {
                    const assistant = message.role === 'assistant';
                    return (
                      <article key={message.id} className={cn('flex gap-3', assistant ? 'justify-start' : 'justify-end')}>
                        {assistant ? (
                          <div className="mt-1 flex h-8 w-8 shrink-0 items-center justify-center rounded-lg border border-border/70 bg-surface-subtle">
                            <Bot className="h-4 w-4" />
                          </div>
                        ) : null}
                        <div className={cn('min-w-0', assistant ? 'w-full max-w-2xl' : 'max-w-[85%]')}>
                          <div className={cn('mb-2 text-[11px] font-semibold text-muted-foreground', !assistant && 'text-right')}>
                            {assistant ? '设计 Agent' : '你'}
                          </div>
                          <div className={assistant ? 'chat-assistant-message-surface' : 'chat-user-message-surface rounded-2xl px-4 py-3'}>
                            <p className="whitespace-pre-wrap text-sm font-medium leading-6">
                              {message.text}
                              {message.streaming && message.text ? (
                                <span
                                  aria-hidden="true"
                                  className="ml-0.5 inline-block h-4 w-0.5 animate-pulse bg-brand align-middle"
                                />
                              ) : null}
                              {message.streaming && !message.text ? (
                                <span
                                  role="status"
                                  aria-label="设计 Agent 正在回复"
                                  className="inline-flex items-center gap-2 text-muted-foreground"
                                >
                                  <Loader2 className="h-3.5 w-3.5 animate-spin" />
                                  正在整理设计方向…
                                </span>
                              ) : null}
                            </p>
                            {message.generationQuote?.status === 'active' ? (
                              <QuoteCard
                                quote={message.generationQuote}
                                busy={confirmingQuoteId === message.generationQuote.quoteId}
                                onConfirm={() => void handleConfirm(message.generationQuote!.quoteId)}
                              />
                            ) : null}
                            {assistant && message.quickReplies.length > 0 ? (
                              <div className="mt-3 flex flex-wrap gap-2">
                                {message.quickReplies.map((reply) => (
                                  <Button
                                    key={reply}
                                    type="button"
                                    variant="outline"
                                    size="sm"
                                     className="h-8 rounded-full px-3 text-xs font-semibold"
                                     onClick={() => {
                                       if (reply === VIDEO_FIRST_FRAME_PICKER_REPLY) {
                                         setFirstFrameError(null);
                                         setFirstFramePickerOpen(true);
                                         return;
                                       }
                                       if (!isGenerationConfirmationIntent(reply)) {
                                        setPrompt(reply);
                                        return;
                                      }
                                      if (quote && generationAvailable) {
                                        void handleConfirm(quote.quoteId);
                                        return;
                                      }
                                      setPrompt('');
                                      setActionError(CONFIRMATION_UNAVAILABLE_MESSAGE);
                                    }}
                                  >
                                    {reply}
                                  </Button>
                                ))}
                              </div>
                            ) : null}
                          </div>
                        </div>
                      </article>
                    );
                  })}
                  <div ref={conversationEndRef} aria-hidden="true" />
                </div>
              )}
            </div>
          </main>

          <form
            data-testid="image-workspace-composer"
            className="shrink-0 bg-background px-4 pb-4 pt-2 sm:px-6"
            onSubmit={(event) => {
              event.preventDefault();
              void handleSend();
            }}
          >
            <div className="mx-auto w-full max-w-3xl">
              <div className="chat-composer-surface rounded-2xl border border-border/70 bg-background p-3 shadow-soft focus-within:ring-1 focus-within:ring-foreground/10">
                {!generationAvailable ? (
                  <p className="mb-2 rounded-xl bg-amber-500/10 px-3 py-2 text-xs font-semibold text-amber-700 dark:text-amber-300">
                    当前环境仅支持设计沟通，图片/视频生成能力尚未启用。
                  </p>
                ) : null}
                <Textarea
                  aria-label="设计需求"
                  value={prompt}
                  onChange={(event) => {
                    setPrompt(event.target.value);
                    setActionError(null);
                  }}
                  onKeyDown={handleComposerKeyDown}
                  rows={3}
                  placeholder="告诉设计 Agent：要做什么、给谁看、希望是什么感觉…"
                  className="max-h-40 !min-h-[72px] resize-none border-0 bg-transparent p-0 text-sm font-medium leading-6 shadow-none focus-visible:ring-0"
                />
                {actionError ? <p role="alert" className="mt-2 rounded-xl bg-destructive/10 px-3 py-2 text-xs font-semibold text-destructive">{actionError}</p> : null}
                <div className="mt-2 flex items-center justify-between gap-3 border-t border-border/70 pt-2">
                  <span className="text-[10px] font-medium text-muted-foreground">⌘/Ctrl + Enter 发送</span>
                  <Button type="submit" className="h-9 rounded-full bg-brand px-4 text-primary-foreground" disabled={submitting || !prompt.trim()}>
                    {submitting ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Send className="mr-2 h-4 w-4" />}
                    发送给设计 Agent
                  </Button>
                </div>
              </div>
            </div>
          </form>
        </section>

        <div data-testid="design-pane-divider" aria-hidden="true" className="hidden w-px shrink-0 bg-border/70 lg:block" />

        <aside data-testid="design-task-list" className="min-h-0 w-full shrink-0 overflow-hidden border-t border-border/70 bg-surface-subtle/45 lg:h-full lg:w-80 lg:border-t-0">
          <div className="flex items-center justify-between border-b border-border/70 px-4 py-3">
            <div>
              <h2 className="text-sm font-semibold">生成任务</h2>
              <p className="mt-0.5 text-[10px] font-medium text-muted-foreground">图片与视频任务统一展示</p>
            </div>
            {hasActiveTasks ? <Clock3 className="h-4 w-4 animate-pulse text-brand" /> : null}
          </div>
          <div data-testid="design-task-scroll" className="h-full max-h-72 space-y-3 overflow-y-auto overscroll-contain p-3 lg:h-[calc(100%-57px)] lg:max-h-none">
            {tasks.length === 0 ? (
              <div className="rounded-2xl border border-dashed border-border/80 bg-background/70 px-4 py-7 text-center">
                <WandSparkles className="mx-auto h-5 w-5 text-muted-foreground" />
                <p className="mt-3 text-xs font-semibold">还没有生成任务</p>
                <p className="mt-1 text-[11px] font-medium leading-5 text-muted-foreground">
                  与 Agent 确认设计方向后，任务会出现在这里。
                </p>
              </div>
            ) : tasks.map((task) => <TaskCard key={task.taskId} task={task} />)}
          </div>
        </aside>
      </div>

      <Dialog
        open={firstFramePickerOpen}
        onOpenChange={(open) => {
          if (!open && firstFrameBusyKey) return;
          setFirstFramePickerOpen(open);
          if (!open) setFirstFrameError(null);
        }}
      >
        <DialogContent className="max-h-[88vh] max-w-4xl grid-rows-[auto_minmax(0,1fr)] gap-0 overflow-hidden p-0">
          <DialogHeader className="flex-row items-start justify-between space-y-0 px-5 py-4 sm:px-6">
            <div className="min-w-0 text-left">
              <DialogTitle className="text-balance text-base">选择视频首帧</DialogTitle>
              <DialogDescription className="mt-1 text-pretty text-xs leading-5">
                从本项目作品中选择一张已完成图片，或上传本地图片作为视频起始画面。
              </DialogDescription>
            </div>
            <DialogClose asChild>
              <Button
                type="button"
                size="icon"
                variant="ghost"
                aria-label="关闭首帧选择"
                className="h-10 w-10 shrink-0 rounded-xl transition-transform active:scale-[0.96]"
                disabled={Boolean(firstFrameBusyKey)}
              >
                <X className="h-4 w-4" />
              </Button>
            </DialogClose>
          </DialogHeader>

          <div className="min-h-0 overflow-y-auto bg-surface-subtle/55 px-5 py-4 sm:px-6">
            <div className="flex flex-wrap items-center justify-between gap-3">
              <div>
                <h3 className="text-sm font-semibold">本项目作品</h3>
                <p className="mt-0.5 text-xs text-muted-foreground">
                  仅展示已成功生成的 PNG 图片
                </p>
              </div>
              <label
                htmlFor="design-first-frame-upload"
                aria-busy={firstFrameBusyKey === 'upload'}
                className={cn(
                  'inline-flex h-10 cursor-pointer items-center justify-center rounded-xl bg-brand px-3 text-xs font-semibold text-primary-foreground shadow-sm transition-transform active:scale-[0.96]',
                  firstFrameBusyKey && 'pointer-events-none opacity-50',
                )}
              >
                {firstFrameBusyKey === 'upload'
                  ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" />
                  : <Upload className="mr-1.5 h-4 w-4" />}
                {firstFrameBusyKey === 'upload' ? '上传并提交中' : '上传本地图片'}
                <input
                  id="design-first-frame-upload"
                  type="file"
                  accept="image/jpeg,image/png,image/webp,.jpg,.jpeg,.png,.webp"
                  aria-label="上传本地图片"
                  className="sr-only"
                  disabled={Boolean(firstFrameBusyKey)}
                  onChange={(event) => void handleFirstFrameUpload(event)}
                />
              </label>
            </div>

            {firstFrameError ? (
              <p role="alert" className="mt-3 rounded-xl bg-destructive/10 px-3 py-2 text-xs font-semibold text-destructive">
                {firstFrameError}
              </p>
            ) : null}

            {firstFrameAssets.length > 0 ? (
              <div className="mt-4 grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
                {firstFrameAssets.map((asset) => (
                  <FirstFrameAssetOption
                    key={asset.assetId}
                    asset={asset}
                    busy={Boolean(firstFrameBusyKey)}
                    onSelect={() => void handleFirstFrameSelect(asset)}
                  />
                ))}
              </div>
            ) : (
              <div className="mt-4 rounded-2xl bg-background px-5 py-10 text-center shadow-soft ring-1 ring-black/5 dark:ring-white/10">
                <ImageIcon className="mx-auto h-6 w-6 text-muted-foreground" />
                <p className="mt-3 text-sm font-semibold">本项目还没有可用图片</p>
                <p className="mt-1 text-xs leading-5 text-muted-foreground">
                  可以先生成一张图片，或直接上传本地图片。
                </p>
              </div>
            )}
          </div>
        </DialogContent>
      </Dialog>
    </div>
  );
}

export default ImageCanvas;
