<?php

/**
 * @version 2.0
 * @author Yeray from App Vinaròs
 * @copyright 2025
 * @link https://appvinaros.com/
 * Chips & bits 😜
 */

/**
 * conectar() | Conecta con la base de datos preparado para el error Too many conections
 * MARK: conectar()
* @version 2.0
 * @return mysqli $con Conexión 
 */
function conectar()
{
    static $con = null;
    static $shutdownRegistered = false;

    if ($con instanceof mysqli) {
        if (@$con->ping()) return $con;
        @$con->close();
        $con = null;
    }

    // Crear conexión “normal” (mejor que persistente para evitar acumular sockets)
    if (!$con = @mysqli_connect("localhost", BBDD_USUARIO, BBDD_CONTRASEÑA)) {
        throw new InvalidArgumentException("[SQL] Error 01 -> No se ha podido conectar con la base de datos");
    }
    if (!@mysqli_select_db($con, BBDD_BASE)) {
        throw new InvalidArgumentException("[SQL] Error 02 -> Fallo en selección de la base de datos");
    }

    date_default_timezone_set('Europe/Madrid');
    setlocale(LC_TIME, 'es_ES.utf8');
    $con->set_charset("utf8mb4");

    if (!$shutdownRegistered) {
        $shutdownRegistered = true;
        register_shutdown_function(function() use (&$con) {
            if ($con instanceof mysqli) { @$con->close(); $con = null; }
        });
    }

    return $con;
}


/**
 * Realiza una consulta segura en la base de datos utilizando parámetros encriptados de forma dinámica.
 * MARK: consulta()
 * @version 2.0
 * 
 * Esta función necesita los siguientes parámetros:
 *
 * @param string $textoconsulta - Consulta con los parámetros en ?
 * @param array $parametros - array de los parámetros.
 * @param bool $esperasdatos - si esperas datos de retorno pon (true), si no quieres obtener resultados pon (false)
 * 
 * @return false|array false falla, sino devolverá un array.
 * 
 * Ejemplo de uso: 
 * 
 * //Ejemplo consultar y recorrer
 * $res=consulta("SELECT * FROM usuarios WHERE id = ?", [$id], true);
 * for($i=0;$i<count($res);$i++)
 *  echo $res[$i]['id'];
 * 
 * //Ejemplo consulta realizada correctamente
 * if(!consulta('INSERT INTO `usuarios`(`usuario`) VALUES (?)',[$usuario],false)) //consulta error
 * else //consulta bien
 */
function consulta($textoconsulta, $parametros, $esperasdatos, $intentos = 0)
{
    try {
        $con = conectar();
        $stmt = $con->prepare($textoconsulta);

        if (!$stmt) {
            throw new Exception("Ha ocurrido un error en la consulta (prepare).");
        }

        if ($parametros != NULL) {
            $tipos = '';
            $vals  = array();

            foreach ($parametros as $valor) {
                if (is_int($valor)) {
                    $tipos .= 'i';
                } elseif (is_float($valor) || is_double($valor)) {
                    $tipos .= 'd';
                } else {
                    $tipos .= 's';
                }
                $vals[] = $valor;
            }

            // Intento moderno (PHP 8+): desempaquetado de argumentos
            if (!@$stmt->bind_param($tipos, ...$vals)) {
                // Fallback para entornos que requieren referencias
                $variables = $vals;
                array_unshift($variables, $tipos);
                $referencias = array();
                foreach ($variables as $clave => $valor) $referencias[$clave] = &$variables[$clave];
                if (!@call_user_func_array(array($stmt, 'bind_param'), $referencias)) {
                    throw new Exception("Ha ocurrido un error en la consulta (bind_param).");
                }
            }
        }

        if (!$stmt->execute()) {
            throw new Exception($stmt->error ?: "Ha ocurrido un error en la consulta (execute).");
        }

        if (!$esperasdatos) {
            $stmt->close();
            // No cerramos $con para reutilizar la conexión
            return true;
        } else {
            $result = $stmt->get_result();
            $resultados = $result ? $result->fetch_all(MYSQLI_ASSOC) : array();
            $stmt->close();
            // No cerramos $con para reutilizar la conexión
            return $resultados;
        }
    } catch (Exception $e) {
        // Verificamos si el error es de "Too many connections"
        $codigo = 0;
        if (isset($con) && $con instanceof mysqli) {
            $codigo = $con->errno ?: 0;
        }
        $esTooMany = ($codigo === 1040) || (strpos($e->getMessage(), "Too many connections") !== false);

        if ($esTooMany) {
            // Cerramos la conexión si existiera para intentar limpiar antes del reintento
            if (isset($con) && $con) {
                @$con->close();
            }

            // Si no hemos alcanzado el máximo de intentos, intentamos de nuevo
            if ($intentos < 3) {
                sleep(2); // Esperamos 2 segundos antes de intentar nuevamente
                return consulta($textoconsulta, $parametros, $esperasdatos, $intentos + 1);
            } else {
                echo "[SQL] Error 1040: (Too many connections) Demasiados intentos de conexión.";
                return false;
            }
        } else {
            // Si el error no es de "Too many connections", lo mostramos y retornamos false
            echo "[SQL] Error 3 -> " . $e->getMessage();
            return false;
        }
    }
}
