export class FormValidator {
    constructor(formSelectorOrElement, options = {}) {
        this.rules = new Map();
        this.errors = new Map();
        this.handleSubmit = (e) => {
            e.preventDefault();
            if (this.validate() && this.form) {
                this.options.onValid(this.form);
            }
        };
        this.handleFieldInput = (e) => {
            this.validateField(e.target);
        };
        this.handleFieldBlur = (e) => {
            this.validateField(e.target);
        };
        this.form =
            typeof formSelectorOrElement === "string"
                ? document.querySelector(formSelectorOrElement)
                : formSelectorOrElement;
        this.options = {
            validateOnInput: options.validateOnInput !== false,
            validateOnBlur: options.validateOnBlur !== false,
            showInlineErrors: options.showInlineErrors !== false,
            invalidClass: options.invalidClass ?? "input--invalid",
            validClass: options.validClass ?? "input--valid",
            errorClass: options.errorClass ?? "input__error",
            onValid: options.onValid ?? (() => { }),
            onInvalid: options.onInvalid ?? (() => { }),
            onFieldValidate: options.onFieldValidate ?? (() => { }),
        };
        if (!this.form) {
            console.warn("[Stylescape] FormValidator form not found");
            return;
        }
        this.init();
    }
    addRule(fieldName, rule) {
        const existing = this.rules.get(fieldName) || [];
        existing.push(rule);
        this.rules.set(fieldName, existing);
    }
    removeRules(fieldName) {
        this.rules.delete(fieldName);
    }
    validate() {
        this.errors.clear();
        let isValid = true;
        this.getFields().forEach((field) => {
            if (!this.validateField(field)) {
                isValid = false;
            }
        });
        if (isValid) {
            if (this.form)
                this.options.onValid(this.form);
        }
        else {
            this.options.onInvalid(this.errors);
        }
        return isValid;
    }
    validateField(field) {
        const fieldName = field.name;
        const value = field.value.trim();
        const rules = this.rules.get(fieldName) || [];
        const fieldErrors = [];
        const dataRules = this.parseDataRules(field);
        const allRules = [...rules, ...dataRules];
        for (const rule of allRules) {
            if (!this.checkRule(rule, value, field)) {
                fieldErrors.push(rule.message);
            }
        }
        if (fieldErrors.length > 0) {
            this.errors.set(fieldName, fieldErrors);
        }
        else {
            this.errors.delete(fieldName);
        }
        this.updateFieldUI(field, fieldErrors);
        this.options.onFieldValidate(field, fieldErrors.length === 0, fieldErrors);
        return fieldErrors.length === 0;
    }
    getErrors() {
        return new Map(this.errors);
    }
    getFieldErrors(fieldName) {
        return this.errors.get(fieldName) || [];
    }
    clear() {
        this.errors.clear();
        this.getFields().forEach((field) => {
            field.classList.remove(this.options.invalidClass, this.options.validClass);
            this.removeErrorMessage(field);
        });
    }
    destroy() {
        this.form?.removeEventListener("submit", this.handleSubmit);
        this.getFields().forEach((field) => {
            field.removeEventListener("input", this.handleFieldInput);
            field.removeEventListener("blur", this.handleFieldBlur);
        });
        this.form = null;
        this.rules.clear();
        this.errors.clear();
    }
    init() {
        if (!this.form)
            return;
        this.form.addEventListener("submit", this.handleSubmit);
        this.getFields().forEach((field) => {
            if (this.options.validateOnInput) {
                field.addEventListener("input", this.handleFieldInput);
            }
            if (this.options.validateOnBlur) {
                field.addEventListener("blur", this.handleFieldBlur);
            }
        });
        this.form.setAttribute("novalidate", "");
    }
    getFields() {
        if (!this.form)
            return [];
        return Array.from(this.form.querySelectorAll("input:not([type='submit']):not([type='button']), textarea, select"));
    }
    parseDataRules(field) {
        const rules = [];
        const prefix = "data-ss-validate-";
        if (field.hasAttribute(`${prefix}required`)) {
            rules.push({
                type: "required",
                message: field.getAttribute(`${prefix}required`) ||
                    "This field is required",
            });
        }
        if (field.hasAttribute(`${prefix}email`)) {
            rules.push({
                type: "email",
                message: field.getAttribute(`${prefix}email`) ||
                    "Invalid email address",
            });
        }
        const minLength = field.getAttribute(`${prefix}min-length`);
        if (minLength) {
            rules.push({
                type: "minLength",
                value: parseInt(minLength, 10),
                message: field.getAttribute(`${prefix}min-length-message`) ||
                    `Minimum ${minLength} characters required`,
            });
        }
        const maxLength = field.getAttribute(`${prefix}max-length`);
        if (maxLength) {
            rules.push({
                type: "maxLength",
                value: parseInt(maxLength, 10),
                message: field.getAttribute(`${prefix}max-length-message`) ||
                    `Maximum ${maxLength} characters allowed`,
            });
        }
        const pattern = field.getAttribute(`${prefix}pattern`);
        if (pattern) {
            rules.push({
                type: "pattern",
                value: new RegExp(pattern),
                message: field.getAttribute(`${prefix}pattern-message`) ||
                    "Invalid format",
            });
        }
        const match = field.getAttribute(`${prefix}match`);
        if (match) {
            rules.push({
                type: "match",
                value: match,
                message: field.getAttribute(`${prefix}match-message`) ||
                    "Fields do not match",
            });
        }
        return rules;
    }
    checkRule(rule, value, field) {
        switch (rule.type) {
            case "required":
                return value.length > 0;
            case "email":
                return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
            case "minLength":
                return value.length >= rule.value;
            case "maxLength":
                return value.length <= rule.value;
            case "pattern":
                return rule.value.test(value);
            case "match": {
                const matchField = this.form?.querySelector(`[name="${rule.value}"]`);
                return matchField ? value === matchField.value : false;
            }
            case "custom":
                return rule.validator ? rule.validator(value, field) : true;
            default:
                return true;
        }
    }
    updateFieldUI(field, errors) {
        const isValid = errors.length === 0;
        field.classList.toggle(this.options.invalidClass, !isValid);
        field.classList.toggle(this.options.validClass, isValid && field.value.length > 0);
        field.setAttribute("aria-invalid", String(!isValid));
        if (this.options.showInlineErrors) {
            this.removeErrorMessage(field);
            if (!isValid) {
                this.showErrorMessage(field, errors[0]);
            }
        }
    }
    showErrorMessage(field, message) {
        const errorId = `${field.name}-error`;
        const errorEl = document.createElement("div");
        errorEl.id = errorId;
        errorEl.className = this.options.errorClass;
        errorEl.textContent = message;
        errorEl.setAttribute("role", "alert");
        field.setAttribute("aria-describedby", errorId);
        field.parentNode?.insertBefore(errorEl, field.nextSibling);
    }
    removeErrorMessage(field) {
        const errorId = `${field.name}-error`;
        const errorEl = document.getElementById(errorId);
        errorEl?.remove();
        field.removeAttribute("aria-describedby");
    }
}
export default FormValidator;
//# sourceMappingURL=FormValidator.js.map