<?php
// =============================================================================
// error_log.php — panel-side error logging.
//
// Writes to `data/panel.error.log` (JSON-lines format, one entry per line).
// Captures: PHP errors via set_error_handler, fatal errors via shutdown,
// uncaught exceptions via set_exception_handler, plus explicit calls to
// panel_log_exception($e, ['context' => 'foo']).
//
// Buyer reads the log through Settings → Error Log (the admin page). The log
// rolls automatically when it crosses 1 MiB.
// =============================================================================

if (!defined('PANEL_ERROR_LOG_PATH')) {
    define('PANEL_ERROR_LOG_PATH',        __DIR__ . '/../data/panel.error.log');
    define('PANEL_ERROR_LOG_DISABLED_FLAG',__DIR__ . '/../data/.error_log_disabled');
    define('PANEL_ERROR_LOG_MAX_BYTES',   1024 * 1024);  // 1 MiB before rotation
}

// Cheap is-enabled check. File-based so the kill-switch works even when the
// DB is broken (which is exactly when you need the error log to keep working).
function panel_log_enabled(): bool
{
    return !is_file(PANEL_ERROR_LOG_DISABLED_FLAG);
}

function panel_log_set_enabled(bool $on): void
{
    $dir = dirname(PANEL_ERROR_LOG_DISABLED_FLAG);
    if (!is_dir($dir)) { @mkdir($dir, 0755, true); }
    if ($on) {
        @unlink(PANEL_ERROR_LOG_DISABLED_FLAG);
    } else {
        @file_put_contents(PANEL_ERROR_LOG_DISABLED_FLAG, "disabled at " . date('c') . "\n");
    }
}

function panel_log_write(array $entry): void
{
    if (!panel_log_enabled()) return;

    $dir = dirname(PANEL_ERROR_LOG_PATH);
    if (!is_dir($dir)) {
        @mkdir($dir, 0755, true);
    }

    // Cheap rotation: if log is over the cap, rename to .1 and start fresh.
    if (is_file(PANEL_ERROR_LOG_PATH) && filesize(PANEL_ERROR_LOG_PATH) > PANEL_ERROR_LOG_MAX_BYTES) {
        @rename(PANEL_ERROR_LOG_PATH, PANEL_ERROR_LOG_PATH . '.1');
    }

    $entry['ts']        = time();
    $entry['ts_iso']    = date('c');
    $entry['ip']        = $_SERVER['REMOTE_ADDR'] ?? 'cli';
    $entry['url']       = $_SERVER['REQUEST_URI'] ?? null;
    $entry['method']    = $_SERVER['REQUEST_METHOD'] ?? null;
    $entry['user']      = $_SESSION['admin_username'] ?? null;
    $entry['php_ver']   = PHP_VERSION;

    @file_put_contents(
        PANEL_ERROR_LOG_PATH,
        json_encode($entry, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . "\n",
        FILE_APPEND | LOCK_EX
    );
}

function panel_log_exception(Throwable $e, array $context = []): void
{
    panel_log_write([
        'level'   => 'exception',
        'message' => $e->getMessage(),
        'class'   => get_class($e),
        'file'    => str_replace(realpath(__DIR__ . '/..'), '', $e->getFile()),
        'line'    => $e->getLine(),
        'trace'   => array_slice(explode("\n", $e->getTraceAsString()), 0, 12),
        'context' => $context,
    ]);
}

function panel_log_php_error(int $errno, string $errstr, string $errfile, int $errline): bool
{
    // Skip silenced errors (@-suppressed).
    if (!(error_reporting() & $errno)) return false;

    $levels = [
        E_ERROR => 'error',           E_WARNING => 'warning',
        E_PARSE => 'parse',           E_NOTICE  => 'notice',
        E_USER_ERROR => 'error',      E_USER_WARNING => 'warning',
        E_USER_NOTICE => 'notice',    E_DEPRECATED  => 'deprecated',
        E_USER_DEPRECATED => 'deprecated',
        E_RECOVERABLE_ERROR => 'error',
        E_STRICT => 'notice',
    ];
    panel_log_write([
        'level'   => $levels[$errno] ?? 'unknown',
        'message' => $errstr,
        'file'    => str_replace(realpath(__DIR__ . '/..'), '', $errfile),
        'line'    => $errline,
    ]);
    return false;  // let PHP's default handler run too
}

function panel_log_shutdown(): void
{
    $e = error_get_last();
    if (!$e) return;
    // Only capture the FATAL classes (parser, fatal, OOM, etc.) — the lower
    // levels were already logged via set_error_handler.
    $fatal_mask = E_ERROR | E_PARSE | E_CORE_ERROR | E_CORE_WARNING
                | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR;
    if (!($e['type'] & $fatal_mask)) return;
    panel_log_write([
        'level'   => 'fatal',
        'message' => $e['message'],
        'file'    => str_replace(realpath(__DIR__ . '/..'), '', $e['file']),
        'line'    => $e['line'],
    ]);
}

// Register the handlers once.
if (!defined('PANEL_ERROR_HANDLER_REGISTERED')) {
    define('PANEL_ERROR_HANDLER_REGISTERED', true);
    set_error_handler('panel_log_php_error');
    set_exception_handler(function (Throwable $e) {
        panel_log_exception($e);
        // Honor PANEL_DEBUG if defined and truthy
        if (defined('PANEL_DEBUG') && PANEL_DEBUG) {
            header('Content-Type: text/plain; charset=utf-8', true, 500);
            echo "Uncaught " . get_class($e) . ": " . $e->getMessage()
               . " at " . $e->getFile() . ":" . $e->getLine();
        } else {
            // Production: clean 500 page
            if (!headers_sent()) {
                header('HTTP/1.1 500 Internal Server Error');
                header('Content-Type: text/html; charset=utf-8');
            }
            echo '<!doctype html><meta charset=utf-8><title>500</title>'
               . '<body style="background:#0a0a0a;color:#e2e8f0;font-family:system-ui;padding:60px;text-align:center;">'
               . '<h1 style="color:#f87171;font-size:48px;margin:0 0 8px">500</h1>'
               . '<p style="color:#aaa;">Something went wrong on the server. The error has been logged.</p>'
               . '<p style="color:#666;font-size:13px;">Open <code style="background:#1e1e1e;padding:2px 6px;border-radius:3px;">Settings → Error Log</code> in the panel to see the details.</p>'
               . '</body>';
        }
        exit;
    });
    register_shutdown_function('panel_log_shutdown');
}
