/**
 * Resolve bound character display name from workflow variable and/or project meta.
 */
export function resolveBoundCharacterName({ boundCharacterVar, characterId, characters = [] }) {
  if (boundCharacterVar) {
    const nameMatch = String(boundCharacterVar).match(/名称[：:]\s*(.+?)(?:\n|$)/);
    if (nameMatch?.[1]?.trim()) {
      return nameMatch[1].trim();
    }
  }

  if (characterId && characters.length > 0) {
    const byId = characters.find((c) => c.id === characterId);
    if (byId?.name) return byId.name;
    const byName = characters.find((c) => c.name === characterId);
    if (byName?.name) return byName.name;
  }

  return null;
}

/** Resolve bound worldbook display name from workflow variable and/or project meta. */
export function resolveBoundWorldbookName({
  boundWorldbookVar,
  worldbookId,
  worldBooks = [],
}) {
  if (boundWorldbookVar) {
    const nameMatch = String(boundWorldbookVar).match(/名称[：:]\s*(.+?)(?:\n|$)/);
    if (nameMatch?.[1]?.trim()) {
      return nameMatch[1].trim();
    }
  }

  if (worldbookId && worldBooks.length > 0) {
    const byId = worldBooks.find((wb) => wb.id === worldbookId);
    if (byId?.name) return byId.name;
  }

  return null;
}

/** Index of the last user message in stepMessages, or -1. */
export function findLastUserMessageIndex(stepMessages = []) {
  for (let i = stepMessages.length - 1; i >= 0; i -= 1) {
    if (stepMessages[i]?.role === 'user') return i;
  }
  return -1;
}

/** Whether undo is available for the active node state. */
export function canUndoRun(activeNodeState) {
  return (activeNodeState?.turnHistory?.length ?? 0) > 0;
}

/** Whether reroll is available (at least one user message in current step). */
export function canRerollRun(activeNodeState) {
  return findLastUserMessageIndex(activeNodeState?.stepMessages) >= 0;
}

/** Whether the user may advance to the next pipeline node (first export). */
export function canAdvanceNextStep(activeNodeState) {
  return Boolean(activeNodeState?.lastDraft) && !nodeHasWrittenEntry(activeNodeState);
}

/** Node has been written to worldbook at least once. */
export function nodeHasWrittenEntry(activeNodeState) {
  if (!activeNodeState) return false;
  if (activeNodeState.status === 'completed') return true;
  return Boolean(activeNodeState.lastDraft?.writtenEntryUid);
}

/** Show 下一步 (first export + advance). */
export function shouldShowNextStep(activeNodeState) {
  return !nodeHasWrittenEntry(activeNodeState);
}

/** Show 增量保存 / 覆盖保存 after first write or on revisit. */
export function shouldShowSaveAppendOverwrite(activeNodeState) {
  return nodeHasWrittenEntry(activeNodeState) && Boolean(activeNodeState?.lastDraft);
}

/** Whether append/overwrite save is allowed (needs draft content). */
export function canSaveWorldbookEntry(activeNodeState) {
  return Boolean(activeNodeState?.lastDraft?.entryContent?.trim());
}

/** Whether a pipeline node may be manually selected on the run graph. */
export function canSwitchToNode(nodeState) {
  if (!nodeState || nodeState.skillId !== 'studio.worldbook_entry') return false;
  return nodeState.status === 'active' || nodeState.status === 'completed';
}

const RUN_CONTROL_ALIASES = {
  incrementalSave: ['incrementalSave', 'saveAppend'],
  overwriteSave: ['overwriteSave', 'saveOverwrite'],
  nextStep: ['nextStep'],
};

const DEFAULT_RUN_CONTROLS = {
  'studio.worldbook_entry': [
    'undo',
    'reroll',
    'interrupt',
    'incrementalSave',
    'overwriteSave',
    'questions',
  ],
  'studio.init_bind': [],
};

/** Resolve run-page control ids for a skill template (from config or defaults). */
export function getRunControls(skillId, skillTemplates = []) {
  if (!skillId) return [];
  const tpl = skillTemplates.find((t) => t.skillId === skillId);
  if (Array.isArray(tpl?.runControls)) return tpl.runControls;
  return DEFAULT_RUN_CONTROLS[skillId] || [];
}

function controlMatches(runControls, control) {
  const aliases = RUN_CONTROL_ALIASES[control] || [control];
  return aliases.some((id) => runControls.includes(id));
}

export function hasRunControl(skillId, control, skillTemplates = []) {
  const runControls = getRunControls(skillId, skillTemplates);
  return controlMatches(runControls, control);
}

/** First export uses 下一步 even when not listed in runControls template. */
export function shouldShowFirstExport(activeNodeState) {
  return (
    activeNodeState?.skillId === 'studio.worldbook_entry'
    && shouldShowNextStep(activeNodeState)
  );
}
