"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const dbIvm = require("@tanstack/db-ivm");
const errors = require("../../errors.cjs");
const expressions = require("../compiler/expressions.cjs");
const index = require("../builder/index.cjs");
const ir = require("../ir.cjs");
function extractCollectionsFromQuery(query) {
  const collections = {};
  function extractFromSource(source) {
    if (source.type === `collectionRef`) {
      collections[source.collection.id] = source.collection;
    } else if (source.type === `queryRef`) {
      extractFromQuery(source.query);
    }
  }
  function extractFromQuery(q) {
    if (q.from) {
      extractFromSource(q.from);
    }
    if (q.join && Array.isArray(q.join)) {
      for (const joinClause of q.join) {
        if (joinClause.from) {
          extractFromSource(joinClause.from);
        }
      }
    }
    if (q.select) {
      extractFromSelect(q.select);
    }
  }
  function extractFromSelect(select) {
    for (const [key, value] of Object.entries(select)) {
      if (typeof key === `string` && key.startsWith(`__SPREAD_SENTINEL__`)) {
        continue;
      }
      if (value instanceof ir.IncludesSubquery) {
        extractFromQuery(value.query);
      } else if (isNestedSelectObject(value)) {
        extractFromSelect(value);
      }
    }
  }
  extractFromQuery(query);
  return collections;
}
function extractCollectionFromSource(query) {
  const from = query.from;
  if (from.type === `collectionRef`) {
    return from.collection;
  } else if (from.type === `queryRef`) {
    return extractCollectionFromSource(from.query);
  }
  throw new Error(
    `Failed to extract collection. Invalid FROM clause: ${JSON.stringify(query)}`
  );
}
function extractCollectionAliases(query) {
  const aliasesById = /* @__PURE__ */ new Map();
  function recordAlias(source) {
    if (!source) return;
    if (source.type === `collectionRef`) {
      const { id } = source.collection;
      const existing = aliasesById.get(id);
      if (existing) {
        existing.add(source.alias);
      } else {
        aliasesById.set(id, /* @__PURE__ */ new Set([source.alias]));
      }
    } else if (source.type === `queryRef`) {
      traverse(source.query);
    }
  }
  function traverseSelect(select) {
    for (const [key, value] of Object.entries(select)) {
      if (typeof key === `string` && key.startsWith(`__SPREAD_SENTINEL__`)) {
        continue;
      }
      if (value instanceof ir.IncludesSubquery) {
        traverse(value.query);
      } else if (isNestedSelectObject(value)) {
        traverseSelect(value);
      }
    }
  }
  function traverse(q) {
    if (!q) return;
    recordAlias(q.from);
    if (q.join) {
      for (const joinClause of q.join) {
        recordAlias(joinClause.from);
      }
    }
    if (q.select) {
      traverseSelect(q.select);
    }
  }
  traverse(query);
  return aliasesById;
}
function isNestedSelectObject(obj) {
  if (obj === null || typeof obj !== `object`) return false;
  if (obj instanceof ir.IncludesSubquery) return false;
  if (`type` in obj && typeof obj.type === `string`) return false;
  if (obj.__refProxy) return false;
  return true;
}
function buildQueryFromConfig(config) {
  const query = typeof config.query === `function` ? index.buildQuery(config.query) : index.getQueryIR(config.query);
  if (config.requireObjectResult && query.select && !isNestedSelectObject(query.select)) {
    throw new errors.UnsupportedRootScalarSelectError();
  }
  return query;
}
function sendChangesToInput(input, changes, getKey) {
  const multiSetArray = [];
  for (const change of changes) {
    const key = getKey(change.value);
    if (change.type === `insert`) {
      multiSetArray.push([[key, change.value], 1]);
    } else if (change.type === `update`) {
      multiSetArray.push([[key, change.previousValue], -1]);
      multiSetArray.push([[key, change.value], 1]);
    } else {
      multiSetArray.push([[key, change.value], -1]);
    }
  }
  if (multiSetArray.length !== 0) {
    input.sendData(new dbIvm.MultiSet(multiSetArray));
  }
  return multiSetArray.length;
}
function* splitUpdates(changes) {
  for (const change of changes) {
    if (change.type === `update`) {
      yield { type: `delete`, key: change.key, value: change.previousValue };
      yield { type: `insert`, key: change.key, value: change.value };
    } else {
      yield change;
    }
  }
}
function filterDuplicateInserts(changes, sentKeys) {
  const filtered = [];
  for (const change of changes) {
    if (change.type === `insert`) {
      if (sentKeys.has(change.key)) {
        continue;
      }
      sentKeys.add(change.key);
    } else if (change.type === `delete`) {
      sentKeys.delete(change.key);
    }
    filtered.push(change);
  }
  return filtered;
}
function trackBiggestSentValue(changes, current, sentKeys, comparator) {
  let biggest = current;
  let shouldResetLoadKey = false;
  for (const change of changes) {
    if (change.type === `delete`) continue;
    const isNewKey = !sentKeys.has(change.key);
    if (biggest === void 0) {
      biggest = change.value;
      shouldResetLoadKey = true;
    } else if (comparator(biggest, change.value) < 0) {
      biggest = change.value;
      shouldResetLoadKey = true;
    } else if (isNewKey) {
      shouldResetLoadKey = true;
    }
  }
  return { biggest, shouldResetLoadKey };
}
function computeSubscriptionOrderByHints(query, alias) {
  const { orderBy, limit, offset } = query;
  const effectiveLimit = limit !== void 0 && offset !== void 0 ? limit + offset : limit;
  const normalizedOrderBy = orderBy ? expressions.normalizeOrderByPaths(orderBy, alias) : void 0;
  const canPassOrderBy = normalizedOrderBy?.every((clause) => {
    const exp = clause.expression;
    if (exp.type !== `ref`) return false;
    const path = exp.path;
    return Array.isArray(path) && path.length === 1;
  }) ?? false;
  return {
    orderBy: canPassOrderBy ? normalizedOrderBy : void 0,
    limit: canPassOrderBy ? effectiveLimit : void 0
  };
}
function computeOrderedLoadCursor(orderByInfo, biggestSentRow, lastLoadRequestKey, alias, limit) {
  const { orderBy, valueExtractorForRawRow, offset } = orderByInfo;
  const extractedValues = biggestSentRow ? valueExtractorForRawRow(biggestSentRow) : void 0;
  let minValues;
  if (extractedValues !== void 0) {
    minValues = Array.isArray(extractedValues) ? extractedValues : [extractedValues];
  }
  const loadRequestKey = dbIvm.serializeValue({
    minValues: minValues ?? null,
    offset,
    limit
  });
  if (lastLoadRequestKey === loadRequestKey) {
    return void 0;
  }
  const normalizedOrderBy = expressions.normalizeOrderByPaths(orderBy, alias);
  return { minValues, normalizedOrderBy, loadRequestKey };
}
exports.buildQueryFromConfig = buildQueryFromConfig;
exports.computeOrderedLoadCursor = computeOrderedLoadCursor;
exports.computeSubscriptionOrderByHints = computeSubscriptionOrderByHints;
exports.extractCollectionAliases = extractCollectionAliases;
exports.extractCollectionFromSource = extractCollectionFromSource;
exports.extractCollectionsFromQuery = extractCollectionsFromQuery;
exports.filterDuplicateInserts = filterDuplicateInserts;
exports.sendChangesToInput = sendChangesToInput;
exports.splitUpdates = splitUpdates;
exports.trackBiggestSentValue = trackBiggestSentValue;
//# sourceMappingURL=utils.cjs.map
