import {
  ArrowLeft,
  CheckCircle2,
  ImagePlus,
  Images,
  Loader2,
  Play,
  Plus,
  Sparkles,
  Trash2,
  X,
} from 'lucide-react';
import { type ChangeEvent, useEffect, useMemo, useRef, useState } from 'react';
import { createPortal } from 'react-dom';

import type { PuzzleAgentActionRequest } from '../../../packages/shared/src/contracts/puzzleAgentActions';
import type {
  PuzzleDraftLevel,
  PuzzleResultDraft,
} from '../../../packages/shared/src/contracts/puzzleAgentDraft';
import type { PuzzleAgentSessionSnapshot } from '../../../packages/shared/src/contracts/puzzleAgentSession';
import { updatePuzzleWork } from '../../services/puzzle-works';
import {
  puzzleAssetClient,
  type PuzzleHistoryAsset,
} from '../../services/puzzle-works/puzzleAssetClient';
import { readPuzzleReferenceImageAsDataUrl } from '../../services/puzzleReferenceImage';
import { useAuthUi } from '../auth/AuthUiContext';
import { ResolvedAssetImage } from '../ResolvedAssetImage';

type PuzzleResultViewProps = {
  session: PuzzleAgentSessionSnapshot;
  profileId?: string | null;
  isBusy?: boolean;
  error?: string | null;
  onBack: () => void;
  onExecuteAction: (payload: PuzzleAgentActionRequest) => void;
  onStartTestRun?: (draft: PuzzleResultDraft) => void;
};

type PuzzleAutoSaveState = 'idle' | 'saving' | 'saved' | 'error';
type PuzzleResultTab = 'levels' | 'work';

type DraftEditState = {
  workTitle: string;
  workDescription: string;
  themeTags: string[];
  levels: PuzzleDraftLevel[];
};

const PUZZLE_MIN_THEME_TAG_COUNT = 3;
const PUZZLE_MAX_THEME_TAG_COUNT = 6;
const PUZZLE_AUTOSAVE_DEBOUNCE_MS = 600;

function normalizeThemeTagInput(value: string) {
  return [
    ...new Set(
      value
        .split(/[\n,，、]/u)
        .map((entry) => entry.trim())
        .filter(Boolean),
    ),
  ];
}

function resolveLevelFormalImageSrc(level: PuzzleDraftLevel) {
  const selectedCandidate =
    level.candidates.find(
      (candidate) =>
        candidate.selected ||
        (level.selectedCandidateId
          ? candidate.candidateId === level.selectedCandidateId
          : false),
    ) ??
    level.candidates[level.candidates.length - 1] ??
    null;

  return (
    selectedCandidate?.imageSrc?.trim() || level.coverImageSrc?.trim() || ''
  );
}

function buildFallbackLevelFromDraft(draft: PuzzleResultDraft): PuzzleDraftLevel {
  return {
    levelId: 'puzzle-level-1',
    levelName: draft.levelName || '',
    pictureDescription: draft.summary,
    candidates: draft.candidates,
    selectedCandidateId: draft.selectedCandidateId,
    coverImageSrc: draft.coverImageSrc,
    coverAssetId: draft.coverAssetId,
    generationStatus: draft.generationStatus,
  };
}

function normalizeDraftLevels(draft: PuzzleResultDraft) {
  const sourceLevels =
    draft.levels && draft.levels.length > 0
      ? draft.levels
      : [buildFallbackLevelFromDraft(draft)];

  return sourceLevels.map((level, index) => ({
    ...level,
    levelId: level.levelId?.trim() || `puzzle-level-${index + 1}`,
    levelName: level.levelName?.trim() || '',
    pictureDescription: level.pictureDescription?.trim() || draft.summary,
    candidates: level.candidates ?? [],
    selectedCandidateId: level.selectedCandidateId ?? null,
    coverImageSrc: level.coverImageSrc ?? null,
    coverAssetId: level.coverAssetId ?? null,
    generationStatus: level.generationStatus || 'idle',
  }));
}

function syncDraftFromEditState(
  draft: PuzzleResultDraft,
  editState: DraftEditState,
): PuzzleResultDraft {
  const levels = editState.levels;
  const primaryLevel = levels[0] ?? buildFallbackLevelFromDraft(draft);
  return {
    ...draft,
    workTitle: editState.workTitle.trim() || draft.workTitle,
    workDescription: editState.workDescription.trim(),
    levelName: primaryLevel.levelName,
    summary: primaryLevel.pictureDescription,
    themeTags: editState.themeTags,
    candidates: primaryLevel.candidates,
    selectedCandidateId: primaryLevel.selectedCandidateId,
    coverImageSrc: primaryLevel.coverImageSrc,
    coverAssetId: primaryLevel.coverAssetId,
    generationStatus: primaryLevel.generationStatus,
    levels,
  };
}

function createDraftEditState(draft: PuzzleResultDraft): DraftEditState {
  return {
    workTitle: draft.workTitle || draft.levelName,
    workDescription: draft.workDescription || '',
    themeTags: normalizeThemeTagInput(draft.themeTags.join('，')),
    levels: normalizeDraftLevels(draft),
  };
}

function createBlankPuzzleLevel(existingLevels: PuzzleDraftLevel[]): PuzzleDraftLevel {
  const nextIndex = existingLevels.length + 1;
  return {
    levelId: `puzzle-level-${Date.now()}-${nextIndex}`,
    levelName: '',
    pictureDescription: '',
    candidates: [],
    selectedCandidateId: null,
    coverImageSrc: null,
    coverAssetId: null,
    generationStatus: 'idle',
  };
}

