import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';

import useAppLayoutStore from '../../Store/AppLayoutSlice';
import useStudioStore from '../../Store/Studio/StudioSlice';
import {
  buildWorkflowVariableLabelMap,
  getWorkflowVariableLabel,
} from './edit/workflowVariableLabels';
import { resolveBoundCharacterName, canUndoRun, canRerollRun, canAdvanceNextStep, getRunControls, hasRunControl, shouldShowFirstExport, shouldShowSaveAppendOverwrite, canSaveWorldbookEntry, canSwitchToNode, isInitBindCompleted } from './studioRunUtils';
import useCharacterStore from '../../Store/SideBarLeft/CharacterSlice';

import StudioContextBlockPopup from './StudioContextBlockPopup';
import StudioInsertionPopup from './StudioInsertionPopup';
import StudioRunChat from './StudioRunChat';
import StudioRunNodeGraph from './StudioRunNodeGraph';

import './StudioRunPage.css';

const RUN_STATUS_LABEL = {
  pending: '待开始',
  running: '运行中',
  paused: '已暂停',
  completed: '已完成',
  failed: '失败',
  cancelled: '已取消',
};

const RUN_LIST_COLLAPSE_THRESHOLD = 4;
const RUN_ITEM_HEIGHT = 56;
const CONFIRM_ACTION_TIMEOUT_MS = 4000;

function ConfirmTurnButton({
  actionKey,
  pendingKey,
  label,
  className,
  disabled,
  title,
  onConfirm,
  onRequestConfirm,
}) {
  const isPending = pendingKey === actionKey;
  return (
    <button
      type="button"
      className={`${className}${isPending ? ' is-confirm-pending' : ''}`}
      onClick={() => {
        if (isPending) {
          onConfirm();
        } else {
          onRequestConfirm(actionKey);
        }
      }}
      disabled={disabled && !isPending}
      title={isPending ? '再次点击确认操作' : title}
    >
      {isPending ? '确认' : label}
    </button>
  );
}

function formatRunTime(iso) {
  if (!iso) return '';
  try {
    const d = new Date(iso);
    return d.toLocaleString('zh-CN', {
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit',
    });
  } catch {
    return iso;
  }
}

function runDisplayName(run) {
  if (run.title?.trim()) return run.title.trim();
  return formatRunTime(run.createdAt) || '未命名运行';
}

