(function (root, factory) {
  "use strict";

  const api = Object.freeze(factory());
  if (typeof module === "object" && module.exports) module.exports = api;
  if (root) root.MbnLegacyBatchSelectionWorkflow = api;
})(typeof globalThis === "object" ? globalThis : this, function () {
  "use strict";

  const MAX_ITEMS = 2048;
  const identifierPattern = /^[A-Za-z0-9_-]{1,128}$/;

  function requireItemCount(value) {
    const count = Number(value);
    if (!Number.isInteger(count) || count < 0 || count > MAX_ITEMS) {
      throw new RangeError(`The selectable item count must be from 0 through ${MAX_ITEMS}.`);
    }
    return count;
  }

  function requireIndex(value, count, name) {
    const index = Number(value);
    if (!Number.isInteger(index) || index < 0 || index >= count) {
      throw new RangeError(`${name} is outside the selectable item range.`);
    }
    return index;
  }

  function normalizeSelectedIndices(values, count) {
    if (!Array.isArray(values)) throw new TypeError("Selected indices must be an array.");
    const unique = new Set();
    for (const value of values) unique.add(requireIndex(value, count, "A selected index"));
    return [...unique].sort((left, right) => left - right);
  }

  function normalizeAnchor(value, count) {
    if (value === undefined || value === null || value === -1) return -1;
    return requireIndex(value, count, "The selection anchor");
  }

  function createSelection(itemCount, selectedIndices = [], anchorIndex = -1) {
    const count = requireItemCount(itemCount);
    const selected = normalizeSelectedIndices(selectedIndices, count);
    const anchor = normalizeAnchor(anchorIndex, count);
    return Object.freeze({
      itemCount: count,
      selectedIndices: Object.freeze(selected),
      anchorIndex: anchor
    });
  }

  function inclusiveRange(left, right) {
    const from = Math.min(left, right);
    const to = Math.max(left, right);
    return Array.from({ length: to - from + 1 }, (_, offset) => from + offset);
  }

  // Mirrors MainForm.listView1_MouseDown: plain click replaces, Ctrl toggles,
  // Shift replaces with an anchor range, and Ctrl+Shift unions that range.
  // The clicked row becomes the next anchor after every successful gesture.
  function applyPointerSelection(selection, targetIndex, modifiers = {}) {
    if (!selection || typeof selection !== "object" || Array.isArray(selection)) {
      throw new TypeError("A legacy selection snapshot is required.");
    }
    const current = createSelection(
      selection.itemCount,
      selection.selectedIndices,
      selection.anchorIndex);
    const target = requireIndex(targetIndex, current.itemCount, "The target index");
    if (!modifiers || typeof modifiers !== "object" || Array.isArray(modifiers)) {
      throw new TypeError("Selection modifiers must be an object.");
    }
    const control = modifiers.ctrlKey === true;
    const shift = modifiers.shiftKey === true;
    const anchor = current.anchorIndex >= 0 ? current.anchorIndex : target;
    let selected;

    if (control && shift) {
      selected = [...new Set([...current.selectedIndices, ...inclusiveRange(anchor, target)])]
        .sort((left, right) => left - right);
    } else if (control) {
      const values = new Set(current.selectedIndices);
      if (values.has(target)) values.delete(target);
      else values.add(target);
      selected = [...values].sort((left, right) => left - right);
    } else if (shift) {
      selected = inclusiveRange(anchor, target);
    } else {
      selected = [target];
    }

    return createSelection(current.itemCount, selected, target);
  }

  function canActivateSingle(selection, targetIndex) {
    if (!selection || typeof selection !== "object" || Array.isArray(selection)) return false;
    try {
      const current = createSelection(
        selection.itemCount,
        selection.selectedIndices,
        selection.anchorIndex);
      const target = requireIndex(targetIndex, current.itemCount, "The target index");
      return current.selectedIndices.length === 1 && current.selectedIndices[0] === target;
    } catch {
      return false;
    }
  }

  // Materialize the complete batch before the caller mutates its playlist. A
  // validation error therefore cannot leave an operator-visible partial batch.
  function materializeAtomicBatch(items, materialize) {
    if (!Array.isArray(items) || items.length < 1 || items.length > MAX_ITEMS) {
      throw new RangeError(`A batch must contain from 1 through ${MAX_ITEMS} items.`);
    }
    if (typeof materialize !== "function") {
      throw new TypeError("A batch materializer is required.");
    }
    const entries = [];
    const ids = new Set();
    for (let index = 0; index < items.length; index += 1) {
      const entry = materialize(items[index], index);
      if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
        throw new TypeError("Every materialized batch item must be a playlist entry object.");
      }
      const id = typeof entry.id === "string" ? entry.id.trim() : "";
      if (!identifierPattern.test(id) || ids.has(id)) {
        throw new TypeError("Every materialized batch item must have a unique safe id.");
      }
      ids.add(id);
      entries.push(entry);
    }
    return Object.freeze(entries);
  }

  return Object.freeze({
    MAX_ITEMS,
    createSelection,
    applyPointerSelection,
    canActivateSingle,
    materializeAtomicBatch
  });
});
