/**
 * Lightweight validation / type helper utilities used across the codebase.
 * Keep implementations small and dependency-free so they work in both
 * Node and Cloudflare Worker runtimes.
 */
/* -------------------------------------------------------------------------- */
/*                               Type Checkers                                */
/* -------------------------------------------------------------------------- */
/** Check whether value is a string primitive */
export const isString = (value) => typeof value === 'string';
/** Check whether value is an array */
export const isArray = (value) => Array.isArray(value);
/** Check whether value is an object (and not null/array) */
export const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
/** Check whether value is a function */
export const isFunction = (value) => typeof value === 'function';
/** Check whether value is a valid Date object */
export const isDate = (value) => value instanceof Date && !Number.isNaN(value.getTime());
/* -------------------------------------------------------------------------- */
/*                            Empty / Null Checks                             */
/* -------------------------------------------------------------------------- */
/**
 * Check if value is "empty".
 * Matches legacy behavior: null, undefined, false, 0, '', '0' are all considered empty.
 */
export const isEmpty = (value) => value === null ||
    value === undefined ||
    value === false ||
    value === 0 ||
    value === '' ||
    value === '0';
/** Check if value is null or string 'null'/'NULL' or empty string */
export const isNull = (value) => value === null ||
    (typeof value === 'string' && value.trim().toLowerCase() === 'null') ||
    (typeof value === 'string' && value === '');
/** Check if value is undefined */
export const isUndefined = (value) => value === undefined;
/** Check if value is undefined or satisfies isNull() */
export const isUndefinedOrNull = (value) => isUndefined(value) || isNull(value);
/**
 * Implementation
 */
export function isBoolean(value, allowString = false) {
    if (typeof value === 'boolean')
        return true;
    if (!allowString)
        return false;
    if (typeof value !== 'string' && typeof value !== 'number')
        return false;
    const v = String(value).trim().toLowerCase();
    return v === 'true' || v === 'false' || v === '1' || v === '0';
}
/** Check if value is a string representation of a boolean (true/false/1/0) */
export const isBooleanString = (value) => typeof value === 'string' && /^(?:true|false|1|0)$/i.test(value.trim());
/* -------------------------------------------------------------------------- */
/*                              Numeric Checks                                */
/* -------------------------------------------------------------------------- */
/** Check if value is a valid number or numeric string */
export const isNumeric = (value) => {
    if (typeof value === 'number')
        return Number.isFinite(value);
    if (typeof value !== 'string')
        return false;
    const s = value.trim();
    if (s.length === 0)
        return false;
    return /^[-+]?\d+(?:\.\d+)?$/.test(s);
};
/**
 * Implementation
 */
export function isInt(value, allowString = false, conditions) {
    let n;
    if (typeof value === 'number') {
        n = value;
    }
    else if (allowString && typeof value === 'string' && /^[+-]?\d+$/.test(value.trim())) {
        n = Number(value);
    }
    else {
        return false;
    }
    if (!Number.isInteger(n))
        return false;
    if (conditions?.min !== undefined && n < conditions.min)
        return false;
    if (conditions?.max !== undefined && n > conditions.max)
        return false;
    return true;
}
/**
 * Implementation
 */