function formatHistoryAssetDate(value: string) {
  const date = new Date(value);
  if (Number.isNaN(date.getTime())) {
    return value || '';
  }
  return date.toLocaleString('zh-CN', {
    month: '2-digit',
    day: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  });
}

function buildPublishReady(
  session: PuzzleAgentSessionSnapshot,
  editState: DraftEditState,
) {
  const preservedBlockers =
    session.resultPreview?.blockers
      .filter(
        (entry) =>
          ![
            'MISSING_LEVEL_NAME',
            'INVALID_TAG_COUNT',
            'MISSING_COVER_IMAGE',
          ].includes(entry.code),
      )
      .map((entry) => entry.message) ?? [];
  const levels = editState.levels;
  const blockers = [
    ...(session.resultPreview ? [] : ['等待结果页草稿完成后再发布。']),
    ...preservedBlockers,
    ...(editState.workTitle.trim() ? [] : ['作品名称不能为空。']),
    ...(editState.workDescription.trim() ? [] : ['作品描述不能为空。']),
    ...(editState.themeTags.length >= PUZZLE_MIN_THEME_TAG_COUNT &&
    editState.themeTags.length <= PUZZLE_MAX_THEME_TAG_COUNT
      ? []
      : [
          `正式标签数量必须在 ${PUZZLE_MIN_THEME_TAG_COUNT} 到 ${PUZZLE_MAX_THEME_TAG_COUNT} 之间。`,
        ]),
    ...(levels.length > 0 ? [] : ['至少需要一个拼图关卡。']),
    ...levels.flatMap((level, index) => [
      ...(level.levelName.trim() ? [] : [`第${index + 1}关名称不能为空。`]),
      ...(resolveLevelFormalImageSrc(level) ? [] : [`第${index + 1}关缺少正式图。`]),
    ]),
  ];

  return {
    blockers: [...new Set(blockers.filter(Boolean))],
    publishReady:
      Boolean(session.resultPreview?.publishReady) &&
      Boolean(editState.workTitle.trim()) &&
      Boolean(editState.workDescription.trim()) &&
      editState.themeTags.length >= PUZZLE_MIN_THEME_TAG_COUNT &&
      editState.themeTags.length <= PUZZLE_MAX_THEME_TAG_COUNT &&
      levels.length > 0 &&
      levels.every(
        (level) => level.levelName.trim() && resolveLevelFormalImageSrc(level),
      ),
  };
}

function PuzzleResultHeader({
  autoSaveState,
  isBusy,
  onBack,
}: {
  autoSaveState: PuzzleAutoSaveState;
  isBusy: boolean;
  onBack: () => void;
}) {
  const autoSaveBadge =
    autoSaveState === 'saving' ? (
      <div className="platform-pill platform-pill--warm px-3 py-1 text-[11px]">
        保存中
      </div>
    ) : autoSaveState === 'saved' ? (
      <div className="platform-pill platform-pill--success px-3 py-1 text-[11px]">
        已自动保存
      </div>
    ) : autoSaveState === 'error' ? (
      <div className="platform-pill platform-pill--rose px-3 py-1 text-[11px]">
        保存失败
      </div>
    ) : null;

  return (
    <div className="mb-4 flex items-center justify-between gap-3">
      <button
        type="button"
        onClick={onBack}
        disabled={isBusy}
        className={`platform-button platform-button--ghost min-h-0 self-start px-3 py-1.5 text-[11px] ${isBusy ? 'opacity-45' : ''}`}
      >
        <span className="inline-flex items-center gap-1.5">
          <ArrowLeft className="h-3.5 w-3.5" />
          返回
        </span>
      </button>
      {autoSaveBadge}
    </div>
  );
}

function PuzzleResultTabs({
  activeTab,
  onChange,
}: {
  activeTab: PuzzleResultTab;
  onChange: (tab: PuzzleResultTab) => void;
}) {
  return (
    <div className="mb-3 grid grid-cols-2 gap-2 rounded-[1.25rem] border border-[var(--platform-subpanel-border)] bg-white/62 p-1">
      {[
        { id: 'levels' as const, label: '拼图关卡' },
        { id: 'work' as const, label: '作品信息' },
      ].map((tab) => (
        <button
          key={tab.id}
          type="button"
          onClick={() => onChange(tab.id)}
          className={`min-h-10 rounded-[1rem] px-3 text-sm font-bold transition ${
            activeTab === tab.id
              ? 'bg-white text-[var(--platform-text-strong)] shadow-sm'
              : 'text-[var(--platform-text-base)] hover:bg-white/60'
          }`}
          aria-pressed={activeTab === tab.id}
        >
          {tab.label}
        </button>
      ))}
    </div>
  );
}

