import type { Node } from '@xyflow/svelte';

import type { Parameter } from '../types';

export type LoopInputs = {
  count?: Parameter;
  items?: Parameter;
  legacy: boolean;
};

function asString(value: unknown) {
  return value == null ? '' : String(value).trim();
}

function cloneParameter(parameter: Parameter): Parameter {
  return {
    ...parameter,
    children: parameter.children?.map(cloneParameter),
  };
}

export function isArrayDataType(dataType?: string | null) {
  const normalized = asString(dataType).toLowerCase();
  return normalized === 'array' || normalized.startsWith('array<');
}

function normalizeDataType(
  dataType?: string | null,
  hasChildren = false,
) {
  const normalized = asString(dataType);
  if (!normalized) {
    return hasChildren ? 'Object' : 'String';
  }
  if (normalized.toLowerCase() === 'array') {
    return `Array<${hasChildren ? 'Object' : 'Any'}>`;
  }
  return normalized;
}

/**
 * 计算参数穿过上级集合后的实际引用类型。
 */
export function projectParameterDataType(
  parameter: Parameter,
  ancestorCollectionDepth = 0,
) {
  const dataType = normalizeDataType(
    parameter.dataType,
    Boolean(parameter.children?.length),
  );
  if (ancestorCollectionDepth <= 0) {
    return dataType;
  }
  const prefix = 'Array<'.repeat(ancestorCollectionDepth);
  const suffix = '>'.repeat(ancestorCollectionDepth);
  return `${prefix}${dataType}${suffix}`;
}

/**
 * 计算循环完成后对外暴露的输出类型。
 */
export function resolveLoopOutputDataType(
  sourceDataType?: string | null,
  flattenAggregation = false,
) {
  const normalized = normalizeDataType(sourceDataType);
  if (flattenAggregation && isArrayDataType(normalized)) {
    return normalized;
  }
  return `Array<${normalized}>`;
}

function getNodeParameters(node: Node): Parameter[] {
  if (node.type === 'startNode') {
    return Array.isArray(node.data?.parameters)
      ? (node.data.parameters as Parameter[])
      : [];
  }
  return Array.isArray(node.data?.outputDefs)
    ? (node.data.outputDefs as Parameter[])
    : [];
}

export function findReferenceParameter(
  nodes: Node[],
  reference?: string | null,
): Parameter | undefined {
  const normalizedReference = asString(reference);
  if (!normalizedReference) {
    return undefined;
  }
  const node = nodes.find((candidate) =>
    normalizedReference.startsWith(`${candidate.id}.`),
  );
  if (!node) {
    return undefined;
  }
  const path = normalizedReference.slice(node.id.length + 1).split('.');
  let parameters = getNodeParameters(node);
  let current: Parameter | undefined;
  let ancestorCollectionDepth = 0;
  for (let index = 0; index < path.length; index++) {
    const segment = path[index];
    current = parameters.find(
      (parameter) => asString(parameter.name) === segment,
    );
    if (!current) {
      return undefined;
    }
    if (
      index < path.length - 1 &&
      isArrayDataType(current.dataType)
    ) {
      ancestorCollectionDepth++;
    }
    parameters = current.children || [];
  }
  if (!current) {
    return undefined;
  }
  const projected = cloneParameter(current);
  projected.dataType = projectParameterDataType(
    current,
    ancestorCollectionDepth,
  );
  return projected;
}

