<?php

/**
 * Shared API error helpers for handlers and db_init.
 */

function qdb_is_lock_exception(Throwable $e): bool
{
    $msg = $e->getMessage();
    return str_contains($msg, 'Could not acquire lock')
        || str_contains($msg, 'Could not open lock file');
}

/**
 * Map internal exceptions to safe, identifiable API error messages (details go to error_log).
 */
function qdb_public_error_message(Throwable $e, string $contextLabel = 'Operation'): string
{
    $msg = $e->getMessage();

    if (qdb_is_lock_exception($e)) {
        return 'Database is busy because another update is in progress. Please try again in a moment.';
    }
    if (str_contains($msg, 'decrypt') || str_contains($msg, 'QDB_MASTER_KEY')) {
        return 'Database configuration error. Contact the server administrator.';
    }
    if (str_contains($msg, 'Could not read') || str_contains($msg, 'Could not write')
        || str_contains($msg, 'Could not save')) {
        return 'Database storage error. Contact the server administrator.';
    }
    if ($e instanceof PDOException) {
        return $contextLabel . ' failed due to a database error.';
    }

    return $contextLabel . ' failed. If this persists, contact support.';
}

/**
 * @return array{0: string, 1: string, 2: int} code, message, http status
 */
function qdb_api_error_for_exception(Throwable $e, string $contextLabel = 'Operation'): array
{
    if (qdb_is_lock_exception($e)) {
        return ['LOCKED', qdb_public_error_message($e, $contextLabel), 503];
    }
    return ['SERVER_ERROR', qdb_public_error_message($e, $contextLabel), 500];
}

/**
 * Standard handler catch: rollback, discard DB temp file, log, JSON error (never returns).
 */
function qdb_handler_fail(
    Throwable $e,
    string $contextLabel,
    ?PDO $pdo = null,
    ?string $tmpDb = null,
    $lockFp = null
): never {
    if (defined('QDB_TESTING') && qdb_test_is_control_flow_exception($e)) {
        throw $e;
    }
    if ($pdo !== null && $pdo->inTransaction()) {
        try {
            $pdo->rollBack();
        } catch (Throwable) {
        }
    }
    if ($tmpDb !== null && $lockFp !== null) {
        require_once __DIR__ . '/../db_init.php';
        qdb_discard($tmpDb, $lockFp);
    }
    qdb_emit_api_error($e, $contextLabel);
}

/**
 * Log exception and exit with unified JSON error (never returns).
 */
function qdb_emit_api_error(Throwable $e, string $contextLabel): never
{
    if (defined('QDB_TESTING') && qdb_test_is_control_flow_exception($e)) {
        throw $e;
    }
    if (function_exists('qdb_api_log_note_exception')) {
        qdb_api_log_note_exception($e);
    }
    error_log($contextLabel . ': ' . $e->getMessage());
    [$code, $message, $status] = qdb_api_error_for_exception($e, $contextLabel);
    json_error($code, $message, $status);
}