function PuzzleThemeTagEditor({
  editState,
  isBusy,
  onChange,
}: {
  editState: DraftEditState;
  isBusy: boolean;
  onChange: (nextState: DraftEditState) => void;
}) {
  const [newTagText, setNewTagText] = useState('');
  const [isAddingTag, setIsAddingTag] = useState(false);

  const addTags = () => {
    const nextTags = normalizeThemeTagInput(newTagText);
    if (nextTags.length <= 0) {
      setIsAddingTag(false);
      setNewTagText('');
      return;
    }

    onChange({
      ...editState,
      themeTags: [...new Set([...editState.themeTags, ...nextTags])],
    });
    setNewTagText('');
    setIsAddingTag(false);
  };

  return (
    <section className="platform-subpanel rounded-[1.35rem] p-4 sm:p-5">
      <div className="flex items-center justify-between gap-3">
        <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
          作品标签
        </div>
        {!isAddingTag ? (
          <button
            type="button"
            disabled={isBusy}
            onClick={() => setIsAddingTag(true)}
            className="platform-icon-button h-9 w-9"
            aria-label="新增作品标签"
            title="新增作品标签"
          >
            <Plus className="h-4 w-4" />
          </button>
        ) : null}
      </div>

      <div className="mt-3 flex flex-wrap gap-2">
        {editState.themeTags.map((tag) => (
          <span
            key={tag}
            className="inline-flex items-center gap-1.5 rounded-full border border-amber-300/35 bg-amber-100/68 px-3 py-1.5 text-xs font-semibold text-amber-700"
          >
            {tag}
            <button
              type="button"
              disabled={isBusy}
              onClick={() => {
                onChange({
                  ...editState,
                  themeTags: editState.themeTags.filter(
                    (currentTag) => currentTag !== tag,
                  ),
                });
              }}
              className="rounded-full text-amber-800/70 transition hover:text-amber-950 disabled:opacity-45"
              aria-label={`删除标签 ${tag}`}
              title="删除标签"
            >
              <X className="h-3.5 w-3.5" />
            </button>
          </span>
        ))}
        {editState.themeTags.length <= 0 ? (
          <span className="text-sm text-[var(--platform-text-soft)]">
            暂无标签
          </span>
        ) : null}
      </div>

      {isAddingTag ? (
        <div className="mt-3 flex flex-col gap-2 rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/68 p-2 sm:flex-row">
          <input
            autoFocus
            value={newTagText}
            disabled={isBusy}
            onChange={(event) => setNewTagText(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === 'Enter') {
                event.preventDefault();
                addTags();
              }
              if (event.key === 'Escape') {
                setIsAddingTag(false);
                setNewTagText('');
              }
            }}
            className="min-h-10 flex-1 rounded-[0.8rem] border border-transparent bg-white/92 px-3 text-sm text-[var(--platform-text-strong)] outline-none"
            placeholder="输入新标签"
            aria-label="新题材标签"
          />
          <div className="flex gap-2">
            <button
              type="button"
              disabled={isBusy}
              onClick={addTags}
              className="platform-button platform-button--primary min-h-10 flex-1 px-4 py-2 text-sm sm:flex-none"
            >
              添加
            </button>
            <button
              type="button"
              disabled={isBusy}
              onClick={() => {
                setIsAddingTag(false);
                setNewTagText('');
              }}
              className="platform-button platform-button--ghost min-h-10 flex-1 px-4 py-2 text-sm sm:flex-none"
            >
              取消
            </button>
          </div>
        </div>
      ) : null}
    </section>
  );
}

function PuzzleHistoryAssetPickerDialog({
  isBusy,
  onClose,
  onSelect,
}: {
  isBusy: boolean;
  onClose: () => void;
  onSelect: (asset: PuzzleHistoryAsset) => void;
}) {
  const platformTheme = useAuthUi()?.platformTheme ?? 'light';
  const [assets, setAssets] = useState<PuzzleHistoryAsset[]>([]);
  const [isLoading, setIsLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    let cancelled = false;
    setIsLoading(true);
    setError(null);
    puzzleAssetClient
      .listHistoryAssets({ limit: 120 })
      .then((nextAssets) => {
        if (!cancelled) {
          setAssets(nextAssets);
        }
      })
      .catch((loadError) => {
        if (!cancelled) {
          setError(
            loadError instanceof Error
              ? loadError.message
              : '历史拼图素材读取失败。',
          );
        }
      })
      .finally(() => {
        if (!cancelled) {
          setIsLoading(false);
        }
      });

    return () => {
      cancelled = true;
    };
  }, []);

  if (typeof document === 'undefined') {
    return null;
  }

  return createPortal(
    <div
      className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[145] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose();
        }
      }}
    >
      <div
        role="dialog"
        aria-modal="true"
        aria-label="选择历史拼图素材"
        className="platform-modal-shell platform-remap-surface flex max-h-[min(92vh,46rem)] w-full max-w-5xl flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
        onClick={(event) => event.stopPropagation()}
      >
        <div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
          <div className="text-base font-semibold text-[var(--platform-text-strong)]">
            选择历史拼图素材
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="关闭"
            className="platform-icon-button"
          >
            <X className="h-4 w-4" />
          </button>
        </div>

        <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
          {error ? (
            <div className="platform-banner platform-banner--danger text-sm leading-6">
              {error}
            </div>
          ) : null}

          {isLoading ? (
            <div className="flex min-h-[14rem] items-center justify-center rounded-[1.35rem] border border-dashed border-[var(--platform-subpanel-border)] bg-white/52 px-6 text-center text-sm text-[var(--platform-text-base)]">
              读取中...
            </div>
          ) : null}

          {!isLoading && !error && assets.length <= 0 ? (
            <div className="flex min-h-[14rem] items-center justify-center rounded-[1.35rem] border border-dashed border-[var(--platform-subpanel-border)] bg-white/52 px-6 text-center text-sm text-[var(--platform-text-base)]">
              暂无历史拼图素材
            </div>
          ) : null}

          {!isLoading && assets.length > 0 ? (
            <div className="grid grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5">
              {assets.map((asset) => (
                <button
                  key={asset.assetObjectId}
                  type="button"
                  disabled={isBusy}
                  onClick={() => onSelect(asset)}
                  className={`overflow-hidden rounded-[1.25rem] border bg-white/82 text-left transition hover:border-amber-300/70 hover:bg-white ${isBusy ? 'cursor-not-allowed opacity-55' : 'border-[var(--platform-subpanel-border)]'}`}
                >
                  <div className="aspect-square overflow-hidden bg-[var(--platform-subpanel-fill)]">
                    <ResolvedAssetImage
                      src={asset.imageSrc}
                      alt={asset.ownerLabel || '历史拼图素材'}
                      className="h-full w-full object-cover"
                    />
                  </div>
                  <div className="space-y-1 px-4 py-4">
                    <div className="truncate text-sm font-black text-[var(--platform-text-strong)]">
                      {asset.ownerLabel || '未记录账号'}
                    </div>
                    <div className="text-xs leading-5 text-[var(--platform-text-base)]">
                      {formatHistoryAssetDate(asset.createdAt)}
                    </div>
                  </div>
                </button>
              ))}
            </div>
          ) : null}
        </div>
      </div>
    </div>,
    document.body,
  );
}