export function isFloat(value, allowString = false, conditions) {
    let n;
    if (typeof value === 'number') {
        n = value;
    }
    else if (allowString &&
        typeof value === 'string' &&
        /^[-+]?\d+(?:\.\d+)?$/.test(value.trim())) {
        n = Number(value);
    }
    else {
        return false;
    }
    if (!Number.isFinite(n))
        return false;
    if (conditions?.min !== undefined && n < conditions.min)
        return false;
    if (conditions?.max !== undefined && n > conditions.max)
        return false;
    return true;
}
/** Check if value is a valid integer string (supports min/max) */
export const isIntString = (value, conditions) => typeof value === 'string' && isInt(value, true, conditions);
/** Check if value is a valid float string (supports min/max) */
export const isFloatString = (value, conditions) => typeof value === 'string' && isFloat(value, true, conditions);
/* -------------------------------------------------------------------------- */
/*                            String / Format Checks                          */
/* -------------------------------------------------------------------------- */
const EMAIL_RE = /^[^\s@]+@[^\s@.]+(\.[^\s@.]+)+$/;
/** Check if value is a valid email string */
export const isEmail = (value) => typeof value === 'string' && EMAIL_RE.test(value);
/** Check if value is a valid URL string (http/https) */
export const isUrl = (value) => {
    if (typeof value !== 'string')
        return false;
    try {
        const u = new URL(value);
        return u.protocol === 'http:' || u.protocol === 'https:';
    }
    catch {
        return false;
    }
};
/** Check if string contains only letters */
export const isAlpha = (value) => typeof value === 'string' && /^[A-Za-z]+$/.test(value);
/** Check if string contains only letters and numbers */
export const isAlphanumeric = (value) => typeof value === 'string' && /^[A-Za-z0-9]+$/.test(value);
/** Check if string matches regex */
const DEFAULT_MAX_REGEX_INPUT_LENGTH = 2048;
/**
 * Check if string matches regex.
 *
 * Security/perf: Regex evaluation can be expensive for some patterns.
 * This helper defensively caps the input length to reduce ReDoS risk
 * when used on untrusted strings.
 */
export const isMatch = (value, regex, options) => {
    if (typeof value !== 'string')
        return false;
    if (!(regex instanceof RegExp))
        return false;
    const maxLength = typeof options?.maxLength === 'number' &&
        Number.isInteger(options.maxLength) &&
        options.maxLength >= 0
        ? options.maxLength
        : DEFAULT_MAX_REGEX_INPUT_LENGTH;
    if (value.length > maxLength)
        return false;
    // Normalize stateful RegExp usage (e.g. /.../g) to avoid surprising false negatives.
    if (regex.global || regex.sticky)
        regex.lastIndex = 0;
    return regex.test(value);
};
/* -------------------------------------------------------------------------- */
/*                            Collection / Length                             */
/* -------------------------------------------------------------------------- */
/** Check if value exists in array */
export const isIn = (value, array) => array.includes(value);
/** Check if value does not exist in array */
export const isNotIn = (value, array) => !array.includes(value);
/** Check if string or array has exact length */
export const isLength = (value, length) => {
    if (typeof value === 'string' || Array.isArray(value))
        return value.length === length;
    return false;
};
/** Check if string or array has minimum length */
export const isMinLength = (value, min) => {
    if (typeof value === 'string' || Array.isArray(value))
        return value.length >= min;
    return false;
};
/** Check if string or array has maximum length */
export const isMaxLength = (value, max) => {
    if (typeof value === 'string' || Array.isArray(value))
        return value.length <= max;
    return false;
};
/* -------------------------------------------------------------------------- */
/*                            Non-Empty Checks                                */
/* -------------------------------------------------------------------------- */
/** Check if value is a string with length > 0 (after trim) */
export const isNonEmptyString = (value) => typeof value === 'string' && value.trim().length > 0;
/** Check if value is an array with items */
export const isNonEmptyArray = (value) => Array.isArray(value) && value.length > 0;
/** Check if value is an object with keys */
export const isNonEmptyObject = (value) => isObject(value) && Object.keys(value).length > 0;
/* -------------------------------------------------------------------------- */
/*                          Additional Format Checks                          */
/* -------------------------------------------------------------------------- */
/**
 * Check if value is a string containing only whitespace.
 * Useful for distinguishing between truly empty and whitespace-only strings.
 */
export const isWhitespaceOnly = (value) => typeof value === 'string' && value.length > 0 && value.trim().length === 0;
/**
 * Check if value is a valid UUID v4 string.
 * Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
 */
export const isUUID = (value) => {
    if (typeof value !== 'string')
        return false;
    const uuidv4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
    return uuidv4Regex.test(value);
};
/**
 * Check if value is a valid JSON string (parses without error).
 */
