/**
 * A utility class for parsing date strings using format strings.
 *
 * Supported date format specifiers:
 * - `d`   : Day of the month (1-31) without leading zero.
 * - `dd`  : Day of the month (01-31) with leading zero.
 * - `M`   : Month (1-12) without leading zero.
 * - `MM`  : Month (01-12) with leading zero.
 * - `y`   : Year as the last two digits without leading zero.
 * - `yy`  : Year as the last two digits with leading zero.
 * - `yyyy`: Year as a four-digit number.
 * - `-`   : Separator (hyphen).
 * - `/`   : Separator (slash).
 * - `.`   : Separator (dot).
 * - ` `   : Separator (space).
 *
 * @public
 */
export class DateParser {
    // #region Fields
    static _segmentRegex = /d{1,2}|y{1,4}|M{1,2}|[-/. ]+/g;
    // #endregion
    // #region Methods
    /**
     * Attempts to parse a date string using the provided format.
     * Returns `null` if parsing fails or produces an invalid date.
     *
     * @param value The date string to parse.
     * @param format The expected format of the date string.
     * @param locale The locale (reserved for future use).
     * @returns The parsed Date or null.
     */
    static tryParse(value, format, locale) {
        try {
            const date = this.parse(value, format, locale);
            if (!this.isValidDate(date)) {
                return null;
            }
            return date;
        }
        catch {
            return null;
        }
    }
    /**
     * Parses a date string according to the given format string.
     *
     * @param value The date string to parse.
     * @param format The format string describing the layout.
     * @param locale The locale (reserved for future use).
     * @returns The parsed Date.
     * @throws Error if the format or value cannot be parsed.
     */
    static parse(value, format, locale) {
        const segments = format.match(this._segmentRegex);
        if (!segments) {
            throw new Error(`Unable to parse format string: "${format}".`);
        }
        let position = 0;
        let day = 1;
        let month = 0;
        let year = 0;
        for (const segment of segments) {
            if ((/[-/. ]+/).test(segment)) {
                // Skip separator characters in the value
                position += segment.length;
                continue;
            }
            const extracted = this.extractNumericValue(value, position, segment);
            position += extracted.length;
            switch (segment) {
                case 'd':
                case 'dd':
                    day = extracted.value;
                    break;
                case 'M':
                case 'MM':
                    month = extracted.value - 1; // Months are zero-based
                    break;
                case 'y':
                case 'yy':
                    year = extracted.value < 100 ? 2000 + extracted.value : extracted.value;
                    break;
                case 'yyy':
                case 'yyyy':
                    year = extracted.value;
                    break;
                default:
                    throw new Error(`Unknown date format segment: "${segment}".`);
            }
        }
        const date = new Date(year, month, day);
        // Validate that the constructed date matches the parsed values
        if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
            throw new Error(`Invalid date values: day=${day}, month=${month + 1}, year=${year}.`);
        }
        return date;
    }
    /**
     * Extracts a numeric value from the input string at the given position.
     * The number of digits consumed is determined by the format segment:
     * - `yyyy` / `yyy`: up to 4 digits (min 3 or 4 depending on specifier length).
     * - `dd` / `MM` / `yy`: exactly 2 digits.
     * - `d` / `M` / `y`: 1 to 2 digits (greedy).
     *
     * @param value - The full input string being parsed.
     * @param position - The current character index in `value`.
     * @param segment - The format segment (e.g., `"dd"`, `"M"`, `"yyyy"`).
     * @returns An object containing the parsed numeric `value` and the `length` of characters consumed.
     * @throws Error if fewer digits are found than the segment requires.
     */
    static extractNumericValue(value, position, segment) {
        const maxLength = segment === 'yyyy' || segment === 'yyy' ? 4 : segment.length === 2 ? 2 : 2;
        const minLength = segment.length === 1 ? 1 : segment.length;
        let end = position;
        while (end < value.length && end - position < maxLength && (/\d/).test(value[end])) {
            end++;
        }
        const extracted = value.substring(position, end);
        if (extracted.length < minLength) {
            throw new Error(`Expected at least ${minLength} digit(s) for segment "${segment}" at position ${position}, got "${extracted}".`);
        }
        const numericValue = parseInt(extracted, 10);
        if (isNaN(numericValue)) {
            throw new Error(`Unable to parse numeric value for segment "${segment}" at position ${position}.`);
        }
        return { value: numericValue, length: extracted.length };
    }
    /**
     * Checks whether the given Date object represents a valid point in time.
     *
     * @param date - The Date object to validate.
     * @returns `true` if the date is valid (not `NaN`), otherwise `false`.
     */
    static isValidDate(date) {
        return !isNaN(date.getTime());
    }
}
//# sourceMappingURL=DateParser.js.map