/**
 * Anime.js - utils - CJS
 * @version v4.5.0
 * @license MIT
 * @copyright 2026 - Julian Garnier
 */

'use strict';

var consts = require('../core/consts.cjs');
var helpers = require('../core/helpers.cjs');
var parser = require('../easings/eases/parser.cjs');
var position = require('../timeline/position.cjs');
var values = require('../core/values.cjs');
var targets = require('../core/targets.cjs');
var random = require('./random.cjs');

/**
 * @import {
 *   StaggerParams,
 *   StaggerFunction,
 *   JSTarget,
 * } from '../types/index.js'
*/

/**
 * @import {
 *   Spring,
 * } from '../easings/spring/index.js'
*/

/**
 * @overload
 * @param {Number} val
 * @param {StaggerParams} [params]
 * @return {StaggerFunction<Number>}
 */

/**
 * @overload
 * @param {String} val
 * @param {StaggerParams} [params]
 * @return {StaggerFunction<String>}
 */

/**
 * @overload
 * @param {[Number, Number]} val
 * @param {StaggerParams} [params]
 * @return {StaggerFunction<Number>}
 */

/**
 * @overload
 * @param {[String, String]} val
 * @param {StaggerParams} [params]
 * @return {StaggerFunction<String>}
 */

/**
 * @param {Number|String|[Number, Number]|[String, String]} val The staggered value or range
 * @param {StaggerParams} [params] The stagger parameters
 * @return {StaggerFunction<Number|String>}
 */