function InitBindGuidanceForm({ node, runAdvancing, onSubmit, compact = false }) {
  const displayParams = node?.displayParams || [];
  const [formValues, setFormValues] = useState({});
  const [fieldErrors, setFieldErrors] = useState({});

  useEffect(() => {
    const initial = {};
    displayParams.forEach((dp) => {
      initial[dp.key] = '';
    });
    setFormValues(initial);
    setFieldErrors({});
  }, [node?.nodeId, displayParams]);

  const handleChange = (key, value) => {
    setFormValues((prev) => ({ ...prev, [key]: value }));
    setFieldErrors((prev) => ({ ...prev, [key]: null }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    const errors = {};
    displayParams.forEach((dp) => {
      if (dp.required && !(formValues[dp.key] || '').trim()) {
        errors[dp.key] = `请填写${dp.label}`;
      }
    });
    if (Object.keys(errors).length > 0) {
      setFieldErrors(errors);
      return;
    }
    onSubmit(formValues);
  };

  if (displayParams.length === 0) {
    return <div className="studio-run-empty">当前步骤未定义引导字段</div>;
  }

  return (
    <form
      className={`studio-run-guidance-form${compact ? ' studio-run-guidance-form--compact' : ''}`}
      onSubmit={handleSubmit}
    >
      <p className="studio-run-hint">
        填写以下信息后将创建角色卡与世界书，并绑定到当前 Studio 项目。
      </p>
      {displayParams.map((dp) => (
        <label key={dp.key} className="studio-run-form-field">
          <span className="studio-run-form-label">
            {dp.label}
            {dp.required ? <span className="studio-run-required">*</span> : null}
          </span>
          <input
            type="text"
            className={`studio-run-form-input${fieldErrors[dp.key] ? ' has-error' : ''}`}
            value={formValues[dp.key] || ''}
            onChange={(ev) => handleChange(dp.key, ev.target.value)}
            placeholder={dp.placeholder || ''}
            disabled={runAdvancing}
          />
          {fieldErrors[dp.key] ? (
            <span className="studio-run-field-error">{fieldErrors[dp.key]}</span>
          ) : null}
        </label>
      ))}
      <button type="submit" className="studio-run-submit-btn" disabled={runAdvancing}>
        {runAdvancing ? '创建中…' : '创建并绑定'}
      </button>
    </form>
  );
}

function ToolOptionsCarousel({ options, index, onPrev, onNext, runAdvancing }) {
  const current = options[index];
  const [selectedOption, setSelectedOption] = useState(null);
  const [copyHint, setCopyHint] = useState('');

  useEffect(() => {
    setSelectedOption(null);
    setCopyHint('');
  }, [index, current?.question]);

  if (!options.length || !current) return null;

  const handleSelect = (opt) => {
    setSelectedOption(opt);
    setCopyHint('');
  };

  const handleCopy = async () => {
    if (!selectedOption) return;
    try {
      await navigator.clipboard.writeText(selectedOption);
      setCopyHint('已复制到剪贴板');
      setTimeout(() => setCopyHint(''), 2000);
    } catch {
      setCopyHint('复制失败，请手动选择文本');
    }
  };

  return (
    <div className="studio-run-tool-carousel">
      <div className="studio-run-tool-carousel__head">
        <span className="studio-run-tool-carousel__label">模型提问</span>
        <span className="studio-run-tool-carousel__pager">
          {index + 1} / {options.length}
        </span>
      </div>
      <p className="studio-run-tool-carousel__question">{current.question}</p>
      <div className="studio-run-tool-carousel__nav">
        <button
          type="button"
          className="studio-run-icon-btn"
          onClick={onPrev}
          disabled={options.length <= 1}
          aria-label="上一题"
        >
          ‹
        </button>
        <div className="studio-run-tool-carousel__options">
          {(current.options || []).map((opt) => (
            <button
              key={opt}
              type="button"
              className={`studio-run-tool-option${selectedOption === opt ? ' is-selected' : ''}`}
              onClick={() => handleSelect(opt)}
              disabled={runAdvancing}
            >
              {opt}
            </button>
          ))}
        </div>
        <button
          type="button"
          className="studio-run-icon-btn"
          onClick={onNext}
          disabled={options.length <= 1}
          aria-label="下一题"
        >
          ›
        </button>
      </div>
      {selectedOption ? (
        <div className="studio-run-tool-copy">
          <div className="studio-run-tool-copy__head">
            <span className="studio-run-tool-copy__label">复制以下内容到输入框</span>
            <button
              type="button"
              className="studio-run-tool-copy__btn"
              onClick={handleCopy}
            >
              复制
            </button>
          </div>
          <textarea
            className="studio-run-tool-copy__textarea"
            readOnly
            value={selectedOption}
            rows={3}
            aria-label="选项内容"
          />
          {copyHint ? (
            <span className="studio-run-tool-copy__hint">{copyHint}</span>
          ) : null}
        </div>
      ) : null}
    </div>
  );
}

function PromptBlocksDebug({ blocks, onBlockClick }) {
  const [open, setOpen] = useState(false);
  if (!blocks?.length) return null;

  return (
    <div className="studio-run-prompt-debug">
      <button
        type="button"
        className="studio-run-prompt-debug__toggle"
        onClick={() => setOpen((v) => !v)}
      >
        {open ? '▼' : '▶'} 上下文块（调试 · {blocks.length}）
      </button>
      {open && (
        <div className="studio-run-prompt-debug__list">
          {blocks.map((block) => (
            <button
              key={block.id}
              type="button"
              className="studio-run-prompt-debug__item"
              onClick={() => onBlockClick?.(block)}
            >
              <span className="studio-run-prompt-debug__label">{block.label}</span>
              <span className="studio-run-prompt-debug__source">{block.source}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function RunListItem({
  run,
  selected,
  onSelect,
  onDelete,
  renamingId,
  renameValue,
  onRenameChange,
  onRenameSubmit,
  onRenameCancel,
  onStartRename,
}) {
  const isRenaming = renamingId === run.id;

  return (
    <li className="studio-run-list-row">
      {isRenaming ? (
        <form
          className="studio-run-rename-form"
          onSubmit={(e) => {
            e.preventDefault();
            onRenameSubmit(run.id);
          }}
        >
          <input
            className="studio-run-rename-input"
            value={renameValue}
            onChange={(e) => onRenameChange(e.target.value)}
            autoFocus
            maxLength={120}
          />
          <div className="studio-run-rename-actions">
            <button type="submit" className="studio-run-list-action" title="保存">
              ✓
            </button>
            <button
              type="button"
              className="studio-run-list-action"
              onClick={onRenameCancel}
              title="取消"
            >
              ✕
            </button>
          </div>
        </form>
      ) : (
        <>
          <button
            type="button"
            className={`studio-run-list-item${selected ? ' selected' : ''}`}
            onClick={() => onSelect(run.id)}
          >
            <span className="studio-run-list-name">{runDisplayName(run)}</span>
            <span className={`studio-run-status-badge status-${run.status}`}>
              {RUN_STATUS_LABEL[run.status] || run.status}
            </span>
          </button>
          <div className="studio-run-list-item-actions">
            <button
              type="button"
              className="studio-run-list-action"
              title="重命名"
              onClick={(e) => {
                e.stopPropagation();
                onStartRename(run);
              }}
            >
              ✎
            </button>
            <button
              type="button"
              className="studio-run-list-action studio-run-list-action--danger"
              title="删除"
              onClick={(e) => {
                e.stopPropagation();
                onDelete(run.id);
              }}
            >
              🗑
            </button>
          </div>
        </>
      )}
    </li>
  );
}

function ProductPreview({ activeNodeState, isWorldbookActive, compact = false }) {
  let productPreview = '暂无产物';
  if (activeNodeState?.lastDraft) {
    const draft = activeNodeState.lastDraft;
    for (const key of ['entryContent', 'content', 'text', 'body']) {
      if (typeof draft[key] === 'string' && draft[key].trim()) {
        productPreview = draft[key];
        break;
      }
    }
    if (productPreview === '暂无产物') {
      productPreview = JSON.stringify(draft, null, 2);
    }
  } else if (isWorldbookActive) {
    productPreview = '（等待模型生成条目草稿…）';
  }

  return (
    <div className={`studio-run-product-main${compact ? ' studio-run-product-main--compact' : ''}`}>
      <div className="studio-run-product-main__head">
        <h2 className="studio-run-section-title">目前产物</h2>
        {activeNodeState?.displayName && (
          <span className="studio-run-product-main__step">
            {activeNodeState.displayName}
          </span>
        )}
      </div>
      <pre className="studio-run-product-main__body">{productPreview}</pre>
    </div>
  );
}

function WorkflowVariablesPanel({ variables, labelMap }) {
  const entries = Object.entries(variables || {});
  if (entries.length === 0) return null;

  return (
    <section className="studio-run-preview-section">
      <h2 className="studio-run-section-title">工作流变量</h2>
      <dl className="studio-run-variables">
        {entries.map(([key, value]) => (
          <div key={key} className="studio-run-variables__row">
            <dt title={key}>{getWorkflowVariableLabel(key, labelMap)}</dt>
            <dd>{typeof value === 'string' ? value : JSON.stringify(value)}</dd>
          </div>
        ))}
      </dl>
    </section>
  );
}

function CurrentNodePanel({ activeNodeState, graphCurrentNodeId, graphPipeline }) {
  const nodeName =
    activeNodeState?.displayName ||
    graphPipeline?.nodes?.find((n) => n.id === graphCurrentNodeId)?.displayName ||
    '—';
  const nodeStatus = activeNodeState?.status;

  return (
    <section className="studio-run-preview-section">
      <h2 className="studio-run-section-title">当前节点</h2>
      <dl className="studio-run-overview studio-run-overview--inline">
        <div>
          <dt>步骤</dt>
          <dd>{nodeName}</dd>
        </div>
        {nodeStatus && (
          <div>
            <dt>状态</dt>
            <dd>{RUN_STATUS_LABEL[nodeStatus] || nodeStatus}</dd>
          </div>
        )}
        {activeNodeState?.skillId && (
          <div>
            <dt>技能</dt>
            <dd>{activeNodeState.skillId}</dd>
          </div>
        )}
      </dl>
    </section>
  );
}

function InsertionSidebar({ insertionPreview, onOpenEntry }) {
  if (!insertionPreview) {
    return (
      <section className="studio-run-right-section">
        <h2 className="studio-run-section-title">插入内容</h2>
        <p className="studio-run-right-placeholder">当前步骤无世界书插入配置</p>
      </section>
    );
  }

  return (
    <section className="studio-run-right-section">
      <h2 className="studio-run-section-title">插入内容</h2>
      <button
        type="button"
        className="studio-run-insertion-entry"
        onClick={() => onOpenEntry(insertionPreview)}
        title={insertionPreview.comment || insertionPreview.key}
      >
        <span className="studio-run-insertion-entry__key">{insertionPreview.key}</span>
        {insertionPreview.comment ? (
          <span className="studio-run-insertion-entry__meta">{insertionPreview.comment}</span>
        ) : null}
      </button>
    </section>
  );
}

function StudioRunPage() {
  const {
    pipeline: projectPipeline,
    workflowVariables: workflowVariablesCatalog,
    meta,
    runProjectId,
    runs,
    currentRunId,
    currentRun,
    runLoading,
    runAdvancing,
    runMessaging,
    runStreamingThinking,
    runError,
    runSessionEntered,
    setRunSessionEntered,
    initStudioRun,
    fetchProject,
    selectRun,
    advanceRun,
    saveWorldbookRun,
    switchRunNode,
    sendRunMessage,
    undoRun,
    rerollRun,
    interruptRun,
    deleteRun,
    renameRun,
    skillTemplates,
  } = useStudioStore();

  const sidebarMode = useAppLayoutStore((s) => s.sidebarMode);
  const isSidebarHovered = useAppLayoutStore((s) => s.isSidebarHovered);
  const setSidebarHovered = useAppLayoutStore((s) => s.setSidebarHovered);

  const hoverTimeoutRef = useRef(null);
  const leaveTimeoutRef = useRef(null);

  const handleLeftMouseEnter = useCallback(() => {
    if (sidebarMode !== 'smart') return;
    if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current);
    hoverTimeoutRef.current = setTimeout(() => {
      setSidebarHovered(true);
    }, 400);
  }, [sidebarMode, setSidebarHovered]);

  const handleLeftMouseLeave = useCallback(() => {
    if (sidebarMode !== 'smart') return;
    if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
    leaveTimeoutRef.current = setTimeout(() => {
      setSidebarHovered(false);
    }, 250);
  }, [sidebarMode, setSidebarHovered]);

  useEffect(() => {
    return () => {
      if (hoverTimeoutRef.current) clearTimeout(hoverTimeoutRef.current);
      if (leaveTimeoutRef.current) clearTimeout(leaveTimeoutRef.current);
    };
  }, []);

  const variableLabelMap = useMemo(
    () => buildWorkflowVariableLabelMap(workflowVariablesCatalog),
    [workflowVariablesCatalog]
  );
  const [insertionPopupOpen, setInsertionPopupOpen] = useState(false);
  const [insertionPopupExpanded, setInsertionPopupExpanded] = useState(true);
  const [contextBlockPopup, setContextBlockPopup] = useState(null);
  const [chatInput, setChatInput] = useState('');
  const [streamOutput, setStreamOutput] = useState(false);
  const [toolOptionIndex, setToolOptionIndex] = useState(0);
  const [runListExpanded, setRunListExpanded] = useState(false);
  const [renamingId, setRenamingId] = useState(null);
  const [renameValue, setRenameValue] = useState('');
  const [confirmPending, setConfirmPending] = useState(null);
  const confirmTimeoutRef = useRef(null);

  useEffect(() => {
    initStudioRun();
  }, [initStudioRun]);

  useEffect(() => {
    if (runProjectId) {
      fetchProject(runProjectId);
    }
  }, [runProjectId, fetchProject]);

  useEffect(() => {
    setRunSessionEntered(false);
    setInsertionPopupOpen(false);
    setContextBlockPopup(null);
  }, [currentRunId, runProjectId, setRunSessionEntered]);

  const activeNodeState = useMemo(() => {
    if (!currentRun?.currentNodeId) return null;
    return currentRun.nodeStates?.find((n) => n.nodeId === currentRun.currentNodeId);
  }, [currentRun]);

  const activePipelineNode = useMemo(() => {
    if (!currentRun?.currentNodeId) return null;
    return currentRun.pipelineSnapshot?.nodes?.find(
      (n) => n.id === currentRun.currentNodeId
    );
  }, [currentRun]);

  const isInitBindPending =
    activeNodeState?.status === 'active' &&
    activeNodeState?.skillId === 'studio.init_bind' &&
    !isInitBindCompleted(activeNodeState);

  const runControls = useMemo(
    () => getRunControls(activeNodeState?.skillId, skillTemplates),
    [activeNodeState?.skillId, skillTemplates]
  );

  const isWorldbookActive =
    activeNodeState?.status === 'active' &&
    hasRunControl(activeNodeState?.skillId, 'interrupt', skillTemplates);

  const toolQuestions = useMemo(
    () => activeNodeState?.lastToolResponse?.questions || [],
    [activeNodeState]
  );

  useEffect(() => {
    setToolOptionIndex(0);
  }, [currentRunId, activeNodeState?.nodeId, toolQuestions.length]);

  const clearConfirmPending = useCallback(() => {
    if (confirmTimeoutRef.current) {
      clearTimeout(confirmTimeoutRef.current);
      confirmTimeoutRef.current = null;
    }
    setConfirmPending(null);
  }, []);

  useEffect(() => {
    clearConfirmPending();
  }, [currentRunId, activeNodeState?.nodeId, clearConfirmPending]);

  useEffect(() => {
    if (!confirmPending) return undefined;
    const handleClickOutside = (event) => {
      if (!event.target.closest('.studio-run-turn-btn')) {
        clearConfirmPending();
      }
    };
    document.addEventListener('mousedown', handleClickOutside);
    return () => document.removeEventListener('mousedown', handleClickOutside);
  }, [confirmPending, clearConfirmPending]);

  useEffect(() => {
    return () => {
      if (confirmTimeoutRef.current) clearTimeout(confirmTimeoutRef.current);
    };
  }, []);

  const requestConfirmAction = useCallback((actionKey) => {
    if (confirmPending === actionKey) {
      clearConfirmPending();
      return true;
    }
    if (confirmTimeoutRef.current) clearTimeout(confirmTimeoutRef.current);
    setConfirmPending(actionKey);
    confirmTimeoutRef.current = setTimeout(() => {
      setConfirmPending(null);
      confirmTimeoutRef.current = null;
    }, CONFIRM_ACTION_TIMEOUT_MS);
    return false;
  }, [confirmPending, clearConfirmPending]);

  const graphPipeline = currentRun?.pipelineSnapshot || projectPipeline;
  const graphNodeStates = currentRun?.nodeStates || [];
  const graphCurrentNodeId = currentRun?.currentNodeId || null;

  const insertionPreview = useMemo(() => {
    const ins = activePipelineNode?.config?.insertion;
    if (!ins) return null;
    return {
      key: ins.key || '（未设置关键词）',
      comment: ins.comment || '',
      position: ins.position,
      activationType: ins.activationType,
      keysecondary: ins.keysecondary || '',
    };
  }, [activePipelineNode]);

  const promptBlocks = currentRun?.lastPromptBlocks || [];
  const runWorkflowVariables = currentRun?.workflowVariables || {};
  const stepMessages = activeNodeState?.stepMessages || [];
  const lastToolResponse = activeNodeState?.lastToolResponse;

  const characters = useCharacterStore((s) => s.characters);
  const boundCharacterName = useMemo(
    () =>
      resolveBoundCharacterName({
        boundCharacterVar: runWorkflowVariables['workflow.boundCharacter'],
        characterId: meta?.characterId,
        characters,
      }),
    [runWorkflowVariables, meta?.characterId, characters]
  );

  const handleOpenInsertion = () => {
    setInsertionPopupExpanded(true);
    setInsertionPopupOpen(true);
  };

  const handleInitBindSubmit = async (displayParams) => {
    await advanceRun(displayParams);
  };

  const handleSelectRun = async (runId) => {
    setRunSessionEntered(false);
    await selectRun(runId);
  };

  const handleEnterSession = () => {
    if (currentRun) {
      setRunSessionEntered(true);
    }
  };

  const handleChatSend = async (text) => {
    if (!text || isInitBindPending || !isWorldbookActive || runMessaging) return;

    setChatInput('');

    const run = await sendRunMessage(text, { stream: streamOutput });
    if (!run) {
      /* error surfaced via runError */
    }
  };

  const handleUndo = async () => {
    if (!hasRunControl(activeNodeState?.skillId, 'undo', skillTemplates)) return;
    if (runMessaging || !canUndoRun(activeNodeState)) return;
    clearConfirmPending();
    await undoRun();
  };

  const handleReroll = async () => {
    if (!hasRunControl(activeNodeState?.skillId, 'reroll', skillTemplates)) return;
    if (runMessaging || !canRerollRun(activeNodeState)) return;
    clearConfirmPending();
    await rerollRun({ stream: streamOutput });
  };

  const handleUndoClick = () => {
    if (runAdvancing || runMessaging || !canUndo) return;
    requestConfirmAction('undo');
  };

  const handleRerollClick = () => {
    if (runAdvancing || runMessaging || !canReroll) return;
    requestConfirmAction('reroll');
  };

  const handleNextStep = async () => {
    if (runAdvancing || runMessaging || !canAdvanceNextStep(activeNodeState)) {
      return;
    }
    clearConfirmPending();
    const run = await advanceRun({});
    if (run) {
      setChatInput('');
      setToolOptionIndex(0);
    }
  };

  const handleSaveIncremental = async () => {
    if (!hasRunControl(activeNodeState?.skillId, 'incrementalSave', skillTemplates)) return;
    if (runAdvancing || runMessaging || !canSaveWorldbookEntry(activeNodeState)) return;
    clearConfirmPending();
    await saveWorldbookRun('incremental');
  };

  const handleSaveOverwrite = async () => {
    if (!hasRunControl(activeNodeState?.skillId, 'overwriteSave', skillTemplates)) return;
    if (runAdvancing || runMessaging || !canSaveWorldbookEntry(activeNodeState)) return;
    clearConfirmPending();
    await saveWorldbookRun('overwrite');
  };

  const handleSaveIncrementalClick = () => {
    if (runAdvancing || runMessaging || !canSaveWorldbookEntry(activeNodeState)) return;
    requestConfirmAction('saveIncremental');
  };

  const handleSaveOverwriteClick = () => {
    if (runAdvancing || runMessaging || !canSaveWorldbookEntry(activeNodeState)) return;
    requestConfirmAction('saveOverwrite');
  };

  const handleGraphNodeSelect = useCallback(
    async (nodeId) => {
      if (!currentRun || nodeId === currentRun.currentNodeId) return;
      if (runLoading || runAdvancing || runMessaging) return;
      const nodeState = currentRun.nodeStates?.find((n) => n.nodeId === nodeId);
      if (!canSwitchToNode(nodeState)) return;
      clearConfirmPending();
      const run = await switchRunNode(nodeId);
      if (run) {
        setChatInput('');
        setToolOptionIndex(0);
      }
    },
    [
      currentRun,
      runLoading,
      runAdvancing,
      runMessaging,
      switchRunNode,
      clearConfirmPending,
    ]
  );

  const handleDeleteRun = async (runId) => {
    const name = runDisplayName(runs.find((r) => r.id === runId) || {});
    if (!window.confirm(`确定删除运行「${name}」？此操作不可撤销。`)) return;
    if (currentRunId === runId) setRunSessionEntered(false);
    await deleteRun(runId);
  };

  const handleStartRename = (run) => {
    setRenamingId(run.id);
    setRenameValue(run.title?.trim() || runDisplayName(run));
  };

  const handleRenameSubmit = async (runId) => {
    const title = renameValue.trim();
    if (!title) return;
    const ok = await renameRun(runId, title);
    if (ok) {
      setRenamingId(null);
      setRenameValue('');
    }
  };

  const runListNeedsCollapse = runs.length > RUN_LIST_COLLAPSE_THRESHOLD;
  const inSession = runSessionEntered && !!currentRun;
  const showToolCarousel =
    inSession && runControls.includes('questions') && toolQuestions.length > 0;
  const showNextStep =
    inSession && shouldShowFirstExport(activeNodeState);
  const showSaveIncremental =
    inSession &&
    hasRunControl(activeNodeState?.skillId, 'incrementalSave', skillTemplates) &&
    shouldShowSaveAppendOverwrite(activeNodeState);
  const showSaveOverwrite =
    inSession &&
    hasRunControl(activeNodeState?.skillId, 'overwriteSave', skillTemplates) &&
    shouldShowSaveAppendOverwrite(activeNodeState);
  const canSaveEntry = canSaveWorldbookEntry(activeNodeState);
  const showUndo = runControls.includes('undo');
  const showReroll = runControls.includes('reroll');
  const canUndo = canUndoRun(activeNodeState);
  const canReroll = canRerollRun(activeNodeState);
  const canNextStep = canAdvanceNextStep(activeNodeState);
  const showTurnActions = inSession && (showUndo || showReroll);
  const canInterrupt = runControls.includes('interrupt');

  const renderRunList = () => (
    <>
      {runLoading && runs.length === 0 ? (
        <div className="studio-run-empty">加载中…</div>
      ) : runs.length === 0 ? (
        <div className="studio-run-empty">暂无会话记录，点击「新开会话」开始</div>
      ) : (
        <>
          <ul
            className={`studio-run-list${runListNeedsCollapse && !runListExpanded ? ' is-collapsed' : ''}`}
            style={
              runListNeedsCollapse && !runListExpanded
                ? { maxHeight: `${RUN_ITEM_HEIGHT * 3.5}px` }
                : undefined
            }
          >
            {runs.map((run) => (
              <RunListItem
                key={run.id}
                run={run}
                selected={currentRunId === run.id}
                onSelect={handleSelectRun}
                onDelete={handleDeleteRun}
                renamingId={renamingId}
                renameValue={renameValue}
                onRenameChange={setRenameValue}
                onRenameSubmit={handleRenameSubmit}
                onRenameCancel={() => {
                  setRenamingId(null);
                  setRenameValue('');
                }}
                onStartRename={handleStartRename}
              />
            ))}
          </ul>
          {runListNeedsCollapse && (
            <button
              type="button"
              className="studio-run-list-fold"
              onClick={() => setRunListExpanded((v) => !v)}
            >
              {runListExpanded ? '收起列表' : `展开全部（共 ${runs.length} 条）`}
            </button>
          )}
        </>
      )}
    </>
  );

  const renderNodeProgress = () => {
    if (!graphPipeline) {
      return <div className="studio-run-empty">暂无流水线数据</div>;
    }
    return (
      <StudioRunNodeGraph
        pipeline={graphPipeline}
        nodeStates={graphNodeStates}
        currentNodeId={graphCurrentNodeId}
        variant="sidebar"
        onNodeSelect={currentRun ? handleGraphNodeSelect : undefined}
        canSelectNode={canSwitchToNode}
      />
    );
  };

  const renderLeftBrowseView = () => (
    <div className="studio-run-left-stack">
      <section className="studio-run-left-stack__sessions">
        <h2 className="studio-run-section-title">会话列表</h2>
        {renderRunList()}
      </section>
      <section className="studio-run-left-stack__progress">
        <h2 className="studio-run-section-title">节点进度</h2>
        <p className="studio-run-hint studio-run-hint--inline">
          基于各步骤上文引用（inputs.ref）生成的依赖顺序。
        </p>
        {renderNodeProgress()}
      </section>
    </div>
  );

  const renderLeftSessionView = () => (
    <div className="studio-run-left-session">
      <ProductPreview
        activeNodeState={activeNodeState}
        isWorldbookActive={isWorldbookActive}
      />
      <section className="studio-run-left-session__graph">
        <h2 className="studio-run-section-title">节点进度</h2>
        <p className="studio-run-hint studio-run-hint--inline">
          点击已完成、进行中或已有草稿的步骤可切换当前焦点。
        </p>
        {renderNodeProgress()}
      </section>
      <PromptBlocksDebug
        blocks={promptBlocks}
        onBlockClick={setContextBlockPopup}
      />
    </div>
  );

  const renderRunPreviewCenter = () => {
    if (!runProjectId) {
      return <div className="studio-run-chat-empty">请先选择项目</div>;
    }

    if (!currentRun) {
      return (
        <div className="studio-run-preview-empty">
          <p className="studio-run-hint">在左侧选择或新建会话，此处将预览运行内容与节点状态。</p>
        </div>
      );
    }

    return (
      <div className="studio-run-preview">
        <header className="studio-run-preview__header">
          <div className="studio-run-preview__title-row">
            <h2 className="studio-run-preview__title">{runDisplayName(currentRun)}</h2>
            <span className={`studio-run-status-badge status-${currentRun.status}`}>
              {RUN_STATUS_LABEL[currentRun.status] || currentRun.status}
            </span>
          </div>
          <div className="studio-run-preview__meta">
            <span>创建 {formatRunTime(currentRun.createdAt)}</span>
            {boundCharacterName ? (
              <span className="studio-run-preview__character">
                角色卡：{boundCharacterName}
              </span>
            ) : null}
          </div>
          <button
            type="button"
            className="studio-run-enter-btn studio-run-enter-btn--header"
            onClick={handleEnterSession}
            disabled={runLoading || runAdvancing}
          >
            进入会话
          </button>
        </header>

        <div className="studio-run-preview__body">
          <CurrentNodePanel
            activeNodeState={activeNodeState}
            graphCurrentNodeId={graphCurrentNodeId}
            graphPipeline={graphPipeline}
          />
          <ProductPreview
            activeNodeState={activeNodeState}
            isWorldbookActive={isWorldbookActive}
            compact
          />
          <WorkflowVariablesPanel
            variables={runWorkflowVariables}
            labelMap={variableLabelMap}
          />
          {promptBlocks.length > 0 && (
            <section className="studio-run-preview-section">
              <PromptBlocksDebug
                blocks={promptBlocks}
                onBlockClick={setContextBlockPopup}
              />
            </section>
          )}
        </div>
      </div>
    );
  };

  const renderSessionCenter = () => {
    if (isInitBindPending && activePipelineNode) {
      return (
        <div className="studio-run-chat-card">
          <InitBindGuidanceForm
            node={{ ...activePipelineNode, nodeId: activePipelineNode.id }}
            runAdvancing={runAdvancing}
            onSubmit={handleInitBindSubmit}
            compact
          />
        </div>
      );
    }

    if (
      activeNodeState?.skillId === 'studio.init_bind' &&
      isInitBindCompleted(activeNodeState)
    ) {
      return (
        <div className="studio-run-chat-empty">
          <p>创建并绑定步骤已完成。</p>
          <p className="studio-run-hint">绑定信息见左侧「目前产物」。</p>
        </div>
      );
    }

    return (
      <div className="studio-run-chat-panel">
        <StudioRunChat
          stepMessages={stepMessages}
          thinking={runStreamingThinking ?? lastToolResponse?.thinking}
          evaluation={runStreamingThinking ? null : lastToolResponse?.evaluation}
          inputValue={chatInput}
          onInputChange={setChatInput}
          onSend={handleChatSend}
          onInterrupt={canInterrupt ? interruptRun : undefined}
          canInterrupt={canInterrupt}
          disabled={!isWorldbookActive || runAdvancing || isInitBindPending}
          sending={runMessaging}
          streamOutput={streamOutput}
          onStreamOutputChange={setStreamOutput}
          placeholder={
            isWorldbookActive
              ? '输入修改意见或与模型对话…'
              : currentRun?.status === 'completed'
                ? '运行已完成'
                : '等待当前步骤…'
          }
        />
      </div>
    );
  };

  const renderRightSidebar = () => (
    <>
      <InsertionSidebar
        insertionPreview={insertionPreview}
        onOpenEntry={handleOpenInsertion}
      />

      {showToolCarousel && (
        <ToolOptionsCarousel
          options={toolQuestions}
          index={toolOptionIndex}
          onPrev={() =>
            setToolOptionIndex((i) => (i - 1 + toolQuestions.length) % toolQuestions.length)
          }
          onNext={() =>
            setToolOptionIndex((i) => (i + 1) % toolQuestions.length)
          }
          runAdvancing={runAdvancing || runMessaging}
        />
      )}

      {showTurnActions && (
        <div className="studio-run-right-section studio-run-turn-actions">
          <h2 className="studio-run-section-title">回合控制</h2>
          <div className="studio-run-turn-actions__row">
            {showUndo ? (
              <ConfirmTurnButton
                actionKey="undo"
                pendingKey={confirmPending}
                label="回退"
                className="studio-run-turn-btn studio-run-turn-btn--undo"
                disabled={runAdvancing || runMessaging || !canUndo}
                title={canUndo ? '回退到上一回合' : '无可回退的回合'}
                onConfirm={handleUndo}
                onRequestConfirm={handleUndoClick}
              />
            ) : null}
            {showReroll ? (
              <ConfirmTurnButton
                actionKey="reroll"
                pendingKey={confirmPending}
                label="重roll"
                className="studio-run-turn-btn studio-run-turn-btn--reroll"
                disabled={runAdvancing || runMessaging || !canReroll}
                title={canReroll ? '用相同输入重新生成' : '尚无用户消息'}
                onConfirm={handleReroll}
                onRequestConfirm={handleRerollClick}
              />
            ) : null}
          </div>
        </div>
      )}

      {showNextStep && (
        <div className="studio-run-right-section studio-run-right-actions">
          <button
            type="button"
            className="studio-run-next-btn"
            onClick={handleNextStep}
            disabled={runAdvancing || runMessaging || !canNextStep}
            title={canNextStep ? '确认当前产物并进入下一节点' : '请先生成当前步骤产物'}
          >
            下一步
          </button>
          <p className="studio-run-stub-note">确认左侧目前产物无误后进入下一节点</p>
        </div>
      )}

      {(showSaveIncremental || showSaveOverwrite) && (
        <div className="studio-run-right-section studio-run-save-actions">
          <h2 className="studio-run-section-title">世界书保存</h2>
          <div className="studio-run-turn-actions__row">
            {showSaveIncremental ? (
              <ConfirmTurnButton
                actionKey="saveIncremental"
                pendingKey={confirmPending}
                label="增量保存"
                className="studio-run-turn-btn studio-run-turn-btn--append"
                disabled={runAdvancing || runMessaging || !canSaveEntry}
                title={canSaveEntry ? '追加为新世界书条目' : '请先生成当前步骤产物'}
                onConfirm={handleSaveIncremental}
                onRequestConfirm={handleSaveIncrementalClick}
              />
            ) : null}
            {showSaveOverwrite ? (
              <ConfirmTurnButton
                actionKey="saveOverwrite"
                pendingKey={confirmPending}
                label="覆盖保存"
                className="studio-run-turn-btn studio-run-turn-btn--overwrite"
                disabled={runAdvancing || runMessaging || !canSaveEntry}
                title={canSaveEntry ? '覆盖上次写入的条目' : '请先生成当前步骤产物'}
                onConfirm={handleSaveOverwrite}
                onRequestConfirm={handleSaveOverwriteClick}
              />
            ) : null}
          </div>
          <p className="studio-run-stub-note">增量保存会新建条目；覆盖保存会更新本步骤上次写入的条目。切换步骤请使用左侧节点图。</p>
        </div>
      )}
    </>
  );

  return (
    <div className={`studio-run-page${inSession ? ' studio-run-page--session' : ''}`}>
      {runError && (
        <div className="studio-run-banner error" role="alert">{runError}</div>
      )}

      <div
        className={`studio-run-body studio-run-sidebar-${sidebarMode}${
          sidebarMode === 'smart' && isSidebarHovered ? ' studio-run-sidebar-expanded' : ''
        }${inSession ? ' in-session' : ''}`}
      >
        <aside
          className={`studio-run-left sidebar-mode-${sidebarMode}${
            sidebarMode === 'smart' && isSidebarHovered ? ' sidebar-expanded' : ''
          }`}
          aria-label="左栏"
          onMouseEnter={handleLeftMouseEnter}
          onMouseLeave={handleLeftMouseLeave}
        >
          <div className="studio-run-left-content">
            {inSession ? renderLeftSessionView() : renderLeftBrowseView()}
          </div>
        </aside>

        <main className="studio-run-center" aria-label={inSession ? '对话' : '运行预览'}>
          {inSession ? renderSessionCenter() : renderRunPreviewCenter()}
        </main>

        <aside className="studio-run-right" aria-label="控制与插入">
          {currentRun ? renderRightSidebar() : (
            <p className="studio-run-right-placeholder">选择会话后显示插入内容与控制项</p>
          )}
        </aside>
      </div>

      <StudioContextBlockPopup
        open={!!contextBlockPopup}
        block={contextBlockPopup}
        onClose={() => setContextBlockPopup(null)}
      />

      <StudioInsertionPopup
        open={insertionPopupOpen && !!insertionPreview}
        title="插入内容"
        defaultExpanded={insertionPopupExpanded}
        onClose={() => setInsertionPopupOpen(false)}
      >
        {({ expanded }) =>
          expanded ? (
            <pre className="studio-insertion-popup__full">
              {JSON.stringify(insertionPreview, null, 2)}
            </pre>
          ) : (
            <div className="studio-insertion-popup__preview">
              <span className="studio-insertion-popup__field-label">关键词</span>
              <span className="studio-insertion-popup__field-value">
                {insertionPreview?.key}
              </span>
              {insertionPreview?.comment ? (
                <>
                  <span className="studio-insertion-popup__field-label">备注</span>
                  <span className="studio-insertion-popup__field-value">
                    {insertionPreview.comment}
                  </span>
                </>
              ) : null}
              <span className="studio-insertion-popup__field-label">位置 / 激活</span>
              <span className="studio-insertion-popup__field-value">
                {insertionPreview?.position} · {insertionPreview?.activationType}
              </span>
            </div>
          )
        }
      </StudioInsertionPopup>
    </div>
  );
}

export default StudioRunPage;
