import { javascript } from '@codemirror/lang-javascript';
import { python } from '@codemirror/lang-python';
import { ensureSyntaxTree, syntaxTree } from '@codemirror/language';
import { EditorState } from '@codemirror/state';

import type { Parameter } from '#types';

import {
  type CodeNodeEngine,
  isValidCodeNodeParameterName,
  normalizeCodeNodeEngine,
} from './codeNodeScaffold';

type SyntaxNode = ReturnType<typeof syntaxTree>['topNode'];

export type CodeNodeDataType =
  | 'Array'
  | 'Boolean'
  | 'File'
  | 'Number'
  | 'Object'
  | 'String';

export type CodeNodeSyncResult = {
  code: string;
  reason?: string;
  synced: boolean;
};

export type InferredCodeNodeOutput = {
  dataType: CodeNodeDataType;
  name: string;
};

type SourceEdit = {
  from: number;
  insert: string;
  to: number;
};

type ParsedDocument = {
  engine: CodeNodeEngine;
  source: string;
  topNode: SyntaxNode;
};

type MainFunction = {
  body: SyntaxNode;
  node: SyntaxNode;
  paramList: SyntaxNode;
};

type ReturnEntry = {
  keyFrom: number;
  keyTo: number;
  name: string;
  propertyFrom: number;
  propertyTo: number;
  shorthand: boolean;
  valueNode: SyntaxNode;
};

type ReturnObject = {
  closeBraceFrom: number;
  entries: ReturnEntry[];
  node: SyntaxNode;
};

const SUPPORTED_DATA_TYPES = new Set<CodeNodeDataType>([
  'String',
  'Number',
  'Boolean',
  'File',
  'Object',
  'Array',
]);

const PUNCTUATION_NODE_NAMES = new Set([
  '(',
  ')',
  '[',
  ']',
  '{',
  '}',
  ',',
  ':',
  ';',
  '=',
  'AssignOp',
  'Equals',
]);

const COMPLEX_CONTROL_FLOW_NODE_NAMES = new Set([
  'DoStatement',
  'ForInStatement',
  'ForOfStatement',
  'ForStatement',
  'IfStatement',
  'SwitchStatement',
  'TryStatement',
  'WhileStatement',
  'WithStatement',
]);

function childNodes(node: SyntaxNode) {
  const result: SyntaxNode[] = [];
  for (let child = node.firstChild; child; child = child.nextSibling) {
    result.push(child);
  }
  return result;
}

function nodeText(node: SyntaxNode, source: string) {
  return source.slice(node.from, node.to);
}

function hasParseError(node: SyntaxNode): boolean {
  if (node.type.isError) {
    return true;
  }
  return childNodes(node).some(hasParseError);
}

function parseDocument(
  source: string,
  rawEngine?: string,
): ParsedDocument | null {
  const engine = normalizeCodeNodeEngine(rawEngine);
  const state = EditorState.create({
    doc: source,
    extensions: [engine === 'python' ? python() : javascript()],
  });
  const tree = ensureSyntaxTree(state, source.length, 100) || syntaxTree(state);
  if (tree.length < source.length || hasParseError(tree.topNode)) {
    return null;
  }
  return {
    engine,
    source,
    topNode: tree.topNode,
  };
}

function findMainFunction(document: ParsedDocument): MainFunction | null {
  const functionNodeName =
    document.engine === 'python' ? 'FunctionDefinition' : 'FunctionDeclaration';
  const candidates = childNodes(document.topNode).filter(
    (node) => node.name === functionNodeName,
  );
  const matched = candidates.filter((node) => {
    const nameNode = childNodes(node).find(
      (child) =>
        child.name === 'VariableName' || child.name === 'VariableDefinition',
    );
    return nameNode && nodeText(nameNode, document.source) === 'main';
  });
  if (matched.length !== 1) {
    return null;
  }

  const node = matched[0];
  const children = childNodes(node);
  const paramList = children.find((child) => child.name === 'ParamList');
  const body = children.find(
    (child) => child.name === 'Body' || child.name === 'Block',
  );
  if (!paramList || !body) {
    return null;
  }
  return {
    body,
    node,
    paramList,
  };
}

function significantChildren(node: SyntaxNode) {
  return childNodes(node).filter(
    (child) => !PUNCTUATION_NODE_NAMES.has(child.name),
  );
}