function PuzzleLevelDetailDialog({
  draft,
  imageRefreshKey,
  isBusy,
  level,
  onClose,
  onGenerate,
  onLevelChange,
  onStartTestRun,
}: {
  draft: PuzzleResultDraft;
  imageRefreshKey: string;
  isBusy: boolean;
  level: PuzzleDraftLevel;
  onClose: () => void;
  onGenerate: (
    levelId: string,
    promptText?: string | null,
    referenceImageSrc?: string | null,
  ) => void;
  onLevelChange: (nextLevel: PuzzleDraftLevel) => void;
  onStartTestRun?: (level: PuzzleDraftLevel) => void;
}) {
  const platformTheme = useAuthUi()?.platformTheme ?? 'light';
  const [referenceImageSrc, setReferenceImageSrc] = useState('');
  const [referenceImageLabel, setReferenceImageLabel] = useState('');
  const [referenceImageError, setReferenceImageError] = useState<string | null>(null);
  const [isHistoryPickerOpen, setIsHistoryPickerOpen] = useState(false);
  const formalImageSrc = resolveLevelFormalImageSrc(level);
  const hasFormalImage = Boolean(formalImageSrc);

  const handleReferenceImageChange = async (
    event: ChangeEvent<HTMLInputElement>,
  ) => {
    const file = event.target.files?.[0];
    event.currentTarget.value = '';
    if (!file) {
      return;
    }

    try {
      const dataUrl = await readPuzzleReferenceImageAsDataUrl(file);
      setReferenceImageSrc(dataUrl);
      setReferenceImageLabel(file.name.trim() || '本地参考图');
      setReferenceImageError(null);
    } catch (uploadError) {
      setReferenceImageError(
        uploadError instanceof Error
          ? uploadError.message
          : '参考图读取失败，请重试。',
      );
    }
  };

  if (typeof document === 'undefined') {
    return null;
  }

  return createPortal(
    <div
      className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[138] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose();
        }
      }}
    >
      <div
        role="dialog"
        aria-modal="true"
        aria-label="关卡详情"
        className="platform-modal-shell platform-remap-surface flex max-h-[min(94vh,50rem)] w-full max-w-2xl flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
        onClick={(event) => event.stopPropagation()}
      >
        <div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
          <div className="min-w-0 truncate text-base font-semibold text-[var(--platform-text-strong)]">
            {level.levelName || '关卡详情'}
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="关闭"
            className="platform-icon-button"
          >
            <X className="h-4 w-4" />
          </button>
        </div>

        <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
          <div className="space-y-4">
            <section className="platform-subpanel rounded-[1.35rem] p-4">
              <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
                关卡名称
              </div>
              <input
                value={level.levelName}
                disabled={isBusy}
                onChange={(event) =>
                  onLevelChange({ ...level, levelName: event.target.value })
                }
                className="mt-2 w-full rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-base font-semibold text-[var(--platform-text-strong)] outline-none"
                aria-label="关卡名称"
              />
            </section>

            {hasFormalImage ? (
              <section className="platform-subpanel rounded-[1.35rem] p-4">
                <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
                  画面图
                </div>
                <div className="relative mt-3 aspect-square overflow-hidden rounded-[1.15rem] bg-[var(--platform-subpanel-fill)]">
                  <ResolvedAssetImage
                    src={formalImageSrc}
                    refreshKey={`${imageRefreshKey}:${level.levelId}`}
                    alt={level.levelName || draft.workTitle || '拼图关卡'}
                    className="h-full w-full object-cover"
                  />
                  <button
                    type="button"
                    disabled={isBusy}
                    onClick={() => setIsHistoryPickerOpen(true)}
                    className={`absolute bottom-3 right-3 inline-flex h-10 w-10 items-center justify-center rounded-full border border-[var(--platform-subpanel-border)] bg-white/96 text-[var(--platform-text-strong)] shadow-sm transition hover:bg-[var(--platform-subpanel-fill)] ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
                    aria-label="从历史拼图素材库选择"
                    title="从历史拼图素材库选择"
                  >
                    <Images className="h-4 w-4" />
                  </button>
                </div>
              </section>
            ) : null}

            <section className="platform-subpanel rounded-[1.35rem] p-4">
              <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
                画面描述
              </div>
              <div className="relative mt-3">
                <textarea
                  value={level.pictureDescription}
                  disabled={isBusy}
                  rows={9}
                  onChange={(event) =>
                    onLevelChange({
                      ...level,
                      pictureDescription: event.target.value,
                    })
                  }
                  className="w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 pb-16 text-sm leading-6 text-[var(--platform-text-strong)] outline-none"
                  aria-label="画面描述"
                />
                <label
                  className={`absolute bottom-3 right-3 inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-full border border-amber-300/70 bg-white/96 text-amber-700 shadow-sm transition hover:bg-amber-50 ${isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
                  title={referenceImageSrc ? '更换参考图' : '添加参考图'}
                >
                  <ImagePlus className="h-4 w-4" />
                  <span className="sr-only">
                    {referenceImageSrc ? '更换参考图' : '添加参考图'}
                  </span>
                  <input
                    type="file"
                    accept="image/png,image/jpeg,image/webp"
                    disabled={isBusy}
                    onChange={(event) => {
                      void handleReferenceImageChange(event);
                    }}
                    className="hidden"
                  />
                </label>
              </div>

              {referenceImageSrc ? (
                <div className="mt-3 flex items-center gap-3 rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/72 px-3 py-3">
                  <div className="h-14 w-14 overflow-hidden rounded-[0.9rem] bg-[var(--platform-subpanel-fill)]">
                    <img
                      src={referenceImageSrc}
                      alt="拼图参考图"
                      className="h-full w-full object-cover"
                    />
                  </div>
                  <div className="min-w-0 flex-1">
                    <div className="truncate text-sm font-semibold text-[var(--platform-text-strong)]">
                      {referenceImageLabel || '已选择参考图'}
                    </div>
                  </div>
                  <button
                    type="button"
                    disabled={isBusy}
                    onClick={() => {
                      setReferenceImageSrc('');
                      setReferenceImageLabel('');
                      setReferenceImageError(null);
                    }}
                    className="platform-icon-button h-9 w-9"
                    aria-label="移除参考图"
                    title="移除参考图"
                  >
                    <X className="h-4 w-4" />
                  </button>
                </div>
              ) : null}

              {referenceImageError ? (
                <div className="mt-2 text-xs leading-5 text-red-600">
                  {referenceImageError}
                </div>
              ) : null}
            </section>
          </div>
        </div>

        <div className="shrink-0 space-y-3 border-t border-[var(--platform-subpanel-border)] bg-[var(--platform-page-fill)] px-5 py-4 pb-[calc(env(safe-area-inset-bottom,0px)+1rem)]">
          {onStartTestRun && hasFormalImage ? (
            <button
              type="button"
              disabled={isBusy}
              onClick={() => onStartTestRun(level)}
              className={`platform-button platform-button--secondary w-full ${isBusy ? 'opacity-55' : ''}`}
            >
              <span className="inline-flex items-center gap-2">
                <Play className="h-4 w-4" />
                关卡测试
              </span>
            </button>
          ) : null}

          <button
            type="button"
            disabled={isBusy}
            onClick={() => {
              onGenerate(
                level.levelId,
                level.pictureDescription.trim() || undefined,
                referenceImageSrc || undefined,
              );
            }}
            className="inline-flex w-full items-center justify-center gap-2 rounded-full bg-amber-600 px-4 py-3 text-sm font-bold text-white disabled:opacity-45"
          >
            {isBusy ? <Loader2 className="h-4 w-4 animate-spin" /> : null}
            <Sparkles className="h-4 w-4" />
            {hasFormalImage ? '重新生成画面' : '生成画面'}
          </button>
        </div>

        {isHistoryPickerOpen ? (
          <PuzzleHistoryAssetPickerDialog
            isBusy={isBusy}
            onClose={() => setIsHistoryPickerOpen(false)}
            onSelect={(asset) => {
              setReferenceImageSrc(asset.imageSrc);
              setReferenceImageLabel(
                `历史素材 · ${asset.ownerLabel || '未记录账号'}`,
              );
              setReferenceImageError(null);
              setIsHistoryPickerOpen(false);
            }}
          />
        ) : null}
      </div>
    </div>,
    document.body,
  );
}

function PuzzlePublishDialog({
  blockers,
  editState,
  imageRefreshKey,
  isBusy,
  publishReady,
  onClose,
  onPublish,
}: {
  blockers: string[];
  editState: DraftEditState;
  imageRefreshKey: string;
  isBusy: boolean;
  publishReady: boolean;
  onClose: () => void;
  onPublish: () => void;
}) {
  const platformTheme = useAuthUi()?.platformTheme ?? 'light';
  const primaryLevel = editState.levels[0] ?? null;
  const formalImageSrc = primaryLevel ? resolveLevelFormalImageSrc(primaryLevel) : '';

  if (typeof document === 'undefined') {
    return null;
  }

  return createPortal(
    <div
      className={`platform-theme platform-theme--${platformTheme} platform-overlay fixed inset-0 z-[140] flex items-end justify-center p-3 backdrop-blur-sm sm:items-center sm:p-4`}
      onClick={(event) => {
        if (event.target === event.currentTarget) {
          onClose();
        }
      }}
    >
      <div
        role="dialog"
        aria-modal="true"
        aria-label="发布拼图作品"
        className="platform-modal-shell platform-remap-surface flex max-h-[min(90vh,42rem)] w-full max-w-3xl flex-col overflow-hidden rounded-t-[1.75rem] shadow-[0_24px_80px_rgba(0,0,0,0.55)] sm:rounded-[1.75rem]"
        onClick={(event) => event.stopPropagation()}
      >
        <div className="flex items-center justify-between gap-3 border-b border-[var(--platform-subpanel-border)] px-5 py-4">
          <div className="text-base font-semibold text-[var(--platform-text-strong)]">
            发布拼图作品
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="关闭"
            className="platform-icon-button"
          >
            <X className="h-4 w-4" />
          </button>
        </div>

        <div className="min-h-0 flex-1 overflow-y-auto px-5 py-4">
          <div className="grid gap-4 md:grid-cols-[minmax(0,1fr)_14rem]">
            <div className="space-y-3">
              <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
                发布检查
              </div>
              {publishReady ? (
                <div className="platform-banner platform-banner--success text-sm leading-6">
                  当前作品已满足发布条件。
                </div>
              ) : (
                <div className="space-y-2">
                  {blockers.map((blocker, index) => (
                    <div
                      key={`puzzle-publish-blocker-${index}-${blocker}`}
                      className="platform-banner platform-banner--warning text-sm leading-6"
                    >
                      {blocker}
                    </div>
                  ))}
                </div>
              )}
            </div>

            <div className="space-y-3">
              <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
                封面关卡
              </div>
              <div className="aspect-square overflow-hidden rounded-[1.15rem] bg-[var(--platform-subpanel-fill)]">
                {formalImageSrc ? (
                  <ResolvedAssetImage
                    src={formalImageSrc}
                    refreshKey={imageRefreshKey}
                    alt={primaryLevel?.levelName || editState.workTitle}
                    className="h-full w-full object-cover"
                  />
                ) : null}
              </div>
              <div className="text-sm font-black text-[var(--platform-text-strong)]">
                {editState.workTitle}
              </div>
            </div>
          </div>
        </div>

        <div className="flex flex-col-reverse gap-3 border-t border-[var(--platform-subpanel-border)] px-5 py-4 sm:flex-row sm:justify-end">
          <button
            type="button"
            onClick={onClose}
            className="platform-button platform-button--ghost"
          >
            取消
          </button>
          <button
            type="button"
            onClick={onPublish}
            disabled={!publishReady || isBusy}
            className={`platform-button platform-button--primary ${!publishReady || isBusy ? 'cursor-not-allowed opacity-55' : ''}`}
          >
            {isBusy ? '发布中...' : '发布到广场'}
          </button>
        </div>
      </div>
    </div>,
    document.body,
  );
}

function PuzzleLevelListTab({
  editState,
  imageRefreshKey,
  isBusy,
  onAddLevel,
  onDeleteLevel,
  onOpenLevel,
}: {
  editState: DraftEditState;
  imageRefreshKey: string;
  isBusy: boolean;
  onAddLevel: () => void;
  onDeleteLevel: (levelId: string) => void;
  onOpenLevel: (levelId: string) => void;
}) {
  return (
    <div className="space-y-3">
      <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-3">
        {editState.levels.map((level, index) => {
          const imageSrc = resolveLevelFormalImageSrc(level);
          const displayLevelName = level.levelName || `第${index + 1}关`;
          return (
            <div
              key={level.levelId}
              className="platform-subpanel overflow-hidden rounded-[1.35rem] p-0"
            >
              <button
                type="button"
                onClick={() => onOpenLevel(level.levelId)}
                className="block w-full text-left"
              >
                <div className="aspect-[4/3] overflow-hidden bg-[var(--platform-subpanel-fill)]">
                  {imageSrc ? (
                    <ResolvedAssetImage
                      src={imageSrc}
                      refreshKey={`${imageRefreshKey}:${level.levelId}`}
                      alt={displayLevelName}
                      className="h-full w-full object-cover"
                    />
                  ) : (
                    <div className="flex h-full items-center justify-center text-sm text-[var(--platform-text-soft)]">
                      暂无正式图
                    </div>
                  )}
                </div>
                <div className="space-y-1 px-4 py-4">
                  <div className="text-[11px] font-bold tracking-[0.16em] text-[var(--platform-text-soft)]">
                    第{index + 1}关
                  </div>
                </div>
              </button>
              <div className="flex items-end gap-2 px-4 pb-4">
                <button
                  type="button"
                  onClick={() => onOpenLevel(level.levelId)}
                  className="min-w-0 flex-1 truncate text-left text-base font-black text-[var(--platform-text-strong)]"
                >
                  {displayLevelName}
                </button>
                <button
                  type="button"
                  disabled={isBusy || editState.levels.length <= 1}
                  onClick={() => onDeleteLevel(level.levelId)}
                  className="platform-icon-button h-9 w-9 shrink-0"
                  aria-label={`删除关卡 ${displayLevelName}`}
                  title="删除关卡"
                >
                  <Trash2 className="h-4 w-4" />
                </button>
              </div>
            </div>
          );
        })}
      </div>

      <button
        type="button"
        disabled={isBusy}
        onClick={onAddLevel}
        className="platform-button platform-button--secondary w-full"
      >
        <span className="inline-flex items-center gap-2">
          <Plus className="h-4 w-4" />
          新增关卡
        </span>
        <span className="mt-1 block text-[11px] font-semibold leading-none text-[var(--platform-text-soft)]">
          获得更多积分激励
        </span>
      </button>
    </div>
  );
}

function PuzzleWorkInfoTab({
  editState,
  isBusy,
  onChange,
}: {
  editState: DraftEditState;
  isBusy: boolean;
  onChange: (nextState: DraftEditState) => void;
}) {
  return (
    <div className="space-y-3">
      <section className="platform-subpanel rounded-[1.35rem] p-4 sm:p-5">
        <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
          作品名称
        </div>
        <input
          value={editState.workTitle}
          disabled={isBusy}
          onChange={(event) =>
            onChange({ ...editState, workTitle: event.target.value })
          }
          className="mt-2 w-full rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/86 px-3 py-3 text-base font-semibold text-[var(--platform-text-strong)] outline-none"
          aria-label="作品名称"
        />
      </section>

      <section className="platform-subpanel rounded-[1.35rem] p-4 sm:p-5">
        <div className="text-xs font-bold tracking-[0.18em] text-[var(--platform-text-soft)]">
          作品描述
        </div>
        <textarea
          value={editState.workDescription}
          disabled={isBusy}
          rows={6}
          onChange={(event) =>
            onChange({ ...editState, workDescription: event.target.value })
          }
          className="mt-3 w-full resize-none rounded-[1rem] border border-[var(--platform-subpanel-border)] bg-white/90 px-4 py-3 text-sm leading-6 text-[var(--platform-text-strong)] outline-none"
          aria-label="作品描述"
        />
      </section>

      <PuzzleThemeTagEditor
        editState={editState}
        isBusy={isBusy}
        onChange={onChange}
      />
    </div>
  );
}

function PuzzleResultActionBar({
  editState,
  imageRefreshKey,
  isBusy,
  publishReady,
  publishBlockers,
  onPublish,
}: {
  editState: DraftEditState;
  imageRefreshKey: string;
  isBusy: boolean;
  publishReady: boolean;
  publishBlockers: string[];
  onPublish: () => void;
}) {
  const [showPublishDialog, setShowPublishDialog] = useState(false);

  return (
    <div className="mt-4 flex items-center justify-end gap-3 pb-[max(0.25rem,env(safe-area-inset-bottom))]">
      <button
        type="button"
        onClick={() => setShowPublishDialog(true)}
        disabled={isBusy}
        className={`platform-button platform-button--primary ${isBusy ? 'opacity-55' : ''}`}
      >
        <span className="inline-flex items-center gap-2">
          <CheckCircle2 className="h-4 w-4" />
          发布
        </span>
      </button>

      {showPublishDialog ? (
        <PuzzlePublishDialog
          blockers={publishBlockers}
          editState={editState}
          imageRefreshKey={imageRefreshKey}
          isBusy={isBusy}
          publishReady={publishReady}
          onClose={() => setShowPublishDialog(false)}
          onPublish={onPublish}
        />
      ) : null}
    </div>
  );
}

export function PuzzleResultView({
  session,
  profileId = null,
  isBusy = false,
  error = null,
  onBack,
  onExecuteAction,
  onStartTestRun,
}: PuzzleResultViewProps) {
  const draft = session.draft;
  const [activeTab, setActiveTab] = useState<PuzzleResultTab>('levels');
  const [activeLevelId, setActiveLevelId] = useState<string | null>(null);
  const [editState, setEditState] = useState<DraftEditState | null>(
    draft ? createDraftEditState(draft) : null,
  );
  const [autoSaveState, setAutoSaveState] =
    useState<PuzzleAutoSaveState>('idle');
  const [autoSaveError, setAutoSaveError] = useState<string | null>(null);
  const savedEditStateRef = useRef<DraftEditState | null>(
    draft ? createDraftEditState(draft) : null,
  );

  useEffect(() => {
    if (!draft) {
      setEditState(null);
      setActiveLevelId(null);
      setAutoSaveState('idle');
      setAutoSaveError(null);
      return;
    }
    const nextState = createDraftEditState(draft);
    savedEditStateRef.current = nextState;
    setEditState(nextState);
    setActiveLevelId((currentLevelId) =>
      currentLevelId &&
      nextState.levels.some((level) => level.levelId === currentLevelId)
        ? currentLevelId
        : null,
    );
    setAutoSaveState('idle');
    setAutoSaveError(null);
  }, [draft]);

  const syncedDraft = useMemo(() => {
    if (!draft || !editState) {
      return null;
    }
    return syncDraftFromEditState(draft, editState);
  }, [draft, editState]);
  const primaryLevel = editState?.levels[0] ?? null;
  const primaryImageSrc = primaryLevel ? resolveLevelFormalImageSrc(primaryLevel) : '';
  const imageRefreshKey = `${session.updatedAt}:${primaryImageSrc}:${editState?.levels.length ?? 0}`;
  const activeLevel =
    editState?.levels.find((level) => level.levelId === activeLevelId) ?? null;

  useEffect(() => {
    if (!draft || !editState || !profileId) {
      return;
    }

    const normalizedState: DraftEditState = {
      ...editState,
      workTitle: editState.workTitle.trim(),
      workDescription: editState.workDescription.trim(),
      themeTags: normalizeThemeTagInput(editState.themeTags.join('，')),
      levels: editState.levels.map((level) => ({
        ...level,
        levelName: level.levelName.trim(),
        pictureDescription: level.pictureDescription.trim(),
      })),
    };
    const originalState = savedEditStateRef.current ?? createDraftEditState(draft);
    const changed =
      JSON.stringify(normalizedState) !== JSON.stringify(originalState);

    if (!changed || normalizedState.levels.length <= 0) {
      return;
    }

    setAutoSaveState('saving');
    setAutoSaveError(null);

    let cancelled = false;
    const timer = window.setTimeout(() => {
      const firstLevel = normalizedState.levels[0]!;
      void updatePuzzleWork(profileId, {
        workTitle: normalizedState.workTitle,
        workDescription: normalizedState.workDescription,
        levelName: firstLevel.levelName,
        summary: firstLevel.pictureDescription,
        themeTags: normalizedState.themeTags,
        coverImageSrc: resolveLevelFormalImageSrc(firstLevel) || null,
        coverAssetId: firstLevel.coverAssetId ?? null,
        levels: normalizedState.levels,
      })
        .then(() => {
          if (cancelled) {
            return;
          }
          // 自动保存成功后推进比较基线，避免新增后再删除回首版形态时漏同步后端。
          savedEditStateRef.current = normalizedState;
          setAutoSaveState('saved');
        })
        .catch((saveError) => {
          if (cancelled) {
            return;
          }
          setAutoSaveState('error');
          setAutoSaveError(
            saveError instanceof Error ? saveError.message : '自动保存失败。',
          );
        });
    }, PUZZLE_AUTOSAVE_DEBOUNCE_MS);

    return () => {
      cancelled = true;
      window.clearTimeout(timer);
    };
  }, [draft, editState, profileId]);

  const publishState = useMemo(() => {
    if (!editState) {
      return {
        blockers: ['等待结果页草稿完成后再发布。'],
        publishReady: false,
      };
    }
    return buildPublishReady(session, editState);
  }, [editState, session]);

  if (!draft || !editState || !syncedDraft) {
    return (
      <div className="flex h-full items-center justify-center">
        <div className="platform-subpanel rounded-2xl px-5 py-4 text-sm text-[var(--platform-text-base)]">
          还没有可编辑的拼图草稿
        </div>
      </div>
    );
  }

  const updateLevel = (nextLevel: PuzzleDraftLevel) => {
    setEditState((currentState) =>
      currentState
        ? {
            ...currentState,
            levels: currentState.levels.map((level) =>
              level.levelId === nextLevel.levelId ? nextLevel : level,
            ),
          }
        : currentState,
    );
  };

  const buildLevelDraft = (level: PuzzleDraftLevel): PuzzleResultDraft => ({
    ...syncedDraft,
    levelName: level.levelName,
    summary: level.pictureDescription,
    candidates: level.candidates,
    selectedCandidateId: level.selectedCandidateId,
    coverImageSrc: resolveLevelFormalImageSrc(level) || level.coverImageSrc,
    coverAssetId: level.coverAssetId,
    generationStatus: level.generationStatus,
    levels: [level],
  });

  return (
    <div className="platform-remap-surface mx-auto flex h-full min-h-0 w-full flex-col xl:max-w-[min(100%,98rem)] xl:px-1 2xl:max-w-[min(100%,112rem)]">
      <PuzzleResultHeader
        autoSaveState={autoSaveState}
        isBusy={isBusy}
        onBack={onBack}
      />

      <PuzzleResultTabs activeTab={activeTab} onChange={setActiveTab} />

      <div className="min-h-0 flex-1 overflow-y-auto pr-1">
        {activeTab === 'levels' ? (
          <PuzzleLevelListTab
            editState={editState}
            imageRefreshKey={imageRefreshKey}
            isBusy={isBusy}
            onAddLevel={() => {
              const nextLevel = createBlankPuzzleLevel(editState.levels);
              setEditState({
                ...editState,
                levels: [...editState.levels, nextLevel],
              });
              setActiveLevelId(nextLevel.levelId);
            }}
            onDeleteLevel={(levelId) => {
              if (editState.levels.length <= 1) {
                return;
              }
              const nextLevels = editState.levels.filter(
                (level) => level.levelId !== levelId,
              );
              setEditState({
                ...editState,
                levels: nextLevels,
              });
              if (activeLevelId === levelId) {
                setActiveLevelId(null);
              }
            }}
            onOpenLevel={setActiveLevelId}
          />
        ) : (
          <PuzzleWorkInfoTab
            editState={editState}
            isBusy={isBusy}
            onChange={setEditState}
          />
        )}
      </div>

      {error ? (
        <div className="platform-banner platform-banner--danger mt-3 rounded-2xl text-sm leading-6">
          {error}
        </div>
      ) : null}
      {!error && autoSaveError ? (
        <div className="platform-banner platform-banner--danger mt-3 rounded-2xl text-sm leading-6">
          {autoSaveError}
        </div>
      ) : null}

      <PuzzleResultActionBar
        editState={editState}
        imageRefreshKey={imageRefreshKey}
        isBusy={isBusy}
        publishReady={publishState.publishReady}
        publishBlockers={publishState.blockers}
        onPublish={() => {
          if (!publishState.publishReady) {
            return;
          }
          const firstLevel = editState.levels[0]!;
          onExecuteAction({
            action: 'publish_puzzle_work',
            workTitle: editState.workTitle.trim(),
            workDescription: editState.workDescription.trim(),
            levelName: firstLevel.levelName.trim(),
            summary: firstLevel.pictureDescription.trim(),
            themeTags: editState.themeTags,
            levelsJson: JSON.stringify(editState.levels),
          });
        }}
      />

      {activeLevel ? (
        <PuzzleLevelDetailDialog
          draft={syncedDraft}
          imageRefreshKey={imageRefreshKey}
          isBusy={isBusy}
          level={activeLevel}
          onClose={() => setActiveLevelId(null)}
          onGenerate={(levelId, promptText, referenceImageSrc) => {
            onExecuteAction({
              action: 'generate_puzzle_images',
              levelId,
              promptText,
              referenceImageSrc,
              candidateCount: 1,
              levelsJson: JSON.stringify(editState.levels),
            });
          }}
          onLevelChange={updateLevel}
          onStartTestRun={
            onStartTestRun
              ? (level) => onStartTestRun(buildLevelDraft(level))
              : undefined
          }
        />
      ) : null}
    </div>
  );
}

export default PuzzleResultView;
