<?php
namespace EasyChat\Library\Catalog;








class CatalogSearchService {

    const POLICY_SMART = 'smart';
    const POLICY_IN_STOCK = 'in_stock';
    const POLICY_ALL_ACTIVE = 'all_active';

    const INTENT_LOOKUP = 'lookup';
    const INTENT_RECOMMENDATION = 'recommendation';

    const AVAILABILITY_IN_STOCK = 'in_stock';
    const AVAILABILITY_PREORDER = 'preorder';
    const AVAILABILITY_OUT_OF_STOCK = 'out_of_stock';
    const AVAILABILITY_UNKNOWN = 'unknown';

     
    private $cms;

     
    private $settings;

     
    private $public_url_service;

    public function __construct($cms, array $settings = array(), $public_url_service = null) {
        $this->cms = $cms;
        $this->settings = $settings;
        $this->public_url_service = $public_url_service;
    }

    


    public function search(string $query, float $max_price = 0.0, string $intent = self::INTENT_LOOKUP): array {
        $query = $this->normalizeQuery($query);
        if ($query === '') {
            return array();
        }

        $intent = $this->normalizeIntent($intent);
        $limit = $this->searchLimit();
        $candidate_limit = min(100, max(20, $limit * 4));
        $rows = $this->cms->searchProducts($query, max(0.0, $max_price), array(
            'intent' => $intent,
            'candidate_limit' => $candidate_limit,
        ));
        $rows = $this->enrichPublicUrls($rows, 'product');

        $products = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $product = $this->normalizeProduct($row, $query);
            if ($product === null || !$this->policyAllows($product, $intent)) {
                continue;
            }
            $products[] = $product;
        }

        usort($products, function (array $left, array $right): int {
            $score = (int)$right['_score'] <=> (int)$left['_score'];
            if ($score !== 0) {
                return $score;
            }
            return (string)$left['name'] <=> (string)$right['name'];
        });

        $products = array_slice($products, 0, $limit);
        foreach ($products as &$product) {
            unset($product['_score'], $product['relevance']);
            if (!$this->showExactQuantity()) {
                unset($product['quantity']);
            }
        }
        unset($product);

