<?php
/**
 * core/Helpers.php
 * Funciones de utilidad globales del ERP.
 * Consolida helpers duplicados de api/lab/_helpers.php y config/config.php.
 *
 * REQUIERE: config/config.php ya cargado (para APP_ROOT si se usa).
 */

// ─── Seguridad / Output ─────────────────────────────────────────────────────

/**
 * Escapa una cadena para salida HTML segura.
 */
if (!function_exists('esc')) {
    function esc(?string $value): string
    {
        return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
    }
}

// ─── Respuestas JSON ────────────────────────────────────────────────────────

/**
 * Envía respuesta JSON de éxito y termina la ejecución.
 *
 * @param array  $payload Datos adicionales a fusionar en la respuesta.
 * @param string $message Mensaje descriptivo opcional.
 */
if (!function_exists('jsonOk')) {
    function jsonOk(array $payload = [], string $message = ''): void
    {
        if (ob_get_level() > 0) {
            ob_clean();
        }
        header('Content-Type: application/json; charset=utf-8');
        $resp = ['ok' => true, 'success' => true];
        if ($message !== '') {
            $resp['message'] = $message;
        }
        echo json_encode(array_merge($resp, $payload), JSON_UNESCAPED_UNICODE);
        exit;
    }
}

/**
 * Envía respuesta JSON de error con código HTTP y termina la ejecución.
 *
 * @param string $message Descripción del error.
 * @param int    $code    Código HTTP (400, 401, 403, 404, 500…).
 */
if (!function_exists('jsonError')) {
    function jsonError(string $message, int $code = 400): void
    {
        if (ob_get_level() > 0) {
            ob_clean();
        }
        http_response_code($code);
        header('Content-Type: application/json; charset=utf-8');
        echo json_encode(
            ['ok' => false, 'success' => false, 'error' => $message, 'code' => $code],
            JSON_UNESCAPED_UNICODE
        );
        exit;
    }
}

// ─── Request ────────────────────────────────────────────────────────────────

/**
 * Detiene la ejecución con 405 JSON si el método HTTP no coincide.
 *
 * @param string $method  Método esperado: 'GET', 'POST', etc.
 */
if (!function_exists('requireMethod')) {
    function requireMethod(string $method): void
    {
        if ($_SERVER['REQUEST_METHOD'] !== strtoupper($method)) {
            jsonError('Método no permitido', 405);
        }
    }
}

/**
 * Lee y decodifica el body JSON del request actual.
 * Se cachea en la misma petición.
 *
 * @return array Datos decodificados o array vacío si el body no es JSON válido.
 */
if (!function_exists('inputJson')) {
    function inputJson(): array
    {
        static $parsed = null;
        if ($parsed === null) {
            $raw    = file_get_contents('php://input');
            $parsed = json_decode($raw ?: '{}', true) ?? [];
        }
        return $parsed;
    }
}

// ─── Texto / Formato ────────────────────────────────────────────────────────

/**
 * Convierte bytes a representación legible (KB, MB, GB).
 */
if (!function_exists('formatBytes')) {
    function formatBytes(int $bytes, int $precision = 2): string
    {
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
        $bytes = max($bytes, 0);
        $pow   = $bytes > 0 ? floor(log($bytes) / log(1024)) : 0;
        $pow   = min($pow, count($units) - 1);
        return round($bytes / (1024 ** $pow), $precision) . ' ' . $units[$pow];
    }
}

/**
 * Formatea una fecha a formato legible en español.
 *
 * @param string|null $date  Fecha en cualquier formato reconocible por strtotime.
 * @param bool        $time  Si true incluye la hora.
 */
if (!function_exists('formatDate')) {
    function formatDate(?string $date, bool $time = false): string
    {
        if (!$date) {
            return '—';
        }
        $ts     = strtotime($date);
        $format = $time ? 'd/m/Y H:i' : 'd/m/Y';
        return $ts ? date($format, $ts) : esc($date);
    }
}
