<?php
require_once __DIR__ . '/config.php';

/**
 * Return the shared PDO connection, lazily opened.
 * Throws on failure — call sites are expected to catch or let it bubble.
 */
function db(): PDO {
    static $pdo = null;
    if ($pdo !== null) return $pdo;

    $dsn = sprintf(
        'mysql:host=%s;dbname=%s;charset=utf8mb4',
        DB_HOST,
        DB_NAME
    );

    $pdo = new PDO($dsn, DB_USER, DB_PASS, [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ]);
    return $pdo;
}

/** Fetch a single row or null. */
function db_row(string $sql, array $params = []): ?array {
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    $row = $stmt->fetch();
    return $row === false ? null : $row;
}

/** Fetch all matching rows. */
function db_all(string $sql, array $params = []): array {
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll();
}

/** Fetch the first column of the first row (or null). */
function db_value(string $sql, array $params = []) {
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    $val = $stmt->fetchColumn();
    return $val === false ? null : $val;
}

/** Run an UPDATE/INSERT/DELETE. Returns the affected row count. */
function db_exec(string $sql, array $params = []): int {
    $stmt = db()->prepare($sql);
    $stmt->execute($params);
    return $stmt->rowCount();
}

/** Insert a row from an associative array. Returns last insert ID. */
function db_insert(string $table, array $row): int {
    $cols = array_keys($row);
    $sql  = sprintf(
        'INSERT INTO %s (%s) VALUES (%s)',
        $table,
        implode(',', array_map(fn($c) => "`$c`", $cols)),
        implode(',', array_map(fn($c) => ":$c", $cols))
    );
    $stmt = db()->prepare($sql);
    $stmt->execute($row);
    return (int)db()->lastInsertId();
}

/** Update a row by its primary key. */
function db_update(string $table, int $id, array $row): int {
    $sets = [];
    foreach (array_keys($row) as $c) {
        $sets[] = "`$c` = :$c";
    }
    $sql = sprintf('UPDATE %s SET %s WHERE id = :__id', $table, implode(',', $sets));
    $stmt = db()->prepare($sql);
    $row['__id'] = $id;
    $stmt->execute($row);
    return $stmt->rowCount();
}

/** Is the database reachable and does the members table exist? */
function db_is_ready(): bool {
    try {
        db_value('SELECT 1 FROM members LIMIT 1');
        return true;
    } catch (Throwable $e) {
        return false;
    }
}

/**
 * Generic logger — writes to the app_log table.
 * Defined here in db.php so any code that uses the database also has logging.
 * Safe to call from anywhere; never throws.
 */
if (!function_exists('app_log')) {
    function app_log(string $msg): void {
        try {
            db_exec(
                "INSERT INTO app_log (occurred_at, level, message) VALUES (NOW(), 'info', :m)",
                ['m' => mb_substr($msg, 0, 5000)]
            );
        } catch (Throwable $e) {
            error_log('app_log failed: ' . $e->getMessage() . ' — original message: ' . $msg);
        }
    }
}