<?php

/**
 * @return bool
 */
function isProd(): bool
{
    if (APP_DEBUG) {
        return false;
    }
    return true;
}

/**
 * @return bool
 */
function isDev(): bool
{
    return APP_DEBUG;
}

/**
 * Gets the API file path for a module.
 *
 * @param string $url Module URL (e.g., "users.list")
 * @param string $path Base path for modules
 * @return string API file path
 */
function getApi(string $url, string $path = "../slm.framework/modules"): string
{
    $url = explode('.', $url);
    $module = $url[0];
    return "$path/$module/slm_api.slm";
}

/**
 * Gets the action file path based on URL and prefix.
 *
 * @param string $url The URL
 * @param string $pre The file prefix (view, ajax, load, proc, end)
 * @param string $path Base path for modules
 * @return string The action file path
 */
function getAction(string $url, string $pre, string $path = "../slm.framework/modules"): string
{
    $url = explode('.', $url);
    $module = $url[0];
    $action = $url[1];
    return "$path/$module/$pre.$action.slm";
}

/**
 * Merges content array into container array.
 *
 * @param array $container The container array (by reference)
 * @param array $content The content to merge
 * @return void
 */
function _mergeArray(array &$container, array $content): void
{
    foreach ($content as $key => $value) {
        $container[$key] = $value;
    }
}

/**
 * Creates a standardized API response array.
 *
 * Usage:
 *   _response(true, 'Error message')           // Error with message
 *   _response(false, 'Success!')               // Success with message
 *   _response(false, '', ['id' => 123])        // Success with data
 *   _response(true, ['custom' => 'data'])      // Error with custom fields
 *
 * @param bool $error True if error, false if success
 * @param string|array|null $message Message string or array of additional fields
 * @param array|null $data Additional data to include in response
 * @return array Standardized response array with 'error' and 'message' keys
 */
function _response(bool $error, string|array|null $message = null, array $data = null): array
{
    $response = [
        'error' => $error,
        'message' => ''
    ];

    if ($message !== null) {
        if (is_array($message)) {
            _mergeArray($response, $message);
        } else {
            $response['message'] = $message;
        }
    }

    if ($data !== null) {
        _mergeArray($response, $data);
    }

    return $response;
}

/**
 * Outputs JSON response and terminates execution.
 *
 * @param array $response The response array to output
 * @return never
 */
function jsonResponse(array $response): never
{
    exit(json_encode($response));
}

/**
 * Creates and outputs an error JSON response.
 *
 * @param string $message The error message
 * @param array|null $data Additional data
 * @return never
 */
function jsonError(string $message, array $data = null): never
{
    jsonResponse(_response(true, $message, $data));
}

/**
 * Creates and outputs a success JSON response.
 *
 * @param string $message The success message
 * @param array|null $data Additional data
 * @return never
 */
function jsonSuccess(string $message = '', array $data = null): never
{
    jsonResponse(_response(false, $message, $data));
}

/**
 * Generates a random alphanumeric string.
 *
 * @param int $length String length
 * @return string Random string
 */
