import { unreachable } from "../../shared/src/asserts.js";
import { must } from "../../shared/src/must.js";
import { SUBQ_PREFIX } from "../../zero-protocol/src/ast.js";
function astToZQL(ast) {
  let code = "";
  if (ast.where) {
    code += transformCondition(ast.where, ".where", /* @__PURE__ */ new Set());
  }
  if (ast.related && ast.related.length > 0) {
    for (const related of ast.related) {
      if (related.hidden) {
        const nestedRelated = related.subquery.related?.[0];
        if (nestedRelated) {
          code += transformRelated(nestedRelated);
        }
      } else {
        code += transformRelated(related);
      }
    }
  }
  if (ast.orderBy && ast.orderBy.length > 0) {
    code += transformOrder(ast.orderBy);
  }
  if (ast.limit !== void 0) {
    code += `.limit(${ast.limit})`;
  }
  if (ast.start) {
    const { row, exclusive } = ast.start;
    code += `.start(${JSON.stringify(row)}${exclusive ? "" : ", { inclusive: true }"})`;
  }
  return code;
}
function transformCondition(condition, prefix, args) {
  switch (condition.type) {
    case "simple":
      return transformSimpleCondition(condition, prefix);
    case "and":
    case "or":
      return transformLogicalCondition(condition, prefix, args);
    case "correlatedSubquery":
      return transformExistsCondition(condition, prefix, args);
    default:
      unreachable();
  }
}
function transformSimpleCondition(condition, prefix) {
  const { left, op, right } = condition;
  const leftCode = transformValuePosition(left);
  const rightCode = transformValuePosition(right);
  if (op === "=") {
    return `${prefix}(${leftCode}, ${rightCode})`;
  }
  return `${prefix}(${leftCode}, '${op}', ${rightCode})`;
}
function transformLogicalCondition(condition, prefix, args) {
  const { type, conditions } = condition;
  if (conditions.length === 1) {
    return transformCondition(conditions[0], prefix, args);
  }
  if (type === "and") {
    const parts = conditions.map((c) => transformCondition(c, prefix, args));
    if (prefix === ".where") {
      return parts.join("");
    }
    args.add("and");
    return "and(" + parts.join(", ") + ")";
  }
  args = /* @__PURE__ */ new Set();
  const conditionsCode = conditions.map((c) => transformCondition(c, "cmp", args)).join(", ");
  args.add("cmp");
  args.add(type);
  const argsCode = [...args].sort().join(", ");
  return `.where(({${argsCode}}) => ${type}(${conditionsCode}))`;
}
function transformExistsCondition(condition, prefix, args) {
  const { related, op } = condition;
  const relationship = extractRelationshipName(related);
  const nextSubquery = getNextExistsSubquery(related);
  const hasSubQueryProps = nextSubquery.where || nextSubquery.related && nextSubquery.related.length > 0 || nextSubquery.orderBy || nextSubquery.limit;
  const optionParts = [];
  if (condition.flip !== void 0) {
    optionParts.push(`flip: ${condition.flip}`);
  }
  if (condition.scalar !== void 0) {
    optionParts.push(`scalar: ${condition.scalar}`);
  }
  const optionsStr = optionParts.length > 0 ? `, {${optionParts.join(", ")}}` : "";
  if (op === "EXISTS") {
    if (!hasSubQueryProps) {
      if (prefix === ".where") {
        return `.whereExists('${relationship}'${optionsStr})`;
      }
      args.add("exists");
      return `exists('${relationship}'${optionsStr})`;
    }
    if (prefix === ".where") {
      return `.whereExists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})`;
    }
    args.add("exists");
    return `exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr})`;
  }
  if (hasSubQueryProps) {
    if (prefix === ".where") {
      return `.where(({exists, not}) => not(exists('${relationship}', q => q${astToZQL(
        nextSubquery
      )}${optionsStr})))`;
    }
    args.add("not");
    args.add("exists");
    return `not(exists('${relationship}', q => q${astToZQL(nextSubquery)}${optionsStr}))`;
  }
  if (prefix === ".where") {
    return `.where(({exists, not}) => not(exists('${relationship}'${optionsStr})))`;
  }
  args.add("not");
  args.add("exists");
  return `not(exists('${relationship}'${optionsStr})))`;
}
function getNextExistsSubquery(related) {
  if (related.subquery.where?.type === "correlatedSubquery" && related.subquery.where.related.subquery.alias?.includes(
    SUBQ_PREFIX + "zhidden_"
  )) {
    return getNextExistsSubquery(related.subquery.where.related);
  }
  return related.subquery;
}
function extractRelationshipName(related) {
  const alias = must(related.subquery.alias);
  return alias.startsWith(SUBQ_PREFIX) ? alias.substring(SUBQ_PREFIX.length) : alias;
}
function transformRelated(related) {
  const { alias } = related.subquery;
  if (!alias) return "";
  const relationship = alias;
  let code = `.related('${relationship}'`;
  if (related.subquery.where || related.subquery.related && related.subquery.related.length > 0 || related.subquery.orderBy || related.subquery.limit) {
    code += ", q => q" + astToZQL(related.subquery);
  }
  code += ")";
  return code;
}
function transformOrder(orderBy) {
  let code = "";
  for (const [field, direction] of orderBy) {
    code += `.orderBy('${field}', '${direction}')`;
  }
  return code;
}
function transformValuePosition(value) {
  switch (value.type) {
    case "literal":
      return transformLiteral(value);
    case "column":
      return `'${value.name}'`;
    case "static":
      return transformParameter(value);
    default:
      unreachable();
  }
}
function transformLiteral(literal) {
  if (literal.value === null) {
    return "null";
  }
  if (Array.isArray(literal.value)) {
    return JSON.stringify(literal.value);
  }
  if (typeof literal.value === "string") {
    return `'${literal.value.replace(/'/g, "\\'")}'`;
  }
  return String(literal.value);
}
function transformParameter(param) {
  const fieldStr = Array.isArray(param.field) ? `[${param.field.map((f) => `'${f}'`).join(", ")}]` : `'${param.field}'`;
  return `authParam(${fieldStr})`;
}
export {
  astToZQL
};
//# sourceMappingURL=ast-to-zql.js.map
