<?php
/**
 * lib/helpers.php — Shared Utilities: JSON I/O, HTML Escaping, CSRF, and Generators.
 */

declare(strict_types=1);

// Panel genel adı (Tek hamlede buradan değiştirebilirsiniz)
define('PANEL_NAME', 'LEGENDS PANEL');

/* ------------------------------------------------------------------ *
 *  PHP 7.4 compatibility polyfills (PHP 8.0+ string helpers)
 * ------------------------------------------------------------------ */
if (!function_exists('str_starts_with')) {
    function str_starts_with(string $haystack, string $needle): bool
    {
        return $needle === '' || strpos($haystack, $needle) === 0;
    }
}
if (!function_exists('str_ends_with')) {
    function str_ends_with(string $haystack, string $needle): bool
    {
        return $needle === '' || substr($haystack, -strlen($needle)) === $needle;
    }
}
if (!function_exists('str_contains')) {
    function str_contains(string $haystack, string $needle): bool
    {
        return $needle === '' || strpos($haystack, $needle) !== false;
    }
}

/* ------------------------------------------------------------------ *
 *  JSON Input / Output (API)
 * ------------------------------------------------------------------ */

/**
 * Emits a JSON response and terminates execution.
 *
 * @param array<string,mixed> $arr  Payload to serialize.
 * @param int                 $code HTTP response code.
 */
function json_out(array $arr, int $code = 200)
{
    http_response_code($code);
    header('Content-Type: application/json; charset=utf-8');
    header('X-Content-Type-Options: nosniff');
    echo json_encode($arr, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    exit;
}

/**
 * Reads and decodes the JSON body of the incoming request.
 * Returns an array (empty if the request body is not valid JSON).
 *
 * @return array<string,mixed>
 */
function read_json_body(): array
{
    $raw = file_get_contents('php://input');
    if ($raw === false || $raw === '') {
        return [];
    }
    $data = json_decode($raw, true);
    return is_array($data) ? $data : [];
}

/* ------------------------------------------------------------------ *
 *  HTML Escaping (Panel UI)
 * ------------------------------------------------------------------ */

/**
 * Escapes a string for safe HTML output (anti-XSS).
 */
function e(?string $value): string
{
    return htmlspecialchars($value ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

/* ------------------------------------------------------------------ *
 *  CSRF Protection (Panel Forms)
 * ------------------------------------------------------------------ */

/**
 * Retrieves (or creates if necessary) the session CSRF token.
 */
function csrf_token(): string
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

/**
 * Generates a hidden HTML input field containing the CSRF token.
 */
function csrf_field(): string
{
    return '<input type="hidden" name="csrf_token" value="' . e(csrf_token()) . '">';
}

/**
 * Validates the CSRF token received via POST. Aborts with 400 status if invalid.
 */
function csrf_check(): void
{
    if (session_status() !== PHP_SESSION_ACTIVE) {
        session_start();
    }
    $sent = $_POST['csrf_token'] ?? '';
    $real = $_SESSION['csrf_token'] ?? '';
    if (!is_string($sent) || $real === '' || !hash_equals($real, $sent)) {
        http_response_code(400);
        exit('Invalid CSRF token. Please reload the page and try again.');
    }
}

/* ------------------------------------------------------------------ *
 *  Generators
 * ------------------------------------------------------------------ */

/**
 * Generates a device_key formatted as "IBO-XXXX-XXXX" (unambiguous alphanumeric).
 */
function generate_device_key(): string
{
    return 'IBO-' . random_token(4) . '-' . random_token(4);
}

/**
 * Generates an activation code formatted as "XXXX-XXXX".
 */
function generate_activation_code(): string
{
    return random_token(4) . '-' . random_token(4);
}

/**
 * Generates a NUMERIC activation code (digits only).
 * Designed for easy remote control typing in TV apps. Defaults to 6 digits.
 */
function generate_numeric_code(int $len = 6): string
{
    $out = '';
    for ($i = 0; $i < $len; $i++) {
        $out .= (string) random_int(0, 9);
    }
    return $out;
}

/**
 * Generates a random string of length $len using an unambiguous alphabet
 * (excluding 0/O and 1/I) for high legibility.
 */
function random_token(int $len): string
{
    $alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
    $max = strlen($alphabet) - 1;
    $out = '';
    for ($i = 0; $i < $len; $i++) {
        $out .= $alphabet[random_int(0, $max)];
    }
    return $out;
}

/* ------------------------------------------------------------------ *
 *  Validation / Formatting Helpers
 * ------------------------------------------------------------------ */

/**
 * Normalizes a MAC address: uppercase with ":" delimiters. Returns null if invalid.
 * Accepts input formatted with ":", "-", or without separators.
 */
function normalize_mac(?string $mac): ?string
{
    if ($mac === null) {
        return null;
    }
    $hex = strtoupper(preg_replace('/[^0-9A-Fa-f]/', '', $mac) ?? '');
    if (strlen($hex) !== 12) {
        return null;
    }
    return implode(':', str_split($hex, 2));
}

/**
 * Converts any value to integer 0/1 (for checkboxes and boolean flags).
 *
 * @param mixed $value
 */
function to_bool_int($value): int
{
    if (is_string($value)) {
        $value = strtolower(trim($value));
        return in_array($value, ['1', 'true', 'on', 'yes'], true) ? 1 : 0;
    }
    return $value ? 1 : 0;
}

/**
 * Validates that a date matches YYYY-MM-DD format (or is empty).
 * Returns the normalized date string or null.
 */
function valid_date(?string $date): ?string
{
    if ($date === null || trim($date) === '') {
        return null;
    }
    $date = trim($date);
    $dt = DateTime::createFromFormat('Y-m-d', $date);
    if ($dt && $dt->format('Y-m-d') === $date) {
        return $date;
    }
    return null;
}

/**
 * Validates a hexadecimal color (#RGB or #RRGGBB). Returns $default if invalid.
 */
function valid_color(?string $color, string $default = '#000000'): string
{
    $color = trim((string) $color);
    if (preg_match('/^#(?:[0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/', $color)) {
        return strtolower($color);
    }
    return $default;
}

/**
 * Returns current UTC timestamp in ISO format for last_seen/created_at fields.
 */
function now_iso(): string
{
    return gmdate('Y-m-d H:i:s');
}