const DISPLAY_DATE_PATTERN = /^(\d{2})\.(\d{2})\.(\d{4})$/;
const ISO_DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
const boundDateInputs = new WeakSet();

export function formatDateInputValue(value) {
    const text = String(value || '');
    const isoMatch = text.match(ISO_DATE_PATTERN);
    if (isoMatch) {
        return `${isoMatch[3]}.${isoMatch[2]}.${isoMatch[1]}`;
    }

    const digits = text.replace(/\D/g, '').slice(0, 8);
    if (digits.length <= 2) return digits;
    if (digits.length <= 4) return `${digits.slice(0, 2)}.${digits.slice(2)}`;
    return `${digits.slice(0, 2)}.${digits.slice(2, 4)}.${digits.slice(4)}`;
}

export function dateInputValueToIso(value) {
    const text = String(value || '').trim();
    if (!text) return '';

    const match = text.match(DISPLAY_DATE_PATTERN);
    if (!match) {
        throw new TypeError('Введите дату полностью в формате ДД.ММ.ГГГГ (год — 4 цифры)');
    }

    const [, dayText, monthText, yearText] = match;
    const day = Number(dayText);
    const month = Number(monthText);
    const year = Number(yearText);
    if (year < 1 || month < 1 || month > 12 || day < 1 || day > daysInMonth(month, year)) {
        throw new TypeError('Введите существующую дату в формате ДД.ММ.ГГГГ');
    }

    return `${yearText}-${monthText}-${dayText}`;
}

export function isoDateToInputValue(value) {
    const text = String(value || '').trim();
    if (!text) return '';

    const match = text.match(ISO_DATE_PATTERN);
    if (!match) {
        throw new TypeError('Ожидалась дата в формате ГГГГ-ММ-ДД');
    }

    const [, year, month, day] = match;
    dateInputValueToIso(`${day}.${month}.${year}`);
    return `${day}.${month}.${year}`;
}

export function bindDateInputMask(input) {
    if (!input || boundDateInputs.has(input)) return;
    boundDateInputs.add(input);

    input.addEventListener('input', () => {
        const rawValue = input.value;
        const caret = input.selectionStart ?? rawValue.length;
        const digitsBeforeCaret = rawValue.slice(0, caret).replace(/\D/g, '').length;
        const caretWasAtEnd = caret === rawValue.length;
        const formattedValue = formatDateInputValue(rawValue);

        input.value = formattedValue;
        clearDateInputError(input);
        const nextCaret = caretPositionAfterDigits(formattedValue, digitsBeforeCaret, caretWasAtEnd);
        input.setSelectionRange?.(nextCaret, nextCaret);
    });

    input.addEventListener('keydown', event => {
        if (event.key === 'Backspace'
            && input.selectionStart === input.selectionEnd
            && input.selectionStart > 0
            && input.value[input.selectionStart - 1] === '.') {
            event.preventDefault();
            const nextCaret = input.selectionStart - 1;
            input.setSelectionRange(nextCaret, nextCaret);
        }

        if (event.key === 'Delete'
            && input.selectionStart === input.selectionEnd
            && input.value[input.selectionStart] === '.') {
            event.preventDefault();
            const nextCaret = input.selectionStart + 1;
            input.setSelectionRange(nextCaret, nextCaret);
        }
    });

    input.addEventListener('blur', () => {
        if (!input.value) {
            clearDateInputError(input);
            return;
        }

        try {
            const isoValue = dateInputValueToIso(input.value);
            input.value = isoDateToInputValue(isoValue);
            clearDateInputError(input);
        } catch (error) {
            setDateInputError(input, error.message);
        }
    });

    input.form?.addEventListener('reset', () => clearDateInputError(input));
}

export function readDateInputIso(input, label = 'Дата') {
    try {
        const value = dateInputValueToIso(input?.value);
        clearDateInputError(input);
        return value;
    } catch (error) {
        const message = `${label}: ${lowercaseFirst(error.message)}`;
        setDateInputError(input, message);
        input?.focus();
        throw new Error(message);
    }
}

export function setDateInputIso(input, value) {
    if (!input) return;
    input.value = isoDateToInputValue(value);
    clearDateInputError(input);
}

function daysInMonth(month, year) {
    const monthLengths = [
        31,
        isLeapYear(year) ? 29 : 28,
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31
    ];
    return monthLengths[month - 1] || 0;
}

function isLeapYear(year) {
    return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
}

function caretPositionAfterDigits(value, digitCount, preferEnd) {
    if (digitCount <= 0) return 0;

    let seenDigits = 0;
    for (let index = 0; index < value.length; index += 1) {
        if (!/\d/.test(value[index])) continue;
        seenDigits += 1;
        if (seenDigits === digitCount) {
            let position = index + 1;
            if (preferEnd) {
                while (value[position] === '.') position += 1;
            }
            return position;
        }
    }
    return value.length;
}

function setDateInputError(input, message) {
    if (!input) return;
    input.setCustomValidity?.(message);
    input.setAttribute?.('aria-invalid', 'true');
}

function clearDateInputError(input) {
    if (!input) return;
    input.setCustomValidity?.('');
    input.removeAttribute?.('aria-invalid');
}

function lowercaseFirst(value) {
    const text = String(value || '');
    return text ? text[0].toLowerCase() + text.slice(1) : text;
}
