import { DataSource, ResultData, PropFunction } from "../type";

import { GraphNodes } from "../node";

import { parseNodeName, isNamedInputs, isObject, isNull } from "./utils";
import { getDataFromSource } from "./data_source";
import { utilsFunctions } from "./prop_function";

const replaceTemplatePlaceholders = (input: string, templateMatch: string[], nodes: GraphNodes, propFunctions: PropFunction[], isSelfNode: boolean) => {
  // GOD format ${:node.prop1.prop2}
  const godResults = resultsOfInner(
    templateMatch.filter((text) => text.startsWith(":")),
    nodes,
    propFunctions,
    isSelfNode,
  );
  // utilsFunctions ${@now}
  const utilsFuncResult = templateMatch
    .filter((text) => text.startsWith("@"))
    .reduce((tmp: Record<string, string | number>, key: string) => {
      tmp[key] = utilsFunctions(key, nodes);
      return tmp;
    }, {});
  return Array.from(templateMatch.keys()).reduce((tmp, key) => {
    if (templateMatch[key].startsWith(":")) {
      return tmp.replaceAll("${" + templateMatch[key] + "}", (godResults as any)[key]);
    }
    return tmp.replaceAll("${" + templateMatch[key] + "}", (utilsFuncResult as any)[templateMatch[key]]);
  }, input);
};

const resultsOfInner = (input: any, nodes: GraphNodes, propFunctions: PropFunction[], isSelfNode: boolean = false): ResultData => {
  if (Array.isArray(input)) {
    return input.map((inp) => resultsOfInner(inp, nodes, propFunctions, isSelfNode));
  }
  if (isNamedInputs(input)) {
    return resultsOf(input, nodes, propFunctions, isSelfNode);
  }
  if (typeof input === "string") {
    const templateMatch = [...input.matchAll(/\${([:@][^}]+)}/g)].map((m) => m[1]);
    if (templateMatch.length > 0) {
      return replaceTemplatePlaceholders(input, templateMatch, nodes, propFunctions, isSelfNode);
    }
  }
  // :node.prod
  return resultOf(parseNodeName(input, isSelfNode, nodes), nodes, propFunctions);
};

export const resultsOf = (
  inputs: Record<string, any>,
  nodes: GraphNodes,
  propFunctions: PropFunction[],
  isSelfNode: boolean = false,
): Record<string, ResultData> => {
  return Object.keys(inputs).reduce((tmp: Record<string, ResultData>, key) => {
    const input = inputs[key];
    tmp[key] = isNamedInputs(input) ? resultsOf(input, nodes, propFunctions, isSelfNode) : resultsOfInner(input, nodes, propFunctions, isSelfNode);
    return tmp;
  }, {});
};

export const resultOf = (source: DataSource, nodes: GraphNodes, propFunctions: PropFunction[]) => {
  const { result } = source.nodeId ? nodes[source.nodeId] : { result: undefined };
  return getDataFromSource(result, source, propFunctions);
};

// clean up object for anyInput
export const cleanResultInner = (results: ResultData): ResultData | null => {
  if (Array.isArray(results)) {
    return results.map((result: ResultData) => cleanResultInner(result)).filter((result) => !isNull(result));
  }

  if (isObject<ResultData>(results)) {
    return Object.keys(results).reduce((tmp: Record<string, ResultData>, key: string) => {
      const value = cleanResultInner(results[key]);
      if (!isNull(value)) {
        tmp[key] = value;
      }
      return tmp;
    }, {});
  }

  return results;
};

export const cleanResult = (results: Record<string, ResultData | undefined>) => {
  return Object.keys(results).reduce((tmp: Record<string, ResultData | undefined>, key: string) => {
    const value = cleanResultInner(results[key]);
    if (!isNull(value)) {
      tmp[key] = value;
    }
    return tmp;
  }, {});
};