function generateRandomString(int $length = 12): string
{
    return substr(str_shuffle(str_repeat($x = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', ceil($length / strlen($x)))), 1, $length);
}

/**
 * Converts a human-readable date to milliseconds.
 *
 * @param string $date The date string
 * @param string $format Optional format for parsing
 * @return float The date in milliseconds
 */
function convertHumanDateToMilliseconds(string $date, string $format = ''): float
{
    if ($format != '') {
        return 1000 * strtotime(date($format, strtotime($date)));
    }
    return 1000 * strtotime($date);
}

/**
 * Creates a directory if it doesn't exist.
 *
 * @param string $path Directory path
 * @param int $permissions Directory permissions
 * @return void
 * @throws Exception If directory creation fails
 */
function createDirectoryIfNotExists(string $path, int $permissions = 0775): void
{
    if (!file_exists($path)) {
        if (!mkdir($path, $permissions, true)) {
            throw new Exception("Failed to create directory.");
        }
    }
}

/**
 * Moves an uploaded file to the specified path.
 *
 * @param string $name Original file name
 * @param string $tmp Temporary file path
 * @param int $size File size
 * @param string $path Destination path
 * @return array File information
 * @throws Exception If upload fails
 */
function _move_uploaded_file($name, $tpm, $size, string $path = '../store'): array
{
    $extensiones_validas = ['jpeg', 'jpg', 'png'];

    $file_name = $name;
    $file_temp = $tpm;
    $file_size = $size;

    $file_ext = strtolower(pathinfo($file_name, PATHINFO_EXTENSION));

    $name = md5(generateRandomString() . getTimestamp());

    $file['originalName'] = $file_name;
    $file['savedName'] = in_array($file_ext, $extensiones_validas) ? "DOCUMENT_$name.$file_ext" : "DOCUMENT_$name";
    $file['inputName'] = $file_name;
    $file['extension'] = $file_ext;
    $file['md5'] = $name;
    $file['path'] = "$path/{$file['savedName']}";

    if (!@move_uploaded_file($file_temp, "$path/{$file['savedName']}")) {
        throw new Exception('File upload failed!');
    }

    return $file;
}

/**
 * Removes accents from a string.
 *
 * @param string $string Input string
 * @return string|array String without accents
 */
function eliminar_acentos($cadena): array|string
{
    $cadena = str_replace(
        ['Á', 'À', 'Â', 'Ä', 'á', 'à', 'ä', 'â', 'ª'],
        ['A', 'A', 'A', 'A', 'a', 'a', 'a', 'a', 'a'],
        $cadena
    );

    $cadena = str_replace(
        ['É', 'È', 'Ê', 'Ë', 'é', 'è', 'ë', 'ê'],
        ['E', 'E', 'E', 'E', 'e', 'e', 'e', 'e'],
        $cadena);

    $cadena = str_replace(
        ['Í', 'Ì', 'Ï', 'Î', 'í', 'ì', 'ï', 'î'],
        ['I', 'I', 'I', 'I', 'i', 'i', 'i', 'i'],
        $cadena);

    $cadena = str_replace(
        ['Ó', 'Ò', 'Ö', 'Ô', 'ó', 'ò', 'ö', 'ô'],
        ['O', 'O', 'O', 'O', 'o', 'o', 'o', 'o'],
        $cadena);

    $cadena = str_replace(
        ['Ú', 'Ù', 'Û', 'Ü', 'ú', 'ù', 'ü', 'û'],
        ['U', 'U', 'U', 'U', 'u', 'u', 'u', 'u'],
        $cadena);

    $cadena = str_replace(
        ['Ñ', 'ñ', 'Ç', 'ç'],
        ['N', 'n', 'C', 'c'],
        $cadena
    );

    return $cadena;
}

/**
 * Converts a string to snake_case.
 *
 * @param string $string Input string
 * @return string Snake case string
 */
function toSnakeCase($string): string
{
    return strtolower(implode('_', explode(' ', eliminar_acentos($string))));
}

// ============================================================================
// VALIDATION HELPERS
// ============================================================================

/**
 * Válida que todos los campos requeridos estén presentes y cumplan con las condiciones especificadas.
 *
 * @param array $fields Lista de campos requeridos
 * @param array|null $source Fuente de datos donde se buscarán los campos. Si no se proporciona, se utilizará $_POST
 * @param bool $allowEmptyArrays Indica si se permiten arrays vacíos como valores de los campos requeridos
 * @return void
 * @throws InvalidArgumentException Si algún campo requerido no está presente o no cumple con las condiciones
 */
function validateRequiredFields(array $fields, array $source = null, bool $allowEmptyArrays = false): void
{
    $source = $source ?? $_POST;

    foreach ($fields as $field) {
        if (!isset($source[$field])) {
            throw new InvalidArgumentException("The field '{$field}' is required");
        }

        $value = $source[$field];

        if (is_array($value)) {
            if (!$allowEmptyArrays && empty($value)) {
                throw new InvalidArgumentException("The field '{$field}' cannot be empty");
            }
        } elseif (trim((string)$value) === '') {
            throw new InvalidArgumentException("The field '{$field}' is required");
        }
    }
}

/**
 * Validates and sanitizes an email address.
 *
 * @param string $email The email to validate
 * @return string The sanitized email
 * @throws InvalidArgumentException If the email is invalid
 */
function validateEmail(string $email): string
{
    $email = filter_var(trim($email), FILTER_SANITIZE_EMAIL);

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        throw new InvalidArgumentException("The email is not valid");
    }

    return $email;
}

/**
 * Sanitizes a string by trimming and optionally converting case.
 *
 * @param string $value The value to sanitize
 * @param string $case Optional case conversion: 'upper', 'lower', or '' for none
 * @return string The sanitized string
 */
function sanitizeString(string $value, string $case = ''): string
{
    $value = trim($value);

    return match($case) {
        'upper' => strtoupper($value),
        'lower' => strtolower($value),
        default => $value
    };
}

/**
 * Validates and returns an integer within a range.
 *
 * @param mixed $value The value to validate
 * @param int $min Minimum allowed value
 * @param int $max Maximum allowed value
 * @param int $default Default value if validation fails
 * @return int The validated integer
 */
function validateInt(mixed $value, int $min = 0, int $max = PHP_INT_MAX, int $default = 0): int
{
    $int = filter_var($value, FILTER_VALIDATE_INT);

    if ($int === false) {
        return $default;
    }

    return max($min, min($max, $int));
}

/**
 * Validates a boolean value from various input types.
 *
 * @param mixed $value The value to validate
 * @return bool The boolean result
 */
function validateBool(mixed $value): bool
{
    return filter_var($value, FILTER_VALIDATE_BOOLEAN);
}

/**
 * Escapes a value for use in Elasticsearch queries.
 *
 * @param string $value The value to escape
 * @return string The escaped value
 */
function escapeEsValue(string $value): string
{
    return addcslashes($value, '+-=&|><!(){}[]^"~*?:\\/');
}

/**
 * Gets a value from POST with optional default.
 *
 * @param string $key The POST key
 * @param mixed $default Default value if key doesn't exist
 * @return mixed The value or default
 */
function getPostValue(string $key, mixed $default = ''): mixed
{
    return $_POST[$key] ?? $default;
}

/**
 * Gets and trims a string value from POST.
 *
 * @param string $key The POST key
 * @param string $default Default value if key doesn't exist
 * @return string The trimmed value
 */
function getPostString(string $key, string $default = ''): string
{
    return trim($_POST[$key] ?? $default);
}

/**
 * Gets an integer value from POST.
 *
 * @param string $key The POST key
 * @param int $default Default value if key doesn't exist or is invalid
 * @return int The integer value
 */
function getPostInt(string $key, int $default = 0): int
{
    return filter_var($_POST[$key] ?? $default, FILTER_VALIDATE_INT) ?: $default;
}

/**
 * Gets a boolean value from POST.
 *
 * @param string $key The POST key
 * @param bool $default Default value if key doesn't exist
 * @return bool The boolean value
 */
function getPostBool(string $key, bool $default = false): bool
{
    if (!isset($_POST[$key])) {
        return $default;
    }
    return filter_var($_POST[$key], FILTER_VALIDATE_BOOLEAN);
}

// ============================================================================
// APPLICATION SPECIFIC HELPERS
// ============================================================================

/**
 * Formats a number as Mexican currency.
 *
 * @param float $amount The amount to format
 * @return string The formatted currency string
 */
function formatCurrency(float $amount): string
{
    return '$' . number_format($amount, 2, '.', ',');
}

/**
 * Formats a timestamp to Spanish date format (DD/MM/YYYY).
 *
 * @param int $timestamp The timestamp in milliseconds
 * @return string The formatted date
 */
function formatDateES(int $timestamp): string
{
    return date('d/m/Y', $timestamp / 1000);
}

/**
 * Formats a timestamp to Spanish datetime format (DD/MM/YYYY HH:MM).
 *
 * @param int $timestamp The timestamp in milliseconds
 * @return string The formatted datetime
 */
function formatDateTimeES(int $timestamp): string
{
    return date('d/m/Y H:i', $timestamp / 1000);
}

/**
 * Gets the human-readable label for a project type.
 *
 * @param string $tipo The project type code
 * @return string The Spanish label
 */
function getTipoProyectoLabel(string $tipo): string
{
    return match($tipo) {
        'casa' => 'Casa',
        'departamento' => 'Departamento',
        'comercial' => 'Comercial',
        'mixto' => 'Mixto',
        default => ucfirst($tipo)
    };
}

/**
 * Gets the human-readable label for a payment frequency.
 *
 * @param string $frecuencia The frequency code
 * @return string The Spanish label
 */
function getFrecuenciaPagoLabel(string $frecuencia): string
{
    return match($frecuencia) {
        'mensual' => 'Mensual',
        'trimestral' => 'Trimestral',
        'anual' => 'Anual',
        default => ucfirst($frecuencia)
    };
}

/**
 * Gets the human-readable label for a risk level.
 *
 * @param string $nivel The risk level code
 * @return string The Spanish label
 */
function getNivelRiesgoLabel(string $nivel): string
{
    return match($nivel) {
        'bajo' => 'Bajo',
        'medio' => 'Medio',
        'alto' => 'Alto',
        default => ucfirst($nivel)
    };
}

/**
 * Gets the Bootstrap badge class for a risk level.
 *
 * @param string $nivel The risk level code
 * @return string The Bootstrap class
 */
function getNivelRiesgoBadgeClass(string $nivel): string
{
    return match($nivel) {
        'bajo' => 'bg-success',
        'medio' => 'bg-warning',
        'alto' => 'bg-danger',
        default => 'bg-secondary'
    };
}

/**
 * Gets the human-readable label for a video category.
 *
 * @param string $categoria The category code
 * @return string The Spanish label
 */
function getCategoriaVideoLabel(string $categoria): string
{
    return match($categoria) {
        'como_invertir' => 'Cómo Invertir',
        'riesgos' => 'Riesgos',
        'casos_exito' => 'Casos de Éxito',
        'faqs' => 'Preguntas Frecuentes',
        default => ucfirst($categoria)
    };
}

/**
 * Validates a percentage value (0-100).
 *
 * @param float $percentage The percentage to validate
 * @param string $fieldName The field name for error messages
 * @throws InvalidArgumentException If the percentage is out of range
 */
function validatePercentage(float $percentage, string $fieldName = 'Porcentaje'): void
{
    if ($percentage < 0 || $percentage > 100) {
        throw new InvalidArgumentException("{$fieldName} debe estar entre 0 y 100");
    }
}

/**
 * Validates a positive amount.
 *
 * @param float $amount The amount to validate
 * @param string $fieldName The field name for error messages
 * @throws InvalidArgumentException If the amount is not positive
 */
function validatePositiveAmount(float $amount, string $fieldName = 'Monto'): void
{
    if ($amount <= 0) {
        throw new InvalidArgumentException("{$fieldName} debe ser mayor a 0");
    }
}

/**
 * Gets a float value from POST.
 *
 * @param string $key The POST key
 * @param float $default Default value if key doesn't exist or is invalid
 * @return float The float value
 */
function getPostFloat(string $key, float $default = 0.0): float
{
    return filter_var($_POST[$key] ?? $default, FILTER_VALIDATE_FLOAT) ?: $default;
}