function readSimpleNames(
  listNode: SyntaxNode,
  source: string,
  allowedNodeNames: Set<string>,
) {
  const children = significantChildren(listNode);
  if (children.some((child) => !allowedNodeNames.has(child.name))) {
    return null;
  }
  return children.map((child) => nodeText(child, source));
}

function findExplicitMainCalls(document: ParsedDocument) {
  const matches: SyntaxNode[] = [];
  for (const statement of childNodes(document.topNode)) {
    if (statement.name === 'FunctionDefinition') {
      continue;
    }
    if (statement.name === 'FunctionDeclaration') {
      continue;
    }

    const assignment =
      document.engine === 'python'
        ? statement.name === 'AssignStatement'
          ? statement
          : null
        : statement.name === 'ExpressionStatement'
          ? significantChildren(statement).find(
              (child) => child.name === 'AssignmentExpression',
            ) || null
          : null;
    if (!assignment) {
      continue;
    }

    const assignmentChildren = significantChildren(assignment);
    const target = assignmentChildren[0];
    const call = assignmentChildren.find(
      (child) => child.name === 'CallExpression',
    );
    if (!target || nodeText(target, document.source) !== '_result' || !call) {
      continue;
    }

    const callChildren = childNodes(call);
    const callee = callChildren.find(
      (child) =>
        child.name === 'VariableName' || child.name === 'VariableDefinition',
    );
    const argList = callChildren.find((child) => child.name === 'ArgList');
    if (callee && nodeText(callee, document.source) === 'main' && argList) {
      matches.push(argList);
    }
  }
  return matches;
}

function arraysEqual(left: string[], right: string[]) {
  return (
    left.length === right.length &&
    left.every((value, index) => value === right[index])
  );
}

function applySourceEdits(source: string, edits: SourceEdit[]) {
  if (edits.length === 0) {
    return source;
  }
  const sorted = [...edits].sort(
    (left, right) => right.from - left.from || right.to - left.to,
  );
  let boundary = source.length;
  let result = source;
  for (const edit of sorted) {
    if (
      edit.from < 0 ||
      edit.to < edit.from ||
      edit.to > source.length ||
      edit.to > boundary
    ) {
      return source;
    }
    result = result.slice(0, edit.from) + edit.insert + result.slice(edit.to);
    boundary = edit.from;
  }
  return result;
}

function parameterNames(
  parameters: Parameter[],
  engine: CodeNodeEngine,
  field: 'codeSyncName' | 'name',
) {
  const names = parameters.map((parameter) =>
    String(parameter[field] || '').trim(),
  );
  if (names.some((name) => !name)) {
    return null;
  }
  if (names.some((name) => !isValidCodeNodeParameterName(name, engine))) {
    return null;
  }
  if (new Set(names).size !== names.length) {
    return null;
  }
  return names;
}

function withPreviousSyncNames(previous: Parameter[], next: Parameter[]) {
  const previousById = new Map(
    previous
      .filter((parameter) => parameter.id)
      .map((parameter) => [parameter.id, parameter]),
  );
  return next.map((parameter, index) => {
    const oldParameter = parameter.id
      ? previousById.get(parameter.id)
      : previous[index];
    const previousSyncName = String(
      oldParameter?.codeSyncName || oldParameter?.name || '',
    ).trim();
    if (parameter.codeSyncName || !previousSyncName) {
      return { ...parameter };
    }
    return {
      ...parameter,
      codeSyncName: previousSyncName,
    };
  });
}

/**
 * 在 main 签名和可选显式调用仍受控时，同步代码节点输入参数。
 */
