<?php

namespace Shm\ShmUtils;

use Manticoresearch\Client;
use Shm\ShmDB\mDB;

class ShmManticore
{
    private static ?Client $client = null;
    private static ?bool $available = null;
    private const TABLE_NAME = 'shm_search';


    public static function getPipeline(string $collection, string $search): array
    {
        if (ShmManticore::isAvailable()) {


            $m_ids = ShmManticore::findItems($collection, $search);


            return [
                '$match' => [
                    '_id' => ['$in' => $m_ids],
                ],
            ];
        }


        return [
            '$match' => [
                'search_string' => ['$regex' => mb_strtolower(trim($search)), '$options' => 'i'],
            ],
        ];
    }

    private static function init(): void
    {
        if (self::$available !== null) {
            return;
        }

        try {
            self::$client = new Client([
                'host' => '127.0.0.1',
                'port' => 9308,
            ]);
            self::$client->table(self::TABLE_NAME)->create([
                'collection' => ['type' => 'string'],
                'item_id' => ['type' => 'string'],
                'search_string' => ['type' => 'text'],
            ], [
                'min_infix_len' => 2,
            ], true);
            self::$available = true;
        } catch (\Throwable) {
            self::$client = null;
            self::$available = false;
        }
    }

    public static function isAvailable(): bool
    {
        self::init();
        return self::$available;
    }

    public static function dropTable(): void
    {
        self::init();
        if (!self::$available) {
            return;
        }

        try {
            self::$client->table(self::TABLE_NAME)->drop(true);
            self::$available = null;
        } catch (\Throwable) {
        }
    }

    private static function docId(string $collection, $_id): int
    {
        $_id = (string)$_id;
        return intval(hexdec(substr(md5($collection . ':' . $_id), 0, 15)));
    }

    public static function addItem(string $collection,  $_id, string $search_string): void
    {

        $_id = (string)$_id;
        self::init();
        if (!self::$available) {
            return;
        }

        try {
            self::$client->replace([
                'body' => [
                    'table' => self::TABLE_NAME,
                    'id' => self::docId($collection, $_id),
                    'doc' => [
                        'collection' => $collection,
                        'item_id' => $_id,
                        'search_string' => $search_string,
                    ],
                ],
            ]);
        } catch (\Throwable) {
        }
    }

    public static function removeItem(string $collection, string $_id): void
    {
        self::init();
        if (!self::$available) {
            return;
        }

        try {
            self::$client->delete([
                'body' => [
                    'table' => self::TABLE_NAME,
                    'id' => self::docId($collection, $_id),
                ],
            ]);
        } catch (\Throwable) {
        }
    }

    /**
     * @return array|null — массив _id если Manticore доступен, null если нет (фоллбэк на MongoDB)
     */
    public static function findItems(string $collection, string $search_string, int $limit = 100): ?array
    {
        self::init();
        if (!self::$available) {
            return null;
        }

        try {
            $words = preg_split('/\s+/', trim($search_string), -1, PREG_SPLIT_NO_EMPTY);
            $query = implode(' ', array_map(fn($w) => '*' . $w . '*', $words));

            $result = self::$client->table(self::TABLE_NAME)
                ->search($query)
                ->filter('collection', $collection)
                ->limit($limit)
                ->get();

            $ids = [];
            foreach ($result as $hit) {
                $ids[] = mDB::id($hit->getData()['item_id']);
            }

            return $ids;
        } catch (\Throwable) {
            return null;
        }
    }
}