        return $products;
    }

     
    public function searchPages(string $query): array {
        $query = $this->normalizeQuery($query);
        if ($query === '') {
            return array();
        }
        $rows = $this->cms->searchPages($query);
        $rows = $this->enrichPublicUrls(is_array($rows) ? $rows : array(), 'page');
        $pages = array();
        foreach ($rows as $row) {
            if (!is_array($row)) {
                continue;
            }
            $name = $this->plainText((string)($row['name'] ?? $row['title'] ?? ''), 300);
            $link = trim((string)($row['link'] ?? ''));
            if ($name === '' || !$this->isSafeLink($link)) {
                continue;
            }
            $pages[] = array(
                'id' => mb_substr(trim((string)($row['id'] ?? '')), 0, 64, 'UTF-8'),
                'name' => $name,
                'excerpt' => $this->plainText((string)($row['excerpt'] ?? ''), 300),
                'link' => $link,
                'link_label' => $this->plainText((string)($row['link_label'] ?? $name), 300),
            );
        }
        return array_slice($pages, 0, $this->searchLimit());
    }

    private function normalizeProduct(array $row, string $query) {
        $name = $this->plainText((string)($row['name'] ?? ''), 300);
        $link = trim((string)($row['link'] ?? ''));
        if ($name === '' || !$this->isSafeLink($link)) {
            return null;
        }

        $availability = strtolower(trim((string)($row['availability'] ?? self::AVAILABILITY_UNKNOWN)));
        if (!in_array($availability, array(
            self::AVAILABILITY_IN_STOCK,
            self::AVAILABILITY_PREORDER,
            self::AVAILABILITY_OUT_OF_STOCK,
            self::AVAILABILITY_UNKNOWN,
        ), true)) {
            $availability = self::AVAILABILITY_UNKNOWN;
        }

        $match_kind = $this->matchKind(
            $query,
            $name,
            (string)($row['model'] ?? ''),
            (string)($row['sku'] ?? '')
        );
        $can_order = array_key_exists('can_order', $row)
            ? (bool)$row['can_order']
            : in_array($availability, array(self::AVAILABILITY_IN_STOCK, self::AVAILABILITY_PREORDER), true);

         
         
        $availability_label = $this->plainText((string)($row['availability_label'] ?? ''), 120);
        if ($availability_label === '') {
            $availability_label = $this->defaultAvailabilityLabel($availability);
        }

        $product = array(
            'id' => mb_substr(trim((string)($row['id'] ?? $row['product_id'] ?? '')), 0, 64, 'UTF-8'),
            'name' => $name,
            'model' => $this->plainText((string)($row['model'] ?? ''), 160),
            'sku' => $this->plainText((string)($row['sku'] ?? ''), 160),
            'price' => $this->plainText((string)($row['price'] ?? ''), 100),
            'price_value' => is_numeric($row['price_value'] ?? null) ? (float)$row['price_value'] : null,
            'currency' => preg_match('/^[A-Z]{3}$/', strtoupper(trim((string)($row['currency'] ?? ''))))
                ? strtoupper(trim((string)$row['currency']))
                : '',
            'link' => $link,
            'link_label' => $this->plainText((string)($row['link_label'] ?? $name), 300),
            'availability' => $availability,
            'availability_label' => $availability_label,
            'can_order' => $can_order,
            'match_kind' => $match_kind,
        );
        if (array_key_exists('quantity', $row) && $row['quantity'] !== null && $row['quantity'] !== '') {
            $product['quantity'] = (float)$row['quantity'];
        }
        $product['_score'] = $this->matchWeight($match_kind)
            + $this->availabilityWeight($availability)
            + min(50, max(0, (int)($row['relevance'] ?? 0)));

        return $product;
    }

    private function policyAllows(array $product, string $intent): bool {
        $policy = $this->availabilityPolicy();
        if ($policy === self::POLICY_ALL_ACTIVE) {
            return true;
        }
        if ($policy === self::POLICY_IN_STOCK) {
            return $product['availability'] === self::AVAILABILITY_IN_STOCK;
        }

         
         
         
         
        if (!empty($product['can_order'])) {
            return true;
        }
        return $intent === self::INTENT_LOOKUP
            && in_array($product['match_kind'], array('exact_identifier', 'exact_name'), true);
    }

    private function matchKind(string $query, string $name, string $model, string $sku): string {
        $query_identifier = $this->normalizeIdentifier($query);
        foreach (array($sku, $model) as $identifier) {
            $identifier = $this->normalizeIdentifier($identifier);
            if ($identifier !== '' && mb_strlen($identifier, 'UTF-8') >= 3
                && ($query_identifier === $identifier || strpos($query_identifier, $identifier) !== false)) {
                return 'exact_identifier';
            }
        }

        $query_text = $this->normalizeText($query);
        $name_text = $this->normalizeText($name);
        if ($name_text !== '' && ($query_text === $name_text || strpos($query_text, $name_text) !== false)) {
            return 'exact_name';
        }
        return 'broad';
    }

    private function normalizeQuery(string $query): string {
        $query = preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $query);
        $query = preg_replace('/\s+/u', ' ', trim((string)$query));
        return mb_substr($query, 0, 160, 'UTF-8');
    }

    private function normalizeText(string $value): string {
        $value = mb_strtolower(trim($value), 'UTF-8');
        return preg_replace('/\s+/u', ' ', $value);
    }

    private function normalizeIdentifier(string $value): string {
        $value = mb_strtolower(trim($value), 'UTF-8');
        $value = str_replace('\\', '', $value);
        return (string)preg_replace('/[\s\-_.\/]+/u', '', $value);
    }

    private function plainText(string $value, int $limit): string {
        $value = html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $value = preg_replace('/[\x00-\x1F\x7F]+/u', ' ', $value);
        $value = preg_replace('/\s+/u', ' ', trim((string)$value));
        return mb_substr($value, 0, $limit, 'UTF-8');
    }

    private function isSafeLink(string $link): bool {
        return CatalogUrlPolicy::isSafeLink($link);
    }

    private function enrichPublicUrls(array $rows, string $resource_type): array {
        if ($this->public_url_service && method_exists($this->public_url_service, 'enrich')) {
            return $this->public_url_service->enrich($rows, $resource_type);
        }
        if (method_exists($this->cms, 'resolvePublicUrls')) {
            try {
                $resolved = $this->cms->resolvePublicUrls($rows, $resource_type);
                return is_array($resolved) ? $resolved : $rows;
            } catch (\Throwable $e) {
                return $rows;
            }
        }
        return $rows;
    }

    private function normalizeIntent(string $intent): string {
        return $intent === self::INTENT_RECOMMENDATION
            ? self::INTENT_RECOMMENDATION
            : self::INTENT_LOOKUP;
    }

    private function availabilityPolicy(): string {
        $policy = strtolower(trim((string)($this->settings['chat_cms_availability_policy'] ?? self::POLICY_SMART)));
        return in_array($policy, array(self::POLICY_SMART, self::POLICY_IN_STOCK, self::POLICY_ALL_ACTIVE), true)
            ? $policy
            : self::POLICY_SMART;
    }

    private function searchLimit(): int {
        return max(1, min(50, (int)($this->settings['chat_cms_search_limit'] ?? 5)));
    }

    private function showExactQuantity(): bool {
        return (string)($this->settings['chat_cms_show_exact_quantity'] ?? '0') === '1';
    }

    private function matchWeight(string $match_kind): int {
        if ($match_kind === 'exact_identifier') {
            return 400;
        }
        if ($match_kind === 'exact_name') {
            return 300;
        }
        return 100;
    }

    private function availabilityWeight(string $availability): int {
        if ($availability === self::AVAILABILITY_IN_STOCK) {
            return 30;
        }
        if ($availability === self::AVAILABILITY_PREORDER) {
            return 20;
        }
        if ($availability === self::AVAILABILITY_UNKNOWN) {
            return 5;
        }
        return 0;
    }

    private function defaultAvailabilityLabel(string $availability): string {
        if ($availability === self::AVAILABILITY_IN_STOCK) {
            return 'В наличии';
        }
        if ($availability === self::AVAILABILITY_PREORDER) {
            return 'Доступно под заказ';
        }
        if ($availability === self::AVAILABILITY_OUT_OF_STOCK) {
            return 'Нет в наличии';
        }
        return 'Наличие уточняется';
    }
}
