utilClasses/MultiLevelAutoNumbering.js

/**
 * File: src/utilClasses/MultiLevelAutoNumbering.js
 */

import { AutoNumbering } from "./AutoNumbering.js";


/**
 * @class MultiLevelAutoNumbering
 *
 * A hierarchical auto-numbering utility supporting nested sequences like:
 * ```
 * 1.
 * 1.1.
 * 1.2.
 * 2.
 * 2.1.1.
 * ```
 * 
 * Internally uses a stack of {@link AutoNumbering} instances, one for each level.
 * Supports `nest()` to go deeper and `unnest()` to go back.
 *
 * Example:
 * ```js
 * const ml = new MultiLevelAutoNumbering();
 * ml.next("Root A");           // "1. Root A"
 * ml.nest().next("Child A");   // "1.1. Child A"
 * ml.next("Child B");         // "1.2. Child B"
 * ml.unnest().next("Root B");  // "2. Root B"
 * ml.next("Root C");           // "3. Root B"
 * ```
 */
export class MultiLevelAutoNumbering {
	/**
	 * Stores the most recent result to calculate the nested prefix 
	 * 
	 * @type {string}  
	 * */
	#_current = "";

	/**
	 * Stack of AutoNumbering generators, one per nesting level
	 *  
	 * @type {AutoNumbering[]} 
	 * */
	#_an = [];

	/**
	 * The start value of the auto numbering
	 * 
	 *  @type {number}  
	 */
	#_startValue = 1;

	/**
	 * Creates a new multi-level auto-numbering generator.
	 * 
	 * Only the default format `"{0}. "` is supported — other formats are not allowed.
	 *
	 * @param {number} [startValue=1] - The starting number for the top-level counter.
	 * 
	 * @throws {Error} If a custom numbering format is provided.
	 */
	constructor(startValue) {
		this.#_startValue = startValue ?? 1;
		this.reset();
	}

	/**
	 * Resets this instance of {@link MultiLevelAutoNumbering}.
	 */ 
	reset(){
		this.#_current = "";
		this.#_an.length = 0;
		this.#_an.push(new AutoNumbering(this.#_startValue, null));
	}

	/**
	 *  Gets the current nesting level (1 = root).
	 * 
	 * @returns {number} Current nesting level (1 = root) 
	 * */
	get level() { return this.#_an.length; }

	/**
	 * Returns the next string in the current nesting level.
	 * 
	 * @param {string} [text] - Optional content to append after the number (e.g., a title).
	 * @returns {string} Formatted numbered string like `1. Title` or `2.3. Another`.
	 */
	next(text) {
		this.#_current = this.#_an[this.#_an.length - 1].next();
		return this.#_current + (text ?? "");
	}

	/**
	 * Increases the nesting level (e.g., goes from `2.` to `2.1.`, or from `1.2.` to `1.2.1.`).
	 * 
	 * The new level resets its own counter, while prefixing the last generated parent string.
	 * 
	 * @param {number} [startValue=1] - Starting number for the new level.
	 * @returns {MultiLevelAutoNumbering} The current instance (for chaining).
	 */
	nest(startValue) {
		let nxtFormat = this.#_current.trim();
		nxtFormat += "{0}. ";
		this.#_an.push(new AutoNumbering(startValue ?? 1, nxtFormat));
		return this;
	}

	/**
	 * Decreases the nesting level (e.g., goes from `1.1.1.` to `1.1.`).
	 * 
	 * Does nothing if already at the top-level (level 1).
	 *
	 * @returns {MultiLevelAutoNumbering} The current instance (for chaining).
	 */
	unnest() { 
		if(this.#_an.length > 1)
			this.#_an.pop();
		return this;
	}
}