export const isJSON = (value) => {
    if (typeof value !== 'string')
        return false;
    try {
        JSON.parse(value);
        return true;
    }
    catch {
        return false;
    }
};
/**
 * Check if value is a valid Base64-encoded string.
 */
export const isBase64 = (value) => {
    if (typeof value !== 'string')
        return false;
    const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
    if (!base64Regex.test(value))
        return false;
    // Base64 length must be multiple of 4
    return value.length % 4 === 0;
};
/**
 * Check if value is a valid hexadecimal color string.
 * Accepts #RGB, #RGBA, #RRGGBB, #RRGGBBAA formats.
 */
export const isHexColor = (value) => {
    if (typeof value !== 'string')
        return false;
    // Matches: #RGB (3), #RGBA (4), #RRGGBB (6), #RRGGBBAA (8)
    return /^#(?:[0-9a-f]{3}|[0-9a-f]{4}|[0-9a-f]{6}|[0-9a-f]{8})$/i.test(value);
};
/**
 * Check if value is a valid URL slug (lowercase alphanumeric with hyphens).
 * Example: "my-blog-post", "user-profile-page"
 */
export const isSlug = (value) => {
    if (typeof value !== 'string')
        return false;
    return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(value);
};
/**
 * Check if value is a string with uppercase letters.
 */
export const isUpperCase = (value) => typeof value === 'string' && value.length > 0 && value === value.toUpperCase();
/**
 * Check if value is a string with lowercase letters.
 */
export const isLowerCase = (value) => typeof value === 'string' && value.length > 0 && value === value.toLowerCase();
/* -------------------------------------------------------------------------- */
/*                          Numeric Predicates                                */
/* -------------------------------------------------------------------------- */
/** Check if number is positive (> 0) */
export const isPositive = (value) => typeof value === 'number' && Number.isFinite(value) && value > 0;
/** Check if number is negative (< 0) */
export const isNegative = (value) => typeof value === 'number' && Number.isFinite(value) && value < 0;
/** Check if number is zero */
export const isZero = (value) => typeof value === 'number' && Number.isFinite(value) && value === 0;
/** Check if number is even */
export const isEven = (value) => typeof value === 'number' && Number.isInteger(value) && value % 2 === 0;
/** Check if number is odd */
export const isOdd = (value) => typeof value === 'number' && Number.isInteger(value) && value % 2 !== 0;
/**
 * Check if number has decimal places.
 * Examples: 1.5 → true, 1.0 → false, 1 → false
 */
export const isDecimal = (value) => typeof value === 'number' && Number.isFinite(value) && !Number.isInteger(value);
/**
 * Check if number is between min and max (inclusive).
 */
export const isBetween = (value, min, max) => typeof value === 'number' && Number.isFinite(value) && value >= min && value <= max;
/**
 * Check if number is divisible by divisor.
 */
export const isDivisibleBy = (value, divisor) => typeof value === 'number' && Number.isInteger(value) && divisor !== 0 && value % divisor === 0;
/* -------------------------------------------------------------------------- */
/*                                Factory Export                              */
/* -------------------------------------------------------------------------- */
export const Helpers = Object.freeze({
    isString,
    isBoolean,
    isBooleanString,
    isEmpty,
    isNull,
    isUndefined,
    isUndefinedOrNull,
    isArray,
    isObject,
    isFunction,
    isDate,
    isEmail,
    isUrl,
    isIn,
    isNotIn,
    isLength,
    isMinLength,
    isMaxLength,
    isMatch,
    isAlpha,
    isAlphanumeric,
    isNumeric,
    isInt,
    isFloat,
    isIntString,
    isFloatString,
    isNonEmptyString,
    isNonEmptyArray,
    isNonEmptyObject,
    isWhitespaceOnly,
    isUUID,
    isJSON,
    isBase64,
    isHexColor,
    isSlug,
    isUpperCase,
    isLowerCase,
    isPositive,
    isNegative,
    isZero,
    isEven,
    isOdd,
    isDecimal,
    isBetween,
    isDivisibleBy,
});
