import { groupedOrderByWithFractionalIndex, orderByWithFractionalIndex } from "@tanstack/db-ivm";
import { makeComparator, defaultComparator } from "../../utils/comparison.js";
import { followRef, PropRef } from "../ir.js";
import { ensureIndexForField } from "../../indexes/auto-index.js";
import { findIndexForField } from "../../utils/index-optimization.js";
import { compileExpression } from "./evaluators.js";
import { replaceAggregatesByRefs } from "./group-by.js";
function processOrderBy(rawQuery, pipeline, orderByClause, selectClause, collection, optimizableOrderByCollections, setWindowFn, limit, offset, groupKeyFn) {
  const compiledOrderBy = orderByClause.map((clause) => {
    const clauseWithoutAggregates = replaceAggregatesByRefs(
      clause.expression,
      selectClause,
      `$selected`
    );
    return {
      compiledExpression: compileExpression(clauseWithoutAggregates),
      compareOptions: buildCompareOptions(clause, collection)
    };
  });
  const valueExtractor = (row) => {
    const orderByContext = row;
    if (orderByClause.length > 1) {
      return compiledOrderBy.map(
        (compiled) => compiled.compiledExpression(orderByContext)
      );
    } else if (orderByClause.length === 1) {
      const compiled = compiledOrderBy[0];
      return compiled.compiledExpression(orderByContext);
    }
    return null;
  };
  const compare = (a, b) => {
    if (orderByClause.length > 1) {
      const arrayA = a;
      const arrayB = b;
      for (let i = 0; i < orderByClause.length; i++) {
        const clause = compiledOrderBy[i];
        const compareFn = makeComparator(clause.compareOptions);
        const result = compareFn(arrayA[i], arrayB[i]);
        if (result !== 0) {
          return result;
        }
      }
      return arrayA.length - arrayB.length;
    }
    if (orderByClause.length === 1) {
      const clause = compiledOrderBy[0];
      const compareFn = makeComparator(clause.compareOptions);
      return compareFn(a, b);
    }
    return defaultComparator(a, b);
  };
  let setSizeCallback;
  let orderByOptimizationInfo;
  if (limit && !groupKeyFn) {
    let index;
    let followRefCollection;
    let firstColumnValueExtractor;
    let orderByAlias = rawQuery.from.alias;
    const firstClause = orderByClause[0];
    const firstOrderByExpression = firstClause.expression;
    if (firstOrderByExpression.type === `ref`) {
      const followRefResult = followRef(
        rawQuery,
        firstOrderByExpression,
        collection
      );
      if (followRefResult) {
        followRefCollection = followRefResult.collection;
        const fieldName = followRefResult.path[0];
        const compareOpts = buildCompareOptions(
          firstClause,
          followRefCollection
        );
        if (fieldName) {
          const firstColumnCompareFn = makeComparator(compareOpts);
          ensureIndexForField(
            fieldName,
            followRefResult.path,
            followRefCollection,
            compareOpts,
            firstColumnCompareFn
          );
        }
        firstColumnValueExtractor = compileExpression(
          new PropRef(followRefResult.path),
          true
        );
        index = findIndexForField(
          followRefCollection,
          followRefResult.path,
          compareOpts
        );
        if (!index?.supports(`gt`)) {
          index = void 0;
        }
        orderByAlias = firstOrderByExpression.path.length > 1 ? String(firstOrderByExpression.path[0]) : rawQuery.from.alias;
      }
    }
    if (!firstColumnValueExtractor) ;
    else {
      const allColumnsAreRefs = orderByClause.every(
        (clause) => clause.expression.type === `ref`
      );
      const allColumnExtractors = allColumnsAreRefs ? orderByClause.map((clause) => {
        const refExpr = clause.expression;
        const followResult = followRef(rawQuery, refExpr, collection);
        if (followResult) {
          return compileExpression(
            new PropRef(followResult.path),
            true
          );
        }
        return compileExpression(
          clause.expression,
          true
        );
      }) : void 0;
      const comparator = (a, b) => {
        if (orderByClause.length === 1) {
          const extractedA = a ? firstColumnValueExtractor(a) : a;
          const extractedB = b ? firstColumnValueExtractor(b) : b;
          return compare(extractedA, extractedB);
        }
        if (allColumnExtractors) {
          const extractAll = (row) => {
            if (!row) return row;
            return allColumnExtractors.map((extractor) => extractor(row));
          };
          return compare(extractAll(a), extractAll(b));
        }
        return 0;
      };
      const rawRowValueExtractor = (row) => {
        if (orderByClause.length === 1) {
          return firstColumnValueExtractor(row);
        }
        if (allColumnExtractors) {
          return allColumnExtractors.map((extractor) => extractor(row));
        }
        return void 0;
      };
      orderByOptimizationInfo = {
        alias: orderByAlias,
        offset: offset ?? 0,
        limit,
        comparator,
        valueExtractorForRawRow: rawRowValueExtractor,
        firstColumnValueExtractor,
        index,
        orderBy: orderByClause
      };
      const targetCollectionId = followRefCollection?.id ?? collection.id;
      optimizableOrderByCollections[targetCollectionId] = orderByOptimizationInfo;
      setSizeCallback = (getSize) => {
        optimizableOrderByCollections[targetCollectionId][`dataNeeded`] = () => {
          const size = getSize();
          return Math.max(0, orderByOptimizationInfo.limit - size);
        };
      };
    }
  }
  if (groupKeyFn) {
    return pipeline.pipe(
      groupedOrderByWithFractionalIndex(valueExtractor, {
        limit,
        offset,
        comparator: compare,
        setSizeCallback,
        groupKeyFn,
        setWindowFn: (windowFn) => {
          setWindowFn((options) => {
            windowFn(options);
            if (orderByOptimizationInfo) {
              orderByOptimizationInfo.offset = options.offset ?? orderByOptimizationInfo.offset;
              orderByOptimizationInfo.limit = options.limit ?? orderByOptimizationInfo.limit;
            }
          });
        }
      })
    );
  }
  return pipeline.pipe(
    orderByWithFractionalIndex(valueExtractor, {
      limit,
      offset,
      comparator: compare,
      setSizeCallback,
      setWindowFn: (windowFn) => {
        setWindowFn(
          // We wrap the move function such that we update the orderByOptimizationInfo
          // because that is used by the `dataNeeded` callback to determine if we need to load more data
          (options) => {
            windowFn(options);
            if (orderByOptimizationInfo) {
              orderByOptimizationInfo.offset = options.offset ?? orderByOptimizationInfo.offset;
              orderByOptimizationInfo.limit = options.limit ?? orderByOptimizationInfo.limit;
            }
          }
        );
      }
    })
    // orderByWithFractionalIndex returns [key, [value, index]] - we keep this format
  );
}
function buildCompareOptions(clause, collection) {
  if (clause.compareOptions.stringSort !== void 0) {
    return clause.compareOptions;
  }
  return {
    ...collection.compareOptions,
    direction: clause.compareOptions.direction,
    nulls: clause.compareOptions.nulls
  };
}
export {
  buildCompareOptions,
  processOrderBy
};
//# sourceMappingURL=order-by.js.map
