"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const dbIvm = require("@tanstack/db-ivm");
const errors = require("../../errors.cjs");
const comparison = require("../../utils/comparison.cjs");
const autoIndex = require("../../indexes/auto-index.cjs");
const ir = require("../ir.cjs");
const functions = require("../builder/functions.cjs");
const evaluators = require("./evaluators.cjs");
const lazyTargets = require("./lazy-targets.cjs");
function processJoins(pipeline, joinClauses, sources, mainCollectionId, mainSource, allInputs, cache, queryMapping, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, rawQuery, onCompileSubquery, aliasToCollectionId, aliasRemapping, sourceWhereClauses) {
  let resultPipeline = pipeline;
  for (const joinClause of joinClauses) {
    resultPipeline = processJoin(
      resultPipeline,
      joinClause,
      sources,
      mainCollectionId,
      mainSource,
      allInputs,
      cache,
      queryMapping,
      collections,
      subscriptions,
      callbacks,
      lazySources,
      optimizableOrderByCollections,
      setWindowFn,
      rawQuery,
      onCompileSubquery,
      aliasToCollectionId,
      aliasRemapping,
      sourceWhereClauses
    );
  }
  return resultPipeline;
}
function processJoin(pipeline, joinClause, sources, mainCollectionId, mainSource, allInputs, cache, queryMapping, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, rawQuery, onCompileSubquery, aliasToCollectionId, aliasRemapping, sourceWhereClauses) {
  const isCollectionRef = joinClause.from.type === `collectionRef`;
  const {
    alias: joinedSource,
    input: joinedInput,
    collectionId: joinedCollectionId
  } = processJoinSource(
    joinClause.from,
    allInputs,
    collections,
    subscriptions,
    callbacks,
    lazySources,
    optimizableOrderByCollections,
    setWindowFn,
    cache,
    queryMapping,
    onCompileSubquery,
    aliasToCollectionId,
    aliasRemapping,
    sourceWhereClauses
  );
  sources[joinedSource] = joinedInput;
  if (isCollectionRef) {
    aliasToCollectionId[joinedSource] = joinedCollectionId;
  }
  const mainCollection = collections[mainCollectionId];
  const joinedCollection = collections[joinedCollectionId];
  if (!mainCollection) {
    throw new errors.JoinCollectionNotFoundError(mainCollectionId);
  }
  if (!joinedCollection) {
    throw new errors.JoinCollectionNotFoundError(joinedCollectionId);
  }
  const { activeSource, lazySource } = getActiveAndLazySources(
    joinClause.type,
    mainCollection,
    joinedCollection
  );
  const availableSources = Object.keys(sources);
  const { mainExpr, joinedExpr } = analyzeJoinExpressions(
    joinClause.left,
    joinClause.right,
    availableSources,
    joinedSource,
    rawQuery.from.type === `unionAll`
  );
  const compiledMainExpr = evaluators.compileExpression(mainExpr);
  const compiledJoinedExpr = evaluators.compileExpression(joinedExpr);
  let mainPipeline = pipeline.pipe(
    dbIvm.map(([currentKey, namespacedRow]) => {
      const mainKey = comparison.normalizeValue(compiledMainExpr(namespacedRow));
      return [mainKey, [currentKey, namespacedRow]];
    })
  );
  let joinedPipeline = joinedInput.pipe(
    dbIvm.map(([currentKey, row]) => {
      const namespacedRow = { [joinedSource]: row };
      const joinedKey = comparison.normalizeValue(compiledJoinedExpr(namespacedRow));
      return [joinedKey, [currentKey, namespacedRow]];
    })
  );
  if (![`inner`, `left`, `right`, `full`].includes(joinClause.type)) {
    throw new errors.UnsupportedJoinTypeError(joinClause.type);
  }
  if (activeSource) {
    const lazyFrom = activeSource === `main` ? joinClause.from : rawQuery.from;
    const limitedSubquery = lazyFrom.type === `queryRef` && (lazyFrom.query.limit || lazyFrom.query.offset);
    const resultUnionLazySide = lazyFrom.type === `unionAll`;
    const lazySourceJoinExpr = activeSource === `main` ? joinedExpr : mainExpr;
    const lazyAlias = activeSource === `main` ? joinedSource : mainSource;
    const lazyTargets$1 = resultUnionLazySide ? [] : lazyTargets.getLazyLoadTargets(
      rawQuery,
      lazyFrom,
      lazyAlias,
      lazySourceJoinExpr,
      lazySource,
      aliasRemapping
    );
    if (!limitedSubquery && lazyTargets$1.length > 0) {
      for (const target of lazyTargets$1) {
        lazySources.add(target.alias);
      }
      const activePipeline = activeSource === `main` ? mainPipeline : joinedPipeline;
      for (const target of lazyTargets$1) {
        const fieldName = target.path[0];
        if (fieldName) {
          autoIndex.ensureIndexForField(fieldName, target.path, target.collection);
        }
      }
      const activePipelineWithLoading = activePipeline.pipe(
        dbIvm.tap((data) => {
          const joinKeys = [
            ...new Set(
              data.getInner().map(([[joinKey]]) => joinKey).filter((key) => key != null)
            )
          ];
          if (joinKeys.length === 0) {
            return;
          }
          for (const target of lazyTargets$1) {
            const lazySourceSubscription = subscriptions[target.alias];
            if (!lazySourceSubscription) {
              throw new errors.SubscriptionNotFoundError(
                target.alias,
                lazyAlias,
                target.collection.id,
                Object.keys(subscriptions)
              );
            }
            if (lazySourceSubscription.hasLoadedInitialState()) {
              continue;
            }
            const lazyJoinRef = new ir.PropRef(target.path);
            const loaded = lazySourceSubscription.requestSnapshot({
              where: functions.inArray(lazyJoinRef, joinKeys),
              optimizedOnly: true
            });
            if (!loaded) {
              const collectionId = target.collection.id;
              const fieldPath = target.path.join(`.`);
              console.warn(
                `[TanStack DB]${collectionId ? ` [${collectionId}]` : ``} Join requires an index on "${fieldPath}" for efficient loading. Falling back to loading all data. Consider creating an index on the collection with collection.createIndex((row) => row.${fieldPath}) or enable auto-indexing with autoIndex: 'eager' and a defaultIndexType.`
              );
              lazySourceSubscription.requestSnapshot();
            }
          }
        })
      );
      if (activeSource === `main`) {
        mainPipeline = activePipelineWithLoading;
      } else {
        joinedPipeline = activePipelineWithLoading;
      }
    }
  }
  return mainPipeline.pipe(
    dbIvm.join(joinedPipeline, joinClause.type),
    processJoinResults(joinClause.type)
  );
}
function analyzeJoinExpressions(left, right, allAvailableSourceAliases, joinedSource, allowResultFields = false) {
  const availableSources = allAvailableSourceAliases.filter(
    (alias) => alias !== joinedSource
  );
  const leftSourceAliases = getSourceAliasesFromExpression(left);
  const rightSourceAliases = getSourceAliasesFromExpression(right);
  const leftReferencesJoined = leftSourceAliases.has(joinedSource);
  const rightReferencesJoined = rightSourceAliases.has(joinedSource);
  const leftAvailableAliases = [...leftSourceAliases].filter(
    (alias) => availableSources.includes(alias) || allowResultFields && alias !== joinedSource
  );
  const rightAvailableAliases = [...rightSourceAliases].filter(
    (alias) => availableSources.includes(alias) || allowResultFields && alias !== joinedSource
  );
  if (leftAvailableAliases.length > 0 && !leftReferencesJoined && rightReferencesJoined && rightAvailableAliases.length === 0) {
    return { mainExpr: left, joinedExpr: right };
  }
  if (leftReferencesJoined && leftAvailableAliases.length === 0 && rightAvailableAliases.length > 0 && !rightReferencesJoined) {
    return { mainExpr: right, joinedExpr: left };
  }
  if (leftSourceAliases.size === 0 || rightSourceAliases.size === 0) {
    throw new errors.InvalidJoinConditionSourceMismatchError();
  }
  if (leftSourceAliases.size === 1 && rightSourceAliases.size === 1 && [...leftSourceAliases][0] === [...rightSourceAliases][0]) {
    throw new errors.InvalidJoinConditionSameSourceError([...leftSourceAliases][0]);
  }
  if (leftAvailableAliases.length === 0) {
    throw new errors.InvalidJoinConditionLeftSourceError([...leftSourceAliases][0]);
  }
  if (!rightReferencesJoined) {
    throw new errors.InvalidJoinConditionRightSourceError(joinedSource);
  }
  throw new errors.InvalidJoinCondition();
}
function getSourceAliasesFromExpression(expr) {
  switch (expr.type) {
    case `ref`:
      return new Set(expr.path[0] ? [expr.path[0]] : []);
    case `func`: {
      const sourceAliases = /* @__PURE__ */ new Set();
      for (const arg of expr.args) {
        for (const alias of getSourceAliasesFromExpression(arg)) {
          sourceAliases.add(alias);
        }
      }
      return sourceAliases;
    }
    default:
      return /* @__PURE__ */ new Set();
  }
}
function processJoinSource(from, allInputs, collections, subscriptions, callbacks, lazySources, optimizableOrderByCollections, setWindowFn, cache, queryMapping, onCompileSubquery, aliasToCollectionId, aliasRemapping, sourceWhereClauses) {
  switch (from.type) {
    case `collectionRef`: {
      const input = allInputs[from.alias];
      if (!input) {
        throw new errors.CollectionInputNotFoundError(
          from.alias,
          from.collection.id,
          Object.keys(allInputs)
        );
      }
      aliasToCollectionId[from.alias] = from.collection.id;
      return { alias: from.alias, input, collectionId: from.collection.id };
    }
    case `queryRef`: {
      const originalQuery = queryMapping.get(from.query) || from.query;
      const subQueryResult = onCompileSubquery(
        originalQuery,
        allInputs,
        collections,
        subscriptions,
        callbacks,
        lazySources,
        optimizableOrderByCollections,
        setWindowFn,
        cache,
        queryMapping
      );
      Object.assign(aliasToCollectionId, subQueryResult.aliasToCollectionId);
      Object.assign(aliasRemapping, subQueryResult.aliasRemapping);
      const isUserDefinedSubquery = queryMapping.has(from.query);
      const fromInnerAlias = getFirstFromAlias(from.query);
      const isOptimizerCreated = !isUserDefinedSubquery && fromInnerAlias !== void 0 && from.alias === fromInnerAlias;
      if (!isOptimizerCreated) {
        for (const [alias, whereClause] of subQueryResult.sourceWhereClauses) {
          sourceWhereClauses.set(alias, whereClause);
        }
      }
      const innerAlias = Object.keys(subQueryResult.aliasToCollectionId).find(
        (alias) => subQueryResult.aliasToCollectionId[alias] === subQueryResult.collectionId
      );
      if (innerAlias && innerAlias !== from.alias) {
        aliasRemapping[from.alias] = innerAlias;
      }
      const subQueryInput = subQueryResult.pipeline;
      const extractedInput = subQueryInput.pipe(
        dbIvm.map((data) => {
          const [key, [value, _orderByIndex]] = data;
          return [key, value];
        })
      );
      return {
        alias: from.alias,
        input: extractedInput,
        collectionId: subQueryResult.collectionId
      };
    }
    default:
      throw new errors.UnsupportedJoinSourceTypeError(from.type);
  }
}
function getFirstFromAlias(query) {
  if (query.from.type === `unionFrom`) {
    return query.from.sources[0]?.alias;
  }
  if (query.from.type === `unionAll`) {
    return void 0;
  }
  return query.from.alias;
}
function processJoinResults(joinType) {
  return function(pipeline) {
    return pipeline.pipe(
      // Process the join result and handle nulls
      dbIvm.filter((result) => {
        const [_key, [main, joined]] = result;
        const mainNamespacedRow = main?.[1];
        const joinedNamespacedRow = joined?.[1];
        if (joinType === `inner`) {
          return !!(mainNamespacedRow && joinedNamespacedRow);
        }
        if (joinType === `left`) {
          return !!mainNamespacedRow;
        }
        if (joinType === `right`) {
          return !!joinedNamespacedRow;
        }
        return true;
      }),
      dbIvm.map((result) => {
        const [_key, [main, joined]] = result;
        const mainKey = main?.[0];
        const mainNamespacedRow = main?.[1];
        const joinedKey = joined?.[0];
        const joinedNamespacedRow = joined?.[1];
        const mergedNamespacedRow = {};
        if (mainNamespacedRow) {
          Object.assign(mergedNamespacedRow, mainNamespacedRow);
        }
        if (joinedNamespacedRow) {
          Object.assign(mergedNamespacedRow, joinedNamespacedRow);
        }
        const resultKey = `[${mainKey},${joinedKey}]`;
        return [resultKey, mergedNamespacedRow];
      })
    );
  };
}
function getActiveAndLazySources(joinType, leftCollection, rightCollection) {
  switch (joinType) {
    case `left`:
      return { activeSource: `main`, lazySource: rightCollection };
    case `right`:
      return { activeSource: `joined`, lazySource: leftCollection };
    case `inner`:
      return leftCollection.size < rightCollection.size ? { activeSource: `main`, lazySource: rightCollection } : { activeSource: `joined`, lazySource: leftCollection };
    default:
      return { activeSource: void 0, lazySource: void 0 };
  }
}
exports.processJoins = processJoins;
//# sourceMappingURL=joins.cjs.map