export function syncCodeNodeInputParameters(
  source: string,
  rawEngine: string | undefined,
  previous: Parameter[],
  next: Parameter[],
) {
  const engine = normalizeCodeNodeEngine(rawEngine);
  const nextWithSyncNames = withPreviousSyncNames(previous, next);
  const nextNames = parameterNames(nextWithSyncNames, engine, 'name');
  if (!nextNames) {
    return {
      code: source,
      parameters: nextWithSyncNames,
      reason: '输入参数名称为空、重复或不是合法变量名',
      synced: false,
    };
  }

  const previousNames = parameterNames(
    previous
      .map((parameter) => ({
        ...parameter,
        codeSyncName: parameter.codeSyncName || parameter.name,
      }))
      .filter((parameter) => String(parameter.codeSyncName || '').trim()),
    engine,
    'codeSyncName',
  );
  const document = parseDocument(source, engine);
  const mainFunction = document && findMainFunction(document);
  const explicitCalls = document ? findExplicitMainCalls(document) : [];
  if (!document || !mainFunction || explicitCalls.length > 1) {
    return {
      code: source,
      parameters: nextWithSyncNames,
      reason: '未找到可安全同步的唯一 main 函数',
      synced: false,
    };
  }

  const currentParams = readSimpleNames(
    mainFunction.paramList,
    source,
    new Set(['VariableDefinition', 'VariableName']),
  );
  const explicitCall = explicitCalls[0];
  const currentArguments = explicitCall
    ? readSimpleNames(explicitCall, source, new Set(['VariableName']))
    : previousNames;
  if (
    !currentParams ||
    !currentArguments ||
    !previousNames ||
    !arraysEqual(currentParams, previousNames) ||
    !arraysEqual(currentArguments, previousNames)
  ) {
    return {
      code: source,
      parameters: nextWithSyncNames,
      reason: 'main 参数已由用户接管',
      synced: false,
    };
  }

  const replacement = nextNames.join(', ');
  const edits: SourceEdit[] = [
    {
      from: mainFunction.paramList.from + 1,
      to: mainFunction.paramList.to - 1,
      insert: replacement,
    },
  ];
  if (explicitCall) {
    edits.push({
      from: explicitCall.from + 1,
      to: explicitCall.to - 1,
      insert: replacement,
    });
  }
  const code = applySourceEdits(source, edits);
  return {
    code,
    parameters: nextWithSyncNames.map((parameter) => ({
      ...parameter,
      codeSyncName: String(parameter.name || '').trim(),
    })),
    synced: code !== source || arraysEqual(previousNames, nextNames),
  };
}

