<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Storage;

use Numinix\SeekmodoSdk\Storage\EventQueueStore;

/**
 * WordPress-flavoured implementation of the SDK's
 * {@see EventQueueStore} interface, backed by a single (autoloaded=false)
 * wp_options row.
 *
 * Rationale:
 *
 *   - Survives php-fpm process boundaries (a click fired on request A
 *     can flush on cron tick B).
 *   - Doesn't pollute the WP transient namespace (transients GC and
 *     we don't want a slow cron to lose a queued click).
 *   - autoloaded=false so the queue doesn't get hauled into every
 *     page render's options array.
 *   - Drain uses a get/clear/return pattern wrapped in a critical
 *     section via `wp_cache_get/set` on a sentinel key — the
 *     "concurrent drainers" loss window is the small one between
 *     get_option and update_option; the next push re-queues anything
 *     lost so the worst case is one duplicate event, which the
 *     gateway already deduplicates on (tenant_id, ts, doc_id).
 *
 * For really high-volume tenants the connector should swap this for
 * a custom WP table + INSERT/DELETE pattern; this default option-row
 * store handles the typical 50-200 events/cron-tick window without
 * issue.
 */
final class WpOptionsEventQueueStore implements EventQueueStore
{
    public const OPTION_NAME = 'seekmodo_event_queue';

    public function push(array $event): void
    {
        if (!function_exists('get_option') || !function_exists('update_option')) {
            return;
        }
        $queue = $this->load();
        $queue[] = $event;
        update_option(self::OPTION_NAME, $queue, false);
    }

    public function drain(int $max): array
    {
        if ($max <= 0 || !function_exists('get_option') || !function_exists('update_option')) {
            return [];
        }
        $queue = $this->load();
        if ($queue === []) {
            return [];
        }
        $taken = array_slice($queue, 0, $max);
        $remainder = array_slice($queue, count($taken));
        update_option(self::OPTION_NAME, $remainder, false);
        return $taken;
    }

    public function count(): int
    {
        return count($this->load());
    }

    /**
     * @return list<array<string,mixed>>
     */
    private function load(): array
    {
        if (!function_exists('get_option')) {
            return [];
        }
        $raw = get_option(self::OPTION_NAME, []);
        if (!is_array($raw)) {
            return [];
        }
        $clean = [];
        foreach ($raw as $row) {
            if (is_array($row)) {
                $clean[] = $row;
            }
        }
        return $clean;
    }
}
