import { isValuelessOperator } from "./advanced-filter.js";
import { buildIlikeTerm } from "../db/buildIlikeTerm.js";
const TREE_LIMITS = {
  maxGroupLevel: 3,
  // root = level 1; deepest nested group = level 3
  maxChildrenPerGroup: 15,
  // counts both rules and nested groups
  maxTotalRules: 50
};
function createEmptyTree() {
  return {
    root: {
      id: crypto.randomUUID(),
      type: "group",
      combinator: "and",
      children: []
    }
  };
}
function validateTreeLimits(tree) {
  let totalRules = 0;
  function walk(node, level) {
    if (node.type === "rule") {
      totalRules += 1;
      return { ok: true };
    }
    if (level > TREE_LIMITS.maxGroupLevel) return { ok: false, reason: "depth" };
    if (node.children.length > TREE_LIMITS.maxChildrenPerGroup) return { ok: false, reason: "width" };
    for (const child of node.children) {
      const r2 = walk(child, level + 1);
      if (!r2.ok) return r2;
    }
    return { ok: true };
  }
  const r = walk(tree.root, 1);
  if (!r.ok) return r;
  if (totalRules > TREE_LIMITS.maxTotalRules) return { ok: false, reason: "total" };
  return { ok: true };
}
function normalizeSingleValue(value) {
  if (typeof value !== "string") return value;
  const trimmed = value.trim();
  return trimmed.length > 0 ? trimmed : null;
}
function normalizeListValue(value) {
  const list = Array.isArray(value) ? value : [value];
  return list.map(normalizeSingleValue).filter((v) => v !== null);
}
function compileRule(rule) {
  if (!rule.field || !rule.operator) return null;
  const filter = {};
  switch (rule.operator) {
    case "is":
    case "equals": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $eq: v };
      break;
    }
    case "is_not":
    case "not_equals": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $ne: v };
      break;
    }
    case "contains": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $ilike: buildIlikeTerm(String(v)) };
      break;
    }
    case "does_not_contain": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $not: { $ilike: buildIlikeTerm(String(v)) } };
      break;
    }
    case "starts_with": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $ilike: buildIlikeTerm(String(v), "startsWith") };
      break;
    }
    case "ends_with": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $ilike: buildIlikeTerm(String(v), "endsWith") };
      break;
    }
    case "is_empty":
      filter[rule.field] = { $exists: false };
      break;
    case "is_not_empty":
      filter[rule.field] = { $exists: true };
      break;
    case "greater_than": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $gt: v };
      break;
    }
    case "less_than": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $lt: v };
      break;
    }
    case "greater_or_equal": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $gte: v };
      break;
    }
    case "less_or_equal": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $lte: v };
      break;
    }
    case "between": {
      if (Array.isArray(rule.value) && rule.value.length === 2) {
        const start = normalizeSingleValue(rule.value[0]);
        const end = normalizeSingleValue(rule.value[1]);
        if (start === null && end === null) return null;
        if (start !== null && end !== null) filter[rule.field] = { $gte: start, $lte: end };
        else if (start !== null) filter[rule.field] = { $gte: start };
        else filter[rule.field] = { $lte: end };
      } else {
        return null;
      }
      break;
    }
    case "is_before": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $lt: v };
      break;
    }
    case "is_after": {
      const v = normalizeSingleValue(rule.value);
      if (v === null) return null;
      filter[rule.field] = { $gt: v };
      break;
    }
    case "is_any_of":
    case "has_any_of": {
      const list = normalizeListValue(rule.value);
      if (list.length === 0) return null;
      filter[rule.field] = { $in: list };
      break;
    }
    case "is_none_of":
    case "has_none_of": {
      const list = normalizeListValue(rule.value);
      if (list.length === 0) return null;
      filter[rule.field] = { $nin: list };
      break;
    }
    case "has_all_of": {
      const list = normalizeListValue(rule.value);
      if (list.length === 0) return null;
      filter[rule.field] = { $contains: list };
      break;
    }
    case "is_true":
      filter[rule.field] = { $eq: true };
      break;
    case "is_false":
      filter[rule.field] = { $eq: false };
      break;
  }
  return Object.keys(filter).length > 0 ? filter : null;
}
function compileTreeToWhere(tree) {
  return compileGroup(tree.root);
}
function compileGroup(node) {
  const compiled = node.children.map((child) => child.type === "rule" ? compileRule(child) : compileGroup(child)).filter((c) => c !== null);
  if (compiled.length === 0) return null;
  if (compiled.length === 1) return compiled[0];
  return node.combinator === "or" ? { $or: compiled } : { $and: compiled };
}
function makeRuleTree(rule) {
  const ruleNode = {
    id: crypto.randomUUID(),
    type: "rule",
    field: rule.field,
    operator: rule.operator,
    value: rule.value
  };
  return {
    root: {
      id: crypto.randomUUID(),
      type: "group",
      combinator: "and",
      children: [ruleNode]
    }
  };
}
function stripRuntimeMetadataDeep(node) {
  if (node.type === "rule") {
    const { addedAt: _ignored2, ...rest2 } = node;
    return rest2;
  }
  const { addedAt: _ignored, children, ...rest } = node;
  const cleanChildren = children.map((c) => stripRuntimeMetadataDeep(c));
  return { ...rest, children: cleanChildren };
}
function serializeTreeForPersist(tree) {
  return { v: 2, root: stripRuntimeMetadataDeep(tree.root) };
}
function isPersistedFilterTree(value) {
  if (!value || typeof value !== "object") return false;
  const record = value;
  if (record.v !== 2) return false;
  const root = record.root;
  if (!root || typeof root !== "object") return false;
  const r = root;
  return r.type === "group" && (r.combinator === "and" || r.combinator === "or") && Array.isArray(r.children);
}
function deserializeTreeFromPersist(value) {
  if (!isPersistedFilterTree(value)) return null;
  return { root: value.root };
}
function treeToFlat(tree) {
  const conditions = [];
  function walk(group, parentJoin) {
    for (const child of group.children) {
      if (child.type === "rule") {
        conditions.push({
          id: child.id,
          field: child.field,
          operator: child.operator,
          value: child.value,
          // First rule overall gets 'and' (it has no left neighbor); the rest
          // inherit the parent group's combinator.
          join: conditions.length === 0 ? "and" : parentJoin
        });
      } else {
        walk(child, child.combinator);
      }
    }
  }
  walk(tree.root, tree.root.combinator);
  return { logic: tree.root.combinator, conditions };
}
function makeMultiRuleTree(rules, combinator = "and") {
  return {
    root: {
      id: crypto.randomUUID(),
      type: "group",
      combinator,
      children: rules.map((rule) => {
        const node = {
          id: crypto.randomUUID(),
          type: "rule",
          field: rule.field,
          operator: rule.operator,
          value: rule.value
        };
        return node;
      })
    }
  };
}
export {
  TREE_LIMITS,
  compileTreeToWhere,
  createEmptyTree,
  deserializeTreeFromPersist,
  isPersistedFilterTree,
  isValuelessOperator,
  makeMultiRuleTree,
  makeRuleTree,
  serializeTreeForPersist,
  treeToFlat,
  validateTreeLimits
};
//# sourceMappingURL=advanced-filter-tree.js.map
