<?php

declare(strict_types=1);

namespace Numinix\SeekmodoWordPress\Gateway;

/**
 * Normalizes the gateway SearchTool envelope into the `{ids, total, meta}`
 * shape {@see Client::search()} callers expect.
 *
 * The REST `/v1/search` handler returns `{results: {hits, found}, meta}`
 * (mirroring Typesense). Zen Cart's connector normalizes this in
 * `_numinix_seekmodo_normalize_response()`; the WordPress connector
 * must do the same or SearchSwap always falls back to native Woo
 * category grids with no stampable product IDs.
 */
final class SearchResponse
{
    /**
     * @param array<string,mixed> $params
     * @return array<string,mixed>
     */
    public static function prepareParams(array $params): array
    {
        if (isset($params['page_size']) && !isset($params['per_page'])) {
            $params['per_page'] = max(1, min(100, (int) $params['page_size']));
            unset($params['page_size']);
        }

        // Stamp shopper context so SearchTool telemetry / bot-check /
        // session-aware fleet linkage see the visitor — not the origin.
        $ctx = ShopperContext::forGateway();
        foreach (['session_id', 'ua', 'ip', 'referer'] as $key) {
            if (!isset($params[$key]) || $params[$key] === '' || $params[$key] === null) {
                $params[$key] = $ctx[$key];
            }
        }

        return $params;
    }

    /**
     * @return array{ids: list<int>, total: int, meta: array<string,mixed>}|null
     */
    public static function normalize(?array $raw): ?array
    {
        if (!is_array($raw)) {
            return null;
        }

        $meta = is_array($raw['meta'] ?? null) ? $raw['meta'] : [];

        if (isset($raw['ids']) && is_array($raw['ids'])) {
            $ids = self::intList($raw['ids']);

            return [
                'ids'   => $ids,
                'total' => (int) ($raw['total'] ?? count($ids)),
                'meta'  => $meta,
            ];
        }

        if (isset($raw['products']) && is_array($raw['products'])) {
            $ids = self::intList($raw['products']);

            return [
                'ids'   => $ids,
                'total' => (int) ($raw['total'] ?? count($ids)),
                'meta'  => $meta,
            ];
        }

        $results = is_array($raw['results'] ?? null) ? $raw['results'] : null;
        if ($results === null) {
            return null;
        }

        $hits = is_array($results['hits'] ?? null) ? $results['hits'] : [];
        $ids  = self::idsFromHits($hits);
        $total = isset($results['found'])
            ? (int) $results['found']
            : (isset($results['total']) ? (int) $results['total'] : count($ids));

        return [
            'ids'   => $ids,
            'total' => $total,
            'meta'  => $meta,
        ];
    }

    /**
     * @param list<mixed> $hits
     * @return list<int>
     */
    private static function idsFromHits(array $hits): array
    {
        $ids = [];
        foreach ($hits as $hit) {
            if (!is_array($hit)) {
                continue;
            }
            $doc = is_array($hit['document'] ?? null) ? $hit['document'] : $hit;
            $id  = $doc['id'] ?? $doc['products_id'] ?? $doc['object_id'] ?? null;
            if ($id === null) {
                continue;
            }
            $intId = (int) $id;
            if ($intId > 0) {
                $ids[] = $intId;
            }
        }

        return $ids;
    }

    /**
     * @param list<mixed> $values
     * @return list<int>
     */
    private static function intList(array $values): array
    {
        $out = [];
        foreach ($values as $v) {
            $int = (int) $v;
            if ($int > 0) {
                $out[] = $int;
            }
        }

        return $out;
    }
}