export function resolveLoopInputs(
  loopNode: Node,
  nodes: Node[],
): LoopInputs {
  const data = (loopNode.data || {}) as Record<string, any>;
  if (data.loopInputs && typeof data.loopInputs === 'object') {
    return {
      count: data.loopInputs.count
        ? cloneParameter(data.loopInputs.count as Parameter)
        : undefined,
      items: data.loopInputs.items
        ? cloneParameter(data.loopInputs.items as Parameter)
        : undefined,
      legacy: false,
    };
  }

  const legacyParameter = Array.isArray(data.loopVars)
    ? (data.loopVars[0] as Parameter | undefined)
    : undefined;
  if (!legacyParameter) {
    return { legacy: false };
  }
  const referencedParameter = findReferenceParameter(
    nodes,
    legacyParameter.ref,
  );
  const legacyIsArray =
    isArrayDataType(legacyParameter.dataType) ||
    isArrayDataType(referencedParameter?.dataType);
  return legacyIsArray
    ? {
        items: {
          ...cloneParameter(legacyParameter),
          name: 'items',
          dataType:
            referencedParameter?.dataType ||
            legacyParameter.dataType ||
            'Array',
        },
        legacy: true,
      }
    : {
        count: {
          ...cloneParameter(legacyParameter),
          name: 'count',
          dataType: 'Number',
        },
        legacy: true,
      };
}

function getArrayItemDataType(dataType?: string | null) {
  const normalized = asString(dataType);
  const genericMatch = normalized.match(/^Array<(.+)>$/i);
  return genericMatch?.[1]?.trim() || '';
}

export function buildLoopItemParameter(
  loopNode: Node,
  nodes: Node[],
): Parameter | undefined {
  const inputs = resolveLoopInputs(loopNode, nodes);
  const items = inputs.items;
  if (!items) {
    return inputs.count
      ? {
          name: 'loopItem',
          displayName: 'loopItem',
          dataType: 'Number',
          nameDisabled: true,
          dataTypeDisabled: true,
          deleteDisabled: true,
        }
      : undefined;
  }
  const referencedParameter =
    findReferenceParameter(nodes, items.ref) || items;
  const children = referencedParameter.children?.map(cloneParameter);
  return {
    name: 'loopItem',
    displayName: 'loopItem',
    dataType:
      getArrayItemDataType(referencedParameter.dataType) ||
      (children?.length ? 'Object' : 'Any'),
    children,
    nameDisabled: true,
    dataTypeDisabled: true,
    deleteDisabled: true,
  };
}

export function isNodeWithinLoopScope(
  currentNode: Node,
  loopNodeId: string,
  nodes: Node[],
) {
  const nodeMap = new Map(nodes.map((node) => [node.id, node]));
  const visited = new Set<string>();
  let current: Node | undefined = currentNode;
  while (current?.parentId && !visited.has(current.id)) {
    visited.add(current.id);
    if (current.parentId === loopNodeId) {
      return true;
    }
    current = nodeMap.get(current.parentId);
  }
  return false;
}

export function buildLoopScopeParameters(
  loopNode: Node,
  currentNode: Node,
  nodes: Node[],
): Parameter[] {
  if (!isNodeWithinLoopScope(currentNode, loopNode.id, nodes)) {
    return [];
  }
  const parameters: Parameter[] = [
    {
      name: 'index',
      displayName: 'index',
      dataType: 'Number',
      nameDisabled: true,
      dataTypeDisabled: true,
      deleteDisabled: true,
    },
  ];
  const loopItem = buildLoopItemParameter(loopNode, nodes);
  if (loopItem) {
    parameters.push(loopItem);
  }
  return parameters;
}

export function buildLoopReferenceParameters(
  loopNode: Node,
  currentNode: Node,
  nodes: Node[],
): Parameter[] {
  const scopeParameters = buildLoopScopeParameters(
    loopNode,
    currentNode,
    nodes,
  );
  if (scopeParameters.length) {
    return scopeParameters;
  }
  return Array.isArray(loopNode.data?.outputDefs)
    ? (loopNode.data.outputDefs as Parameter[]).map((parameter) => {
        const outputParameter = cloneParameter(parameter);
        const sourceParameter = findReferenceParameter(
          nodes,
          outputParameter.ref,
        );
        const sourceDataType =
          asString(
            sourceParameter?.dataType || outputParameter.dataType,
          ) || 'String';
        const sourceChildren = (
          sourceParameter?.children || outputParameter.children
        )?.map(cloneParameter);
        return {
          ...outputParameter,
          dataType: resolveLoopOutputDataType(
            sourceDataType,
            Boolean(outputParameter.flattenAggregation),
          ),
          children: sourceChildren,
        };
      })
    : [];
}
