<?php











function easychat_runtime_reader_safe_call($callback, $fallback = false)
{
    set_error_handler(function () {
        return true;
    });
    try {
        return call_user_func($callback);
    } catch (\Throwable $e) {
        return $fallback;
    } finally {
        restore_error_handler();
    }
}

function easychat_runtime_reader_is_same_private_file($path, $handle, $require_private = true)
{
    clearstatcache(true, $path);
    $path_stat = easychat_runtime_reader_safe_call(function () use ($path) {
        return lstat($path);
    }, false);
    $handle_stat = easychat_runtime_reader_safe_call(function () use ($handle) {
        return fstat($handle);
    }, false);

    return is_array($path_stat)
        && is_array($handle_stat)
        && (($path_stat['mode'] ?? 0) & 0170000) === 0100000
        && (($handle_stat['mode'] ?? 0) & 0170000) === 0100000
        && (!$require_private || ((($path_stat['mode'] ?? 0) & 0077) === 0
            && (($handle_stat['mode'] ?? 0) & 0077) === 0))
        && (($path_stat['mode'] ?? 0) & 0022) === 0
        && (($handle_stat['mode'] ?? 0) & 0022) === 0
        && (int)($path_stat['nlink'] ?? 0) === 1
        && (int)($handle_stat['nlink'] ?? 0) === 1
        && ($path_stat['dev'] ?? null) === ($handle_stat['dev'] ?? null)
        && ($path_stat['ino'] ?? null) === ($handle_stat['ino'] ?? null);
}





function easychat_runtime_reader_private_json($path)
{
    if (!file_exists($path) && !is_link($path)) {
        return null;
    }
    if (is_link($path) || is_dir($path)) {
        return false;
    }

    $handle = easychat_runtime_reader_safe_call(function () use ($path) {
        return fopen($path, 'rb');
    }, false);
    if (!is_resource($handle) || !easychat_runtime_reader_is_same_private_file($path, $handle)) {
        if (is_resource($handle)) {
            fclose($handle);
        }
        return false;
    }

    $contents = easychat_runtime_reader_safe_call(function () use ($handle) {
        return stream_get_contents($handle, 4097);
    }, false);
    $still_same = easychat_runtime_reader_is_same_private_file($path, $handle);
    fclose($handle);
    if (!is_string($contents) || strlen($contents) > 4096 || !$still_same) {
        return false;
    }

    $decoded = json_decode($contents, true);
    return is_array($decoded) ? $decoded : false;
}

function easychat_runtime_reader_intent_payload_is_valid($payload)
{
    return is_array($payload)
        && (int)($payload['format'] ?? 0) === 1
        && is_string($payload['nonce'] ?? null)
        && preg_match('/^[a-f0-9]{32}$/', (string)$payload['nonce']) === 1
        && (int)($payload['created_at'] ?? 0) > 0
        && (int)($payload['heartbeat_at'] ?? 0) >= (int)$payload['created_at'];
}

function easychat_runtime_reader_intent_is_stale(array $payload)
{
    $heartbeat_at = (int)$payload['heartbeat_at'];
     
     
    if ($heartbeat_at > time() + 300) {
        return false;
    }
    return (time() - $heartbeat_at) > 120;
}






function easychat_runtime_reader_open_guard($path, $operation)
{
    if (is_link($path) || is_dir($path)) {
        return false;
    }

    $handle = false;
    if (!file_exists($path)) {
         
         
        $handle = easychat_runtime_reader_safe_call(function () use ($path) {
            $previous_umask = umask(0077);
            try {
                return fopen($path, 'x+');
            } finally {
                umask($previous_umask);
            }
        }, false);
    }
    if (!is_resource($handle) && !is_link($path) && !is_dir($path)) {
         
         
        $handle = easychat_runtime_reader_safe_call(function () use ($path) {
            return fopen($path, 'r+');
        }, false);
    }
    if (!is_resource($handle)) {
        return false;
    }
    $locked = easychat_runtime_reader_is_same_private_file($path, $handle)
        && (bool)easychat_runtime_reader_safe_call(function () use ($handle, $operation) {
            return flock($handle, $operation | LOCK_NB);
        }, false)
        && easychat_runtime_reader_is_same_private_file($path, $handle);
    if (!$locked) {
        fclose($handle);
        return false;
    }
    return $handle;
}





function easychat_runtime_reader_has_blocking_intent($log_dir)
{
    $intent_path = $log_dir . DIRECTORY_SEPARATOR . 'easychat-publish.intent';
    $payload = easychat_runtime_reader_private_json($intent_path);
    if ($payload === null) {
        return false;
    }
    if (!easychat_runtime_reader_intent_payload_is_valid($payload)) {
        return true;
    }
    if (!easychat_runtime_reader_intent_is_stale($payload)) {
        return true;
    }

    $guard_path = $log_dir . DIRECTORY_SEPARATOR . 'easychat-publish.intent.lock';
    $guard = easychat_runtime_reader_open_guard($guard_path, LOCK_EX);
    if (!is_resource($guard)) {
        return true;
    }

    try {
        $current = easychat_runtime_reader_private_json($intent_path);
        if ($current === null) {
            return false;
        }
        if (!easychat_runtime_reader_intent_payload_is_valid($current)
            || !easychat_runtime_reader_intent_is_stale($current)
        ) {
            return true;
        }
        $removed = (bool)easychat_runtime_reader_safe_call(function () use ($intent_path) {
            return unlink($intent_path);
        }, false);
        clearstatcache(true, $intent_path);
        return !$removed || file_exists($intent_path) || is_link($intent_path);
    } finally {
        flock($guard, LOCK_UN);
        fclose($guard);
    }
}