const stagger = (val, params = {}) => {
  let values$1 = [];
  let maxValue = 0;
  let cachedOffset;
  let jitterSamples = null;
  const from = params.from;
  const reversed = params.reversed;
  const ease = params.ease;
  const hasEasing = !helpers.isUnd(ease);
  const hasSpring = hasEasing && !helpers.isUnd(/** @type {Spring} */(ease).ease);
  const staggerEase = hasSpring ? /** @type {Spring} */(ease).ease : hasEasing ? parser.parseEase(ease) : null;
  const grid = params.grid;
  const autoGrid = grid === true;
  const axis = params.axis;
  const customTotal = params.total;
  const fromFirst = helpers.isUnd(from) || from === 0 || from === 'first';
  const fromCenter = from === 'center';
  const fromLast = from === 'last';
  const fromRandom = from === 'random';
  const fromArr = helpers.isArr(from);
  const isRange = helpers.isArr(val);
  const useProp = params.use;
  const val1 = isRange ? helpers.parseNumber(val[0]) : helpers.parseNumber(val);
  const val2 = isRange ? helpers.parseNumber(val[1]) : 0;
  const unitMatch = consts.unitsExecRgx.exec((isRange ? val[1] : val) + consts.emptyString);
  const start = params.start || 0 + (isRange ? val1 : 0);
  const seed = params.seed;
  const hasSeed = !helpers.isUnd(seed) && seed !== false;
  const rng = hasSeed ? random.createSeededRandom(seed === true ? 0 : /** @type {Number} */(seed)) : random.random;
  const jitter = params.jitter;
  const hasJitter = !helpers.isUnd(jitter);
  const jitterIsArr = helpers.isArr(jitter);
  const jitterStart = jitterIsArr ? /** @type {[Number,Number]} */(jitter)[0] : /** @type {Number} */(jitter) || 0;
  const jitterEnd = jitterIsArr ? /** @type {[Number,Number]} */(jitter)[1] : /** @type {Number} */(jitter) || 0;
  let fromIndex = fromFirst ? 0 : helpers.isNum(from) ? from : 0;
  return (target, i, t, _, tl) => {
    const [ registeredTarget ] = targets.registerTargets(target);
    const total = helpers.isUnd(customTotal) ? t.length : customTotal;
    const customIndex = !helpers.isUnd(useProp) ? helpers.isFnc(useProp) ? useProp(registeredTarget, i, total) : values.getOriginalAnimatableValue(registeredTarget, useProp) : false;
    const customIdx = helpers.isNum(customIndex) || helpers.isStr(customIndex) && helpers.isNum(+customIndex) ? +customIndex : i;
    // Fall back to the natural index when the resolved value lands outside [0, total) so values[staggerIndex] never reads undefined.
    const staggerIndex = customIdx >= 0 && customIdx < total ? customIdx : i;
    if (fromCenter) fromIndex = (total - 1) / 2;
    if (fromLast) fromIndex = total - 1;
    if (!values$1.length) {
      if (autoGrid) {
        let hasPositions = true;
        let has3D = false;
        let minPosX = Infinity;
        let minPosY = Infinity;
        let minPosZ = Infinity;
        let maxPosX = -Infinity;
        let maxPosY = -Infinity;
        let maxPosZ = -Infinity;
        const pxArr = [];
        const pyArr = [];
        const pzArr = [];
        for (let index = 0; index < total; index++) {
          const el = t[index];
          let px = 0;
          let py = 0;
          let pz = 0;
          let found = false;
          if (el && helpers.isFnc(el.getBoundingClientRect)) {
            const rect = el.getBoundingClientRect();
            px = rect.left + rect.width / 2;
            py = rect.top + rect.height / 2;
            found = true;
          } else {
            const obj = /** @type {JSTarget} */(el);
            if (obj && helpers.isNum(obj.x) && helpers.isNum(obj.y)) {
              px = obj.x;
              py = obj.y;
              if (helpers.isNum(obj.z)) {
                pz = obj.z;
                has3D = true;
              }
              found = true;
            }
          }
          if (!found) {
            hasPositions = false;
            break;
          }
          pxArr.push(px);
          pyArr.push(py);
          pzArr.push(pz);
          if (px < minPosX) minPosX = px;
          if (py < minPosY) minPosY = py;
          if (pz < minPosZ) minPosZ = pz;
          if (px > maxPosX) maxPosX = px;
          if (py > maxPosY) maxPosY = py;
          if (pz > maxPosZ) maxPosZ = pz;
        }
        if (hasPositions) {
          let fX = pxArr[0];
          let fY = pyArr[0];
          let fZ = pzArr[0];
          if (fromArr) {
            fX = minPosX + from[0] * (maxPosX - minPosX);
            fY = minPosY + from[1] * (maxPosY - minPosY);
            fZ = has3D ? minPosZ + (from.length >= 3 ? from[2] : 0.5) * (maxPosZ - minPosZ) : 0;
          } else if (fromCenter) {
            fX = (minPosX + maxPosX) / 2;
            fY = (minPosY + maxPosY) / 2;
            fZ = (minPosZ + maxPosZ) / 2;
          } else if (fromLast) {
            fX = pxArr[total - 1];
            fY = pyArr[total - 1];
            fZ = pzArr[total - 1];
          } else if (helpers.isNum(from)) {
            fX = pxArr[from];
            fY = pyArr[from];
            fZ = pzArr[from];
          }
          for (let index = 0; index < total; index++) {
            const distanceX = fX - pxArr[index];
            const distanceY = fY - pyArr[index];
            const distanceZ = fZ - pzArr[index];
            let value = helpers.sqrt(distanceX * distanceX + distanceY * distanceY + (has3D ? distanceZ * distanceZ : 0));
            if (axis === 'x') value = -distanceX;
            if (axis === 'y') value = -distanceY;
            if (axis === 'z') value = -distanceZ;
            values$1.push(value);
          }
          let minDist = Infinity;
          for (let index = 0; index < total; index++) {
            const absVal = helpers.abs(values$1[index]);
            if (absVal > 0 && absVal < minDist) minDist = absVal;
          }
          if (minDist > 0 && minDist < Infinity) {
            for (let index = 0; index < total; index++) {
              values$1[index] = values$1[index] / minDist;
            }
          }
        } else {
          for (let index = 0; index < total; index++) {
            values$1.push(helpers.abs(fromIndex - index));
          }
        }
      } else {
        for (let index = 0; index < total; index++) {
          if (!grid) {
            values$1.push(helpers.abs(fromIndex - index));
          } else {
            const dims = grid.length;
            const wh = grid[0] * grid[1];
            let fromX, fromY, fromZ;
            if (fromArr) {
              fromX = from[0] * (grid[0] - 1);
              fromY = from[1] * (grid[1] - 1);
              fromZ = dims === 3 ? (from.length >= 3 ? from[2] : 0.5) * (grid[2] - 1) : 0;
            } else if (fromCenter) {
              fromX = (grid[0] - 1) / 2;
              fromY = (grid[1] - 1) / 2;
              fromZ = dims === 3 ? (grid[2] - 1) / 2 : 0;
            } else {
              fromX = fromIndex % grid[0];
              fromY = helpers.floor(fromIndex / grid[0]) % grid[1];
              fromZ = dims === 3 ? helpers.floor(fromIndex / wh) : 0;
            }
            const toX = index % grid[0];
            const toY = helpers.floor(index / grid[0]) % grid[1];
            const toZ = dims === 3 ? helpers.floor(index / wh) : 0;
            const distanceX = fromX - toX;
            const distanceY = fromY - toY;
            const distanceZ = fromZ - toZ;
            let value = helpers.sqrt(distanceX * distanceX + distanceY * distanceY + (dims === 3 ? distanceZ * distanceZ : 0));
            if (axis === 'x') value = -distanceX;
            if (axis === 'y') value = -distanceY;
            if (axis === 'z') value = -distanceZ;
            values$1.push(value);
          }
        }
      }
      maxValue = values$1[0];
      for (let k = 1; k < total; k++) if (values$1[k] > maxValue) maxValue = values$1[k];
      if (staggerEase || reversed) {
        for (let k = 0; k < total; k++) {
          let v = values$1[k];
          if (staggerEase) v = staggerEase(v / maxValue) * maxValue;
          if (reversed) v = axis ? -v : helpers.abs(maxValue - v);
          values$1[k] = v;
        }
      }
      if (hasJitter) {
        jitterSamples = new Array(total);
        for (let k = 0; k < total; k++) jitterSamples[k] = rng(-1, 1, 4);
      }
      if (fromRandom) values$1 = random.shuffle(values$1, rng);
    }
    const spacing = isRange ? (val2 - val1) / maxValue : val1;
    if (helpers.isUnd(cachedOffset)) {
      cachedOffset = tl ? position.parseTimelinePosition(tl, helpers.isUnd(params.start) ? tl.iterationDuration : start) : /** @type {Number} */(start);
    }
    /** @type {String|Number} */
    let output = cachedOffset + ((spacing * helpers.round(values$1[staggerIndex], 2)) || 0);
    if (hasJitter) {
      const progress = maxValue ? values$1[staggerIndex] / maxValue : 0;
      const mag = jitterStart + (jitterEnd - jitterStart) * progress;
      output = /** @type {Number} */(output) + jitterSamples[staggerIndex] * mag;
    }
    if (params.modifier) output = params.modifier(/** @type {Number} */(output));
    if (unitMatch) output = `${output}${unitMatch[2]}`;
    return output;
  }
};

exports.stagger = stagger;
