import { buildIlikeTerm } from "../db/buildIlikeTerm.js";
const OPERATORS_BY_FIELD_TYPE = {
  text: ["is", "is_not", "contains", "does_not_contain", "starts_with", "ends_with", "is_empty", "is_not_empty"],
  number: ["equals", "not_equals", "greater_than", "less_than", "greater_or_equal", "less_or_equal", "between", "is_empty"],
  date: ["is", "is_before", "is_after", "between", "is_empty", "is_not_empty"],
  select: ["is", "is_not", "is_any_of", "is_none_of", "is_empty"],
  boolean: ["is_true", "is_false"],
  tags: ["has_any_of", "has_all_of", "has_none_of", "is_empty"]
};
function getDefaultOperator(fieldType) {
  switch (fieldType) {
    case "text":
      return "contains";
    case "number":
      return "equals";
    case "date":
      return "is_after";
    case "select":
      return "is";
    case "boolean":
      return "is_true";
    case "tags":
      return "has_any_of";
  }
}
const VALID_OPERATORS = /* @__PURE__ */ new Set([
  "is",
  "is_not",
  "contains",
  "does_not_contain",
  "starts_with",
  "ends_with",
  "is_empty",
  "is_not_empty",
  "equals",
  "not_equals",
  "greater_than",
  "less_than",
  "greater_or_equal",
  "less_or_equal",
  "between",
  "is_before",
  "is_after",
  "is_any_of",
  "is_none_of",
  "is_true",
  "is_false",
  "has_any_of",
  "has_all_of",
  "has_none_of"
]);
function isValidOperator(op) {
  return VALID_OPERATORS.has(op);
}
function isValuelessOperator(operator) {
  return operator === "is_empty" || operator === "is_not_empty" || operator === "is_true" || operator === "is_false";
}
function createEmptyCondition() {
  return {
    id: crypto.randomUUID(),
    field: "",
    operator: "contains",
    value: "",
    join: "and"
  };
}
function normalizeJoinOperator(value, fallback = "and") {
  return value === "or" ? "or" : value === "and" ? "and" : fallback;
}
function parseSerializedFilterValue(value) {
  if (typeof value !== "string") return value ?? null;
  const trimmed = value.trim();
  if (!trimmed) return value;
  if (!trimmed.startsWith("[") && !trimmed.startsWith("{")) return value;
  try {
    return JSON.parse(trimmed);
  } catch {
    return value;
  }
}
function normalizeAdvancedFilterState(state) {
  const logic = normalizeJoinOperator(state.logic);
  return {
    logic,
    conditions: state.conditions.map((condition, index) => ({
      ...condition,
      join: index === 0 ? "and" : normalizeJoinOperator(condition.join, logic)
    }))
  };
}
function serializeAdvancedFilter(state) {
  const params = {};
  const normalized = normalizeAdvancedFilterState(state);
  if (!normalized.conditions.length) return params;
  params["filter[logic]"] = normalized.logic;
  normalized.conditions.forEach((condition, index) => {
    const prefix = `filter[conditions][${index}]`;
    params[`${prefix}[field]`] = condition.field;
    params[`${prefix}[op]`] = condition.operator;
    if (index > 0) {
      params[`${prefix}[join]`] = condition.join ?? normalized.logic;
    }
    if (!isValuelessOperator(condition.operator) && condition.value != null) {
      params[`${prefix}[value]`] = typeof condition.value === "object" ? JSON.stringify(condition.value) : String(condition.value);
    }
  });
  return params;
}
function isUsableFilterField(field) {
  return field.length > 0 && !field.startsWith("$");
}
function deserializeAdvancedFilter(query) {
  const logic = normalizeJoinOperator(query["filter[logic]"]);
  const conditions = [];
  for (let i = 0; i < 20; i++) {
    const field = query[`filter[conditions][${i}][field]`];
    const op = query[`filter[conditions][${i}][op]`];
    if (typeof field !== "string" || typeof op !== "string") break;
    if (!isValidOperator(op) || !isUsableFilterField(field)) continue;
    const value = query[`filter[conditions][${i}][value]`];
    const join = i === 0 ? "and" : normalizeJoinOperator(query[`filter[conditions][${i}][join]`], logic);
    conditions.push({
      id: String(i),
      field,
      operator: op,
      value: parseSerializedFilterValue(value),
      join
    });
  }
  if (!conditions.length) return null;
  return normalizeAdvancedFilterState({ logic, conditions });
}
function buildConditionFilter(condition) {
  if (!condition.field || !condition.operator) return null;
  const normalizeSingleValue = (value) => {
    if (typeof value !== "string") return value;
    const trimmed = value.trim();
    return trimmed.length > 0 ? trimmed : null;
  };
  const normalizeListValue = (value) => {
    const list = Array.isArray(value) ? value : [value];
    return list.map((entry) => normalizeSingleValue(entry)).filter((entry) => entry !== null);
  };
  const filter = {};
  switch (condition.operator) {
    case "is":
    case "equals":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $eq: normalizeSingleValue(condition.value) };
      break;
    case "is_not":
    case "not_equals":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $ne: normalizeSingleValue(condition.value) };
      break;
    case "contains":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) };
      break;
    case "does_not_contain":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $not: { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value))) } };
      break;
    case "starts_with":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), "startsWith") };
      break;
    case "ends_with":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $ilike: buildIlikeTerm(String(normalizeSingleValue(condition.value)), "endsWith") };
      break;
    case "is_empty":
      filter[condition.field] = { $exists: false };
      break;
    case "is_not_empty":
      filter[condition.field] = { $exists: true };
      break;
    case "greater_than":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $gt: normalizeSingleValue(condition.value) };
      break;
    case "less_than":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $lt: normalizeSingleValue(condition.value) };
      break;
    case "greater_or_equal":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $gte: normalizeSingleValue(condition.value) };
      break;
    case "less_or_equal":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $lte: normalizeSingleValue(condition.value) };
      break;
    case "between":
      if (Array.isArray(condition.value) && condition.value.length === 2) {
        const start = normalizeSingleValue(condition.value[0]);
        const end = normalizeSingleValue(condition.value[1]);
        if (start === null && end === null) return null;
        if (start !== null && end !== null) {
          filter[condition.field] = { $gte: start, $lte: end };
        } else if (start !== null) {
          filter[condition.field] = { $gte: start };
        } else if (end !== null) {
          filter[condition.field] = { $lte: end };
        }
      }
      break;
    case "is_before":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $lt: normalizeSingleValue(condition.value) };
      break;
    case "is_after":
      if (normalizeSingleValue(condition.value) === null) return null;
      filter[condition.field] = { $gt: normalizeSingleValue(condition.value) };
      break;
    case "is_any_of":
    case "has_any_of":
      if (normalizeListValue(condition.value).length === 0) return null;
      filter[condition.field] = { $in: normalizeListValue(condition.value) };
      break;
    case "is_none_of":
    case "has_none_of":
      if (normalizeListValue(condition.value).length === 0) return null;
      filter[condition.field] = { $nin: normalizeListValue(condition.value) };
      break;
    case "has_all_of": {
      const allOfValues = normalizeListValue(condition.value);
      if (allOfValues.length === 0) return null;
      filter[condition.field] = { $contains: allOfValues };
      break;
    }
    case "is_true":
      filter[condition.field] = { $eq: true };
      break;
    case "is_false":
      filter[condition.field] = { $eq: false };
      break;
  }
  return Object.keys(filter).length > 0 ? filter : null;
}
function convertAdvancedFilterToWhere(state) {
  const normalized = normalizeAdvancedFilterState(state);
  if (!normalized.conditions.length) return {};
  const conditionEntries = normalized.conditions.map((condition) => {
    const filter = buildConditionFilter(condition);
    return filter ? {
      join: normalizeJoinOperator(condition.join, normalized.logic),
      filter
    } : null;
  }).filter((entry) => entry !== null);
  if (!conditionEntries.length) return {};
  let clauses = [conditionEntries[0].filter];
  for (const entry of conditionEntries.slice(1)) {
    if (entry.join === "or") {
      clauses = [...clauses, entry.filter];
      continue;
    }
    clauses = clauses.map((clause) => ({
      ...clause,
      ...entry.filter
    }));
  }
  return clauses.length > 1 ? { $or: clauses } : clauses[0];
}
function serializeTree(tree) {
  if (!treeHasRules(tree.root)) return {};
  const out = { "filter[v]": "2" };
  serializeTreeGroup(tree.root, "filter[root]", out);
  return out;
}
function treeHasRules(group) {
  return group.children.some((child) => child.type === "rule" || treeHasRules(child));
}
function serializeTreeGroup(group, prefix, out) {
  out[`${prefix}[combinator]`] = group.combinator;
  group.children.forEach((child, idx) => {
    const childPrefix = `${prefix}[children][${idx}]`;
    if (child.type === "rule") serializeTreeRule(child, childPrefix, out);
    else {
      out[`${childPrefix}[type]`] = "group";
      serializeTreeGroup(child, childPrefix, out);
    }
  });
}
function serializeTreeRule(rule, prefix, out) {
  out[`${prefix}[type]`] = "rule";
  out[`${prefix}[field]`] = rule.field;
  out[`${prefix}[op]`] = rule.operator;
  if (!isValuelessOperator(rule.operator) && rule.value != null) {
    out[`${prefix}[value]`] = typeof rule.value === "object" ? JSON.stringify(rule.value) : String(rule.value);
  }
}
function deserializeTree(query) {
  if (query["filter[v]"] !== "2") return null;
  const root = readTreeGroup("filter[root]", query);
  if (!root) return null;
  return { root };
}
function readTreeGroup(prefix, query) {
  const combRaw = query[`${prefix}[combinator]`];
  if (combRaw !== "and" && combRaw !== "or") return null;
  const children = [];
  for (let i = 0; i < 64; i++) {
    const childPrefix = `${prefix}[children][${i}]`;
    const type = query[`${childPrefix}[type]`];
    if (type === "rule") {
      const field = query[`${childPrefix}[field]`];
      const op = query[`${childPrefix}[op]`];
      if (typeof field !== "string" || typeof op !== "string" || !isValidOperator(op)) continue;
      if (!isUsableFilterField(field)) continue;
      const rawVal = query[`${childPrefix}[value]`];
      children.push({
        id: crypto.randomUUID(),
        type: "rule",
        field,
        operator: op,
        value: parseSerializedFilterValue(rawVal)
      });
    } else if (type === "group") {
      const sub = readTreeGroup(childPrefix, query);
      if (sub) children.push(sub);
    } else {
      break;
    }
  }
  return {
    id: crypto.randomUUID(),
    type: "group",
    combinator: combRaw,
    children
  };
}
function isAdvancedFilterState(value) {
  if (!value || typeof value !== "object") return false;
  const record = value;
  return Array.isArray(record.conditions) && (record.logic === "and" || record.logic === "or");
}
function flatToTree(flat) {
  if (flat.conditions.length === 0) {
    return { root: { id: crypto.randomUUID(), type: "group", combinator: "and", children: [] } };
  }
  const andRuns = [[flat.conditions[0]]];
  for (let i = 1; i < flat.conditions.length; i++) {
    const c = flat.conditions[i];
    if (c.join === "or") andRuns.push([c]);
    else andRuns[andRuns.length - 1].push(c);
  }
  const ruleFromCondition = (c) => ({
    id: crypto.randomUUID(),
    type: "rule",
    field: c.field,
    operator: c.operator,
    value: c.value
  });
  const orChildren = andRuns.map((run) => {
    if (run.length === 1) return ruleFromCondition(run[0]);
    return {
      id: crypto.randomUUID(),
      type: "group",
      combinator: "and",
      children: run.map(ruleFromCondition)
    };
  });
  if (orChildren.length === 1) {
    const only = orChildren[0];
    if (only.type === "group") return { root: only };
    return { root: { id: crypto.randomUUID(), type: "group", combinator: "and", children: [only] } };
  }
  return {
    root: { id: crypto.randomUUID(), type: "group", combinator: "or", children: orChildren }
  };
}
function mapDictionaryColorToTone(color) {
  if (!color || typeof color !== "string") return void 0;
  const trimmed = color.trim().toLowerCase();
  if (!trimmed) return void 0;
  const named = {
    red: "error",
    crimson: "error",
    pink: "pink",
    rose: "pink",
    fuchsia: "pink",
    magenta: "pink",
    green: "success",
    emerald: "success",
    lime: "success",
    teal: "success",
    amber: "warning",
    yellow: "warning",
    orange: "warning",
    blue: "info",
    cyan: "info",
    sky: "info",
    indigo: "info",
    gray: "neutral",
    grey: "neutral",
    slate: "neutral",
    zinc: "neutral",
    stone: "neutral",
    violet: "brand",
    purple: "brand"
  };
  if (named[trimmed]) return named[trimmed];
  const hexMatch = /^#?([0-9a-f]{6})$/i.exec(trimmed);
  if (!hexMatch) return void 0;
  const hex = hexMatch[1];
  const r = parseInt(hex.slice(0, 2), 16);
  const g = parseInt(hex.slice(2, 4), 16);
  const b = parseInt(hex.slice(4, 6), 16);
  const max = Math.max(r, g, b);
  const min = Math.min(r, g, b);
  const delta = max - min;
  if (delta < 24) return "neutral";
  let hue = 0;
  if (max === r) hue = (g - b) / delta % 6;
  else if (max === g) hue = (b - r) / delta + 2;
  else hue = (r - g) / delta + 4;
  hue = (hue * 60 + 360) % 360;
  if (hue < 20) return "error";
  if (hue < 50) return "warning";
  if (hue < 80) return "warning";
  if (hue < 170) return "success";
  if (hue < 260) return "info";
  if (hue < 320) return "brand";
  if (hue < 340) return "pink";
  return "error";
}
export {
  OPERATORS_BY_FIELD_TYPE,
  convertAdvancedFilterToWhere,
  createEmptyCondition,
  deserializeAdvancedFilter,
  deserializeTree,
  flatToTree,
  getDefaultOperator,
  isAdvancedFilterState,
  isValidOperator,
  isValuelessOperator,
  mapDictionaryColorToTone,
  normalizeAdvancedFilterState,
  serializeAdvancedFilter,
  serializeTree
};
//# sourceMappingURL=advanced-filter.js.map