function parseQuotedString(rawValue: string) {
  const raw = rawValue.trim();
  if (raw.length < 2) {
    return null;
  }
  if (raw.startsWith('"') && raw.endsWith('"')) {
    try {
      return JSON.parse(raw) as string;
    } catch {
      return null;
    }
  }
  if (raw.startsWith("'") && raw.endsWith("'")) {
    return raw.slice(1, -1).replace(/\\\\/g, '\\').replace(/\\'/g, "'");
  }
  return null;
}

function parseJavaScriptReturnObject(
  node: SyntaxNode,
  source: string,
): ReturnObject | null {
  const entries: ReturnEntry[] = [];
  let closeBraceFrom = node.to - 1;
  for (const child of childNodes(node)) {
    if (child.name === '}') {
      closeBraceFrom = child.from;
      continue;
    }
    if (PUNCTUATION_NODE_NAMES.has(child.name)) {
      continue;
    }
    if (child.name !== 'Property') {
      return null;
    }
    const propertyChildren = childNodes(child);
    const keyNode = propertyChildren.find(
      (propertyChild) =>
        propertyChild.name === 'PropertyDefinition' ||
        propertyChild.name === 'String',
    );
    const propertySource = nodeText(child, source);
    if (!keyNode || propertySource.trim().startsWith('[')) {
      return null;
    }
    const keyRaw = nodeText(keyNode, source);
    const name = parseQuotedString(keyRaw) || keyRaw;
    if (!name) {
      return null;
    }
    const colonIndex = propertyChildren.findIndex(
      (propertyChild) => propertyChild.name === ':',
    );
    const shorthand = colonIndex < 0;
    const valueNode = shorthand
      ? keyNode
      : propertyChildren
          .slice(colonIndex + 1)
          .find(
            (propertyChild) => !PUNCTUATION_NODE_NAMES.has(propertyChild.name),
          );
    if (!valueNode) {
      return null;
    }
    entries.push({
      name,
      keyFrom: keyNode.from,
      keyTo: keyNode.to,
      propertyFrom: child.from,
      propertyTo: child.to,
      shorthand,
      valueNode,
    });
  }
  return {
    node,
    closeBraceFrom,
    entries,
  };
}

function parsePythonReturnObject(
  node: SyntaxNode,
  source: string,
): ReturnObject | null {
  const children = childNodes(node);
  const entries: ReturnEntry[] = [];
  let closeBraceFrom = node.to - 1;
  for (let index = 0; index < children.length; index += 1) {
    const child = children[index];
    if (child.name === '}') {
      closeBraceFrom = child.from;
      continue;
    }
    if (PUNCTUATION_NODE_NAMES.has(child.name)) {
      continue;
    }
    if (child.name !== 'String') {
      return null;
    }
    const colon = children[index + 1];
    const valueNode = children[index + 2];
    if (!colon || colon.name !== ':' || !valueNode) {
      return null;
    }
    const name = parseQuotedString(nodeText(child, source));
    if (!name) {
      return null;
    }
    entries.push({
      name,
      keyFrom: child.from,
      keyTo: child.to,
      propertyFrom: child.from,
      propertyTo: valueNode.to,
      shorthand: false,
      valueNode,
    });
    index += 2;
  }
  return {
    node,
    closeBraceFrom,
    entries,
  };
}

function collectReturnObjects(
  document: ParsedDocument,
  mainFunction: MainFunction,
) {
  const result: ReturnObject[] = [];
  const expectedObjectName =
    document.engine === 'python' ? 'DictionaryExpression' : 'ObjectExpression';

  function visit(node: SyntaxNode) {
    if (
      node !== mainFunction.node &&
      (node.name === 'FunctionDefinition' ||
        node.name === 'FunctionDeclaration')
    ) {
      return;
    }
    if (node.name === 'ReturnStatement') {
      const valueNode = significantChildren(node).find(
        (child) => child.name !== 'return',
      );
      if (valueNode?.name === expectedObjectName) {
        const returnObject =
          document.engine === 'python'
            ? parsePythonReturnObject(valueNode, document.source)
            : parseJavaScriptReturnObject(valueNode, document.source);
        if (returnObject) {
          result.push(returnObject);
        }
      }
      return;
    }
    childNodes(node).forEach(visit);
  }

  visit(mainFunction.body);
  return result;
}

function normalizeDataType(value?: string) {
  return SUPPORTED_DATA_TYPES.has(value as CodeNodeDataType)
    ? (value as CodeNodeDataType)
    : undefined;
}

function inferExpressionType(
  node: SyntaxNode,
  source: string,
  environment: Map<string, CodeNodeDataType>,
): CodeNodeDataType | undefined {
  if (
    node.name === 'String' ||
    node.name === 'TemplateString' ||
    node.name === 'FormatString'
  ) {
    return 'String';
  }
  if (node.name === 'Number') {
    return 'Number';
  }
  if (node.name === 'Boolean' || node.name === 'BooleanLiteral') {
    return 'Boolean';
  }
  if (
    node.name === 'ObjectExpression' ||
    node.name === 'DictionaryExpression'
  ) {
    return 'Object';
  }
  if (
    node.name === 'ArrayExpression' ||
    node.name === 'ListExpression' ||
    node.name === 'SetExpression' ||
    node.name === 'TupleExpression'
  ) {
    return 'Array';
  }
  if (
    node.name === 'VariableName' ||
    node.name === 'VariableDefinition' ||
    node.name === 'PropertyDefinition'
  ) {
    return environment.get(nodeText(node, source));
  }
  if (node.name === 'ParenthesizedExpression') {
    const inner = significantChildren(node)[0];
    return inner ? inferExpressionType(inner, source, environment) : undefined;
  }
  if (node.name === 'BinaryExpression') {
    const children = childNodes(node);
    const operatorNode = children.find(
      (child) =>
        child.name === 'ArithOp' ||
        child.name === 'CompareOp' ||
        child.name === 'LogicOp',
    );
    const expressionNodes = children.filter(
      (child) =>
        !PUNCTUATION_NODE_NAMES.has(child.name) && child !== operatorNode,
    );
    const left = expressionNodes[0]
      ? inferExpressionType(expressionNodes[0], source, environment)
      : undefined;
    const right = expressionNodes[1]
      ? inferExpressionType(expressionNodes[1], source, environment)
      : undefined;
    const operator = operatorNode ? nodeText(operatorNode, source).trim() : '';
    if (
      operatorNode?.name === 'CompareOp' ||
      ['==', '!=', '<', '<=', '>', '>=', 'in', 'is'].includes(operator)
    ) {
      return 'Boolean';
    }
    if (operator === '+' && (left === 'String' || right === 'String')) {
      return 'String';
    }
    if (
      left === 'Number' &&
      right === 'Number' &&
      ['+', '-', '*', '/', '//', '%', '**'].includes(operator)
    ) {
      return 'Number';
    }
    return undefined;
  }
  if (node.name === 'BooleanExpression' || node.name === 'CompareExpression') {
    return 'Boolean';
  }
  if (node.name === 'UnaryExpression') {
    const raw = nodeText(node, source).trim();
    if (raw.startsWith('!') || raw.startsWith('not ')) {
      return 'Boolean';
    }
    const child = significantChildren(node).at(-1);
    return child && inferExpressionType(child, source, environment) === 'Number'
      ? 'Number'
      : undefined;
  }
  if (node.name === 'CallExpression') {
    const callee = childNodes(node)[0];
    const name = callee ? nodeText(callee, source) : '';
    if (['String', 'str'].includes(name)) {
      return 'String';
    }
    if (['Number', 'float', 'int'].includes(name)) {
      return 'Number';
    }
    if (['Boolean', 'bool'].includes(name)) {
      return 'Boolean';
    }
    if (['Array', 'list', 'set', 'tuple'].includes(name)) {
      return 'Array';
    }
    if (['Object', 'dict'].includes(name)) {
      return 'Object';
    }
  }
  return undefined;
}

function collectAssignmentNodes(body: SyntaxNode) {
  const result: SyntaxNode[] = [];
  for (const node of childNodes(body)) {
    if (
      node.name === 'AssignStatement' ||
      node.name === 'VariableDeclaration'
    ) {
      result.push(node);
      continue;
    }
    if (node.name === 'ExpressionStatement') {
      const assignment = significantChildren(node).find(
        (child) => child.name === 'AssignmentExpression',
      );
      if (assignment) {
        result.push(assignment);
      }
    }
  }
  return result.sort((left, right) => left.from - right.from);
}

function hasComplexControlFlow(body: SyntaxNode) {
  function visit(node: SyntaxNode): boolean {
    if (
      node !== body &&
      (node.name === 'FunctionDefinition' ||
        node.name === 'FunctionDeclaration')
    ) {
      return false;
    }
    if (COMPLEX_CONTROL_FLOW_NODE_NAMES.has(node.name)) {
      return true;
    }
    return childNodes(node).some(visit);
  }

  return visit(body);
}

function applyAssignmentType(
  node: SyntaxNode,
  source: string,
  environment: Map<string, CodeNodeDataType>,
) {
  const children = childNodes(node);
  const definitionNodes = children.filter(
    (child) =>
      child.name === 'VariableDefinition' || child.name === 'VariableName',
  );
  if (definitionNodes.length !== 1) {
    return;
  }
  const equalsIndex = children.findIndex(
    (child) => child.name === 'Equals' || child.name === 'AssignOp',
  );
  if (equalsIndex < 0) {
    return;
  }
  const valueNode = children
    .slice(equalsIndex + 1)
    .find((child) => !PUNCTUATION_NODE_NAMES.has(child.name));
  if (!valueNode) {
    return;
  }
  const inferred = inferExpressionType(valueNode, source, environment);
  const name = nodeText(definitionNodes[0], source);
  if (inferred) {
    environment.set(name, inferred);
  } else {
    environment.delete(name);
  }
}

/**
 * 分析 main 静态返回对象，并在受限范围内推断输出类型。
 */
export function analyzeCodeNodeOutputs(
  source: string,
  rawEngine: string | undefined,
  parameters: Parameter[],
) {
  const document = parseDocument(source, rawEngine);
  const mainFunction = document && findMainFunction(document);
  if (!document || !mainFunction) {
    return null;
  }
  const returnObjects = collectReturnObjects(document, mainFunction);
  if (returnObjects.length === 0) {
    return null;
  }

  const environment = new Map<string, CodeNodeDataType>();
  for (const parameter of parameters) {
    const name = String(parameter.name || '').trim();
    const dataType = normalizeDataType(parameter.dataType);
    if (name && dataType) {
      environment.set(name, dataType);
    }
  }
  if (!hasComplexControlFlow(mainFunction.body)) {
    for (const assignment of collectAssignmentNodes(mainFunction.body)) {
      applyAssignmentType(assignment, source, environment);
    }
  }

  const inferredByName = new Map<
    string,
    { conflicted: boolean; dataType?: CodeNodeDataType }
  >();
  for (const returnObject of returnObjects) {
    for (const entry of returnObject.entries) {
      const inferred = inferExpressionType(
        entry.valueNode,
        source,
        environment,
      );
      const current = inferredByName.get(entry.name);
      if (!current) {
        inferredByName.set(entry.name, {
          dataType: inferred,
          conflicted: false,
        });
      } else if (
        !current.dataType ||
        !inferred ||
        current.dataType !== inferred
      ) {
        current.conflicted = true;
        current.dataType = undefined;
      }
    }
  }

  return [...inferredByName.entries()].map(([name, inferred]) => ({
    name,
    dataType:
      !inferred.conflicted && inferred.dataType ? inferred.dataType : 'String',
  })) satisfies InferredCodeNodeOutput[];
}

/**
 * 将代码分析结果合并到输出参数，仅自动维护仍由代码推断管理的项。
 */
export function reconcileInferredCodeNodeOutputs(
  current: Parameter[],
  inferred: InferredCodeNodeOutput[],
  createId: () => string,
) {
  const inferredByName = new Map(
    inferred.map((output) => [output.name, output]),
  );
  const existingNames = new Set<string>();
  const result: Parameter[] = [];

  for (const parameter of current) {
    const name = String(parameter.name || '').trim();
    const inferredOutput = inferredByName.get(name);
    if (
      parameter.codeInferred === true &&
      parameter.codeInferredDataType &&
      parameter.dataType !== parameter.codeInferredDataType
    ) {
      const {
        codeInferred: _codeInferred,
        codeInferredDataType: _codeInferredDataType,
        ...manualParameter
      } = parameter;
      result.push(manualParameter);
      if (name) {
        existingNames.add(name);
      }
      continue;
    }
    if (parameter.codeInferred === true && !inferredOutput) {
      continue;
    }
    if (parameter.codeInferred === true && inferredOutput) {
      result.push({
        ...parameter,
        dataType: inferredOutput.dataType,
        codeInferredDataType: inferredOutput.dataType,
        codeSyncName: name,
      });
    } else {
      result.push({ ...parameter });
    }
    if (name) {
      existingNames.add(name);
    }
  }

  for (const output of inferred) {
    if (existingNames.has(output.name)) {
      continue;
    }
    result.push({
      id: createId(),
      name: output.name,
      dataType: output.dataType,
      codeInferred: true,
      codeInferredDataType: output.dataType,
      codeSyncName: output.name,
    });
    existingNames.add(output.name);
  }
  return result;
}

function closeLineIndent(source: string, closeBraceFrom: number) {
  const lineStart = source.lastIndexOf('\n', closeBraceFrom - 1) + 1;
  const prefix = source.slice(lineStart, closeBraceFrom);
  return /^\s*$/.test(prefix)
    ? {
        indent: prefix,
        lineStart,
      }
    : null;
}

function buildOutputInsertEdits(
  returnObject: ReturnObject,
  names: string[],
  document: ParsedDocument,
) {
  if (names.length === 0) {
    return [] as SourceEdit[];
  }
  const defaultValue = document.engine === 'python' ? 'None' : 'null';
  const indentUnit = document.engine === 'python' ? '    ' : '  ';
  const lineIndent = closeLineIndent(
    document.source,
    returnObject.closeBraceFrom,
  );
  const edits: SourceEdit[] = [];

  if (lineIndent) {
    const entryIndent = lineIndent.indent + indentUnit;
    const lines = names.map(
      (name) => `${entryIndent}${JSON.stringify(name)}: ${defaultValue},`,
    );
    if (returnObject.entries.length > 0) {
      const lastEntry = returnObject.entries[returnObject.entries.length - 1];
      const afterLastEntry = document.source.slice(
        lastEntry.propertyTo,
        returnObject.closeBraceFrom,
      );
      if (!afterLastEntry.includes(',')) {
        edits.push({
          from: lastEntry.propertyTo,
          to: lastEntry.propertyTo,
          insert: ',',
        });
      }
    }
    edits.push({
      from: lineIndent.lineStart,
      to: returnObject.closeBraceFrom,
      insert: `${lines.join('\n')}\n${lineIndent.indent}`,
    });
    return edits;
  }

  const prefix = returnObject.entries.length > 0 ? ', ' : '';
  edits.push({
    from: returnObject.closeBraceFrom,
    to: returnObject.closeBraceFrom,
    insert: `${prefix}${names
      .map((name) => `${JSON.stringify(name)}: ${defaultValue}`)
      .join(', ')}`,
  });
  return edits;
}

/**
 * 将输出参数新增和改名安全同步到 main 的静态返回对象。
 */
export function syncCodeNodeOutputDefinitions(
  source: string,
  rawEngine: string | undefined,
  previous: Parameter[],
  next: Parameter[],
) {
  const nextWithSyncNames = withPreviousSyncNames(previous, next).map(
    (parameter) => {
      const oldParameter = previous.find(
        (candidate) => candidate.id && candidate.id === parameter.id,
      );
      if (
        oldParameter?.codeInferred === true &&
        oldParameter.dataType !== parameter.dataType
      ) {
        const {
          codeInferred: _codeInferred,
          codeInferredDataType: _codeInferredDataType,
          ...manualParameter
        } = parameter;
        return manualParameter;
      }
      return parameter;
    },
  );
  const nextNames = nextWithSyncNames.map((parameter) =>
    String(parameter.name || '').trim(),
  );
  if (
    nextNames.some((name) => !name) ||
    new Set(nextNames).size !== nextNames.length
  ) {
    return {
      code: source,
      outputDefs: nextWithSyncNames,
      reason: '输出参数名称为空或重复',
      synced: false,
    };
  }

  const document = parseDocument(source, rawEngine);
  const mainFunction = document && findMainFunction(document);
  if (!document || !mainFunction) {
    return {
      code: source,
      outputDefs: nextWithSyncNames,
      reason: '未找到可安全同步的 main 函数',
      synced: false,
    };
  }
  const returnObjects = collectReturnObjects(document, mainFunction);
  if (returnObjects.length === 0) {
    return {
      code: source,
      outputDefs: nextWithSyncNames,
      reason: 'main 中没有静态对象返回值',
      synced: false,
    };
  }

  const previousById = new Map(
    previous
      .filter((parameter) => parameter.id)
      .map((parameter) => [parameter.id, parameter]),
  );
  const renames: Array<{ from: string; to: string }> = [];
  const additions: string[] = [];
  for (const parameter of nextWithSyncNames) {
    const previousParameter = parameter.id
      ? previousById.get(parameter.id)
      : undefined;
    const oldName = String(
      previousParameter?.codeSyncName || previousParameter?.name || '',
    ).trim();
    const newName = String(parameter.name || '').trim();
    if (oldName && oldName !== newName) {
      renames.push({
        from: oldName,
        to: newName,
      });
    } else if (!oldName && newName) {
      additions.push(newName);
    }
  }

  const edits: SourceEdit[] = [];
  for (const returnObject of returnObjects) {
    const namesAfterRename = new Set(
      returnObject.entries.map((entry) => {
        const rename = renames.find(
          (candidate) => candidate.from === entry.name,
        );
        return rename?.to || entry.name;
      }),
    );
    for (const rename of renames) {
      if (
        rename.from !== rename.to &&
        namesAfterRename.has(rename.to) &&
        returnObject.entries.some(
          (entry) => entry.name === rename.to && entry.name !== rename.from,
        )
      ) {
        return {
          code: source,
          outputDefs: nextWithSyncNames,
          reason: `return 中已存在输出字段 ${rename.to}`,
          synced: false,
        };
      }
      const entry = returnObject.entries.find(
        (candidate) => candidate.name === rename.from,
      );
      if (!entry) {
        continue;
      }
      if (entry.shorthand) {
        edits.push({
          from: entry.propertyFrom,
          to: entry.propertyTo,
          insert: `${JSON.stringify(rename.to)}: ${rename.from}`,
        });
      } else {
        edits.push({
          from: entry.keyFrom,
          to: entry.keyTo,
          insert: JSON.stringify(rename.to),
        });
      }
    }
    const missingRenameTargets = renames
      .filter(
        (rename) =>
          !returnObject.entries.some((entry) => entry.name === rename.from),
      )
      .map((rename) => rename.to);
    const missing = [...additions, ...missingRenameTargets].filter(
      (name, index, names) =>
        !namesAfterRename.has(name) && names.indexOf(name) === index,
    );
    edits.push(...buildOutputInsertEdits(returnObject, missing, document));
  }

  const code = applySourceEdits(source, edits);
  return {
    code,
    outputDefs: nextWithSyncNames.map((parameter) => ({
      ...parameter,
      codeSyncName: String(parameter.name || '').trim(),
    })),
    synced: true,
  };
}
