export type RuntimeVariables = Record<string, unknown>;

export function evaluateRuntimeExpression(text: string, variables: RuntimeVariables = {}): unknown {
  const normalized = normalizeExpression(text);
  if (normalized === "") {
    return undefined;
  }
  if (normalized === "true") {
    return true;
  }
  if (normalized === "false") {
    return false;
  }
  if (/^-?\d+(?:\.\d+)?$/.test(normalized)) {
    return Number(normalized);
  }
  if (Object.prototype.hasOwnProperty.call(variables, normalized)) {
    return variables[normalized];
  }

  const comparison = normalized.match(/^(.+?)\s*(==|!=|>=|<=|>|<)\s*(.+)$/);
  if (comparison) {
    const left = valueOf(comparison[1]!, variables);
    const right = valueOf(comparison[3]!, variables);
    switch (comparison[2]) {
      case "==":
        return left === right;
      case "!=":
        return left !== right;
      case ">=":
        return Number(left) >= Number(right);
      case "<=":
        return Number(left) <= Number(right);
      case ">":
        return Number(left) > Number(right);
      case "<":
        return Number(left) < Number(right);
      default:
        return false;
    }
  }

  return Boolean(variables[normalized]);
}

export function evaluateRuntimeBoolean(text: string, variables: RuntimeVariables = {}): boolean {
  return Boolean(evaluateRuntimeExpression(text, variables));
}

export function assignRuntimeExpression(text: string, variables: RuntimeVariables, value?: unknown): boolean {
  const normalized = normalizeExpression(text);
  const assignment = normalized.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*(?::=|=)\s*(.+)$/);
  if (!assignment) {
    return false;
  }
  variables[assignment[1]!] = value !== undefined ? value : valueOf(assignment[2]!, variables);
  return true;
}

function valueOf(raw: string, variables: RuntimeVariables): unknown {
  const text = normalizeExpression(raw);
  if (text === "true") {
    return true;
  }
  if (text === "false") {
    return false;
  }
  if ((text.startsWith("\"") && text.endsWith("\"")) || (text.startsWith("'") && text.endsWith("'"))) {
    return text.slice(1, -1);
  }
  if (/^-?\d+(?:\.\d+)?$/.test(text)) {
    return Number(text);
  }
  return variables[text];
}

function normalizeExpression(text: string): string {
  return text
    .replace(/\s*\.\s*/g, ".")
    .replace(/\s*\[\s*/g, "[")
    .replace(/\s*\]\s*/g, "]")
    .replace(/\s+/g, " ")
    .trim();
}