function easychat_runtime_reader_has_journal($log_dir)
{
    $state_path = $log_dir . DIRECTORY_SEPARATOR . 'easychat-publish-state.json';
    return file_exists($state_path) || is_link($state_path);
}

function easychat_runtime_reader_marker_revision($path)
{
    $payload = easychat_runtime_reader_private_json($path);
    if ($payload === null) {
        return false;
    }
    if (!is_array($payload)
        || (int)($payload['format'] ?? 0) !== 1
        || !is_string($payload['publish_revision'] ?? null)
        || preg_match('/^[1-9][0-9]*$/', (string)$payload['publish_revision']) !== 1
        || (int)$payload['publish_revision'] < 3
    ) {
        return null;
    }
    return (int)$payload['publish_revision'];
}

function easychat_runtime_reader_temporary_failure($web_response)
{
    if ($web_response) {
        http_response_code(503);
        header('Retry-After: 5');
        header('Cache-Control: no-store, no-cache, must-revalidate');
        exit('EasyChat временно завершает безопасное обновление. Повторите запрос через несколько секунд.');
    }
    exit(75);  
}








function easychat_runtime_reader_lock_acquire($runtime_root, $web_response)
{
    $runtime_root = rtrim((string)$runtime_root, '/\\');
    if ($runtime_root === '') {
        easychat_runtime_reader_temporary_failure($web_response);
    }

    $log_dir = $runtime_root . DIRECTORY_SEPARATOR . 'log';
    $config_path = $runtime_root . DIRECTORY_SEPARATOR . 'config.php';
    $durable_revision = easychat_runtime_reader_marker_revision(
        $runtime_root . DIRECTORY_SEPARATOR . '.easychat-runtime-publisher.json'
    );
    if ($durable_revision === null) {
        easychat_runtime_reader_temporary_failure($web_response);
    }
    if (!is_dir($log_dir) || is_link($log_dir)) {
        if ($durable_revision !== false) {
            easychat_runtime_reader_temporary_failure($web_response);
        }
        return null;
    }

     
     
    if (easychat_runtime_reader_has_blocking_intent($log_dir)
        || easychat_runtime_reader_has_journal($log_dir)
    ) {
        easychat_runtime_reader_temporary_failure($web_response);
    }

    $log_revision = easychat_runtime_reader_marker_revision(
        $log_dir . DIRECTORY_SEPARATOR . 'easychat-publish-managed.json'
    );
    if ($log_revision === null) {
        easychat_runtime_reader_temporary_failure($web_response);
    }
     
     
    if (($durable_revision === false) !== ($log_revision === false)
        || ($durable_revision !== false && $durable_revision !== $log_revision)
    ) {
        easychat_runtime_reader_temporary_failure($web_response);
    }
    if ($durable_revision === false) {
        return null;
    }

    $lock_path = $log_dir . DIRECTORY_SEPARATOR . 'easychat-publish.lock';
    $lock = easychat_runtime_reader_open_guard($lock_path, LOCK_SH);
    if (!is_resource($lock)) {
        easychat_runtime_reader_temporary_failure($web_response);
    }

     
     
    if (easychat_runtime_reader_has_blocking_intent($log_dir)
        || easychat_runtime_reader_has_journal($log_dir)
    ) {
        flock($lock, LOCK_UN);
        fclose($lock);
        easychat_runtime_reader_temporary_failure($web_response);
    }

    $GLOBALS['easychat_runtime_reader_lock_context'] = array(
        'runtime_root' => $runtime_root,
        'lock' => $lock,
    );
    register_shutdown_function(function () use ($lock) {
        if (is_resource($lock)) {
            flock($lock, LOCK_UN);
            fclose($lock);
        }
    });

    return $lock;
}






function easychat_runtime_reader_lock_release()
{
    $context = $GLOBALS['easychat_runtime_reader_lock_context'] ?? null;
    if (!is_array($context) || !is_resource($context['lock'] ?? null)) {
        return true;
    }

    $released = flock($context['lock'], LOCK_UN);
    fclose($context['lock']);
    unset($GLOBALS['easychat_runtime_reader_lock_context']);

    return (bool)$released;
}





function easychat_runtime_reader_lock_checkpoint($before_exit = null)
{
    $context = $GLOBALS['easychat_runtime_reader_lock_context'] ?? null;
    if (!is_array($context) || !is_resource($context['lock'] ?? null)) {
        return true;
    }
    $log_dir = rtrim((string)$context['runtime_root'], '/\\') . DIRECTORY_SEPARATOR . 'log';
    if (!easychat_runtime_reader_has_blocking_intent($log_dir)) {
        return true;
    }
    if (is_callable($before_exit)) {
        easychat_runtime_reader_safe_call($before_exit, false);
    }
    easychat_runtime_reader_lock_release();
    exit(75);
}
