<?php
namespace EasyChat\Model\Cms;

use EasyChat\Library\Catalog\CatalogSearchUnavailableException;

class CmsModelBitrix extends CmsModelBase {

    


    public function getProducts(): array {
        $p      = $this->prefix;
        $result = $this->db->query(
            "SELECT el.ID AS id, el.NAME AS name
             FROM `{$p}iblock_element` el
             INNER JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
             WHERE el.ACTIVE = 'Y'
             ORDER BY el.ID ASC"
        );
        return $result->rows ?? [];
    }

    public function getProductsBatch(int $after_id, int $limit): array {
        $after_id = max(0, $after_id);
        $limit = max(1, min(100, $limit));
        $p = $this->prefix;
        $result = $this->db->query(
            "SELECT el.ID AS id, el.NAME AS name
             FROM `{$p}iblock_element` el
             INNER JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
             LEFT JOIN `{$p}iblock_property` link_prop
               ON link_prop.IBLOCK_ID = el.IBLOCK_ID AND link_prop.CODE = 'CML2_LINK'
             LEFT JOIN `{$p}iblock_element_property` link_value
               ON link_value.IBLOCK_ELEMENT_ID = el.ID AND link_value.IBLOCK_PROPERTY_ID = link_prop.ID
             LEFT JOIN `{$p}iblock_element` pel ON link_value.VALUE = pel.ID
             LEFT JOIN `{$p}iblock` ib ON COALESCE(pel.IBLOCK_ID, el.IBLOCK_ID) = ib.ID
             LEFT JOIN `{$p}iblock_section` sec
               ON COALESCE(pel.IBLOCK_SECTION_ID, el.IBLOCK_SECTION_ID) = sec.ID
             WHERE el.ACTIVE = 'Y' AND el.ID > {$after_id}
               AND (el.ACTIVE_FROM IS NULL OR el.ACTIVE_FROM <= NOW())
               AND (el.ACTIVE_TO IS NULL OR el.ACTIVE_TO >= NOW())
               AND (pel.ID IS NULL OR (
                   pel.ACTIVE = 'Y'
                   AND (pel.ACTIVE_FROM IS NULL OR pel.ACTIVE_FROM <= NOW())
                   AND (pel.ACTIVE_TO IS NULL OR pel.ACTIVE_TO >= NOW())
               ))
               AND ib.ACTIVE = 'Y'
               AND (sec.ID IS NULL OR sec.ACTIVE = 'Y')
             ORDER BY el.ID ASC LIMIT {$limit}"
        );
        return $result->rows ?? array();
    }

    


    public function getProductText(int $product_id): string {
        $parts = [];
        $p     = $this->prefix;

        $result  = $this->db->query(
            "SELECT COALESCE(NULLIF(pel.NAME, ''), el.NAME) AS name,
                    COALESCE(NULLIF(el.DETAIL_TEXT, ''), NULLIF(el.PREVIEW_TEXT, ''),
                             NULLIF(pel.DETAIL_TEXT, ''), pel.PREVIEW_TEXT, '') AS description
             FROM `{$p}iblock_element` el
             INNER JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
             LEFT JOIN `{$p}iblock_property` link_prop
               ON link_prop.IBLOCK_ID = el.IBLOCK_ID AND link_prop.CODE = 'CML2_LINK'
             LEFT JOIN `{$p}iblock_element_property` link_value
               ON link_value.IBLOCK_ELEMENT_ID = el.ID AND link_value.IBLOCK_PROPERTY_ID = link_prop.ID
             LEFT JOIN `{$p}iblock_element` pel ON link_value.VALUE = pel.ID
             LEFT JOIN `{$p}iblock` ib ON COALESCE(pel.IBLOCK_ID, el.IBLOCK_ID) = ib.ID
             LEFT JOIN `{$p}iblock_section` sec
               ON COALESCE(pel.IBLOCK_SECTION_ID, el.IBLOCK_SECTION_ID) = sec.ID
             WHERE el.ID = $product_id AND el.ACTIVE = 'Y'
               AND (el.ACTIVE_FROM IS NULL OR el.ACTIVE_FROM <= NOW())
               AND (el.ACTIVE_TO IS NULL OR el.ACTIVE_TO >= NOW())
               AND (pel.ID IS NULL OR (
                   pel.ACTIVE = 'Y'
                   AND (pel.ACTIVE_FROM IS NULL OR pel.ACTIVE_FROM <= NOW())
                   AND (pel.ACTIVE_TO IS NULL OR pel.ACTIVE_TO >= NOW())
               ))
               AND ib.ACTIVE = 'Y'
               AND (sec.ID IS NULL OR sec.ACTIVE = 'Y')
             LIMIT 1"
        );
        $product = $result->row ?? [];

        if (empty($product)) {
            return '';
        }

        if ( $this->settings['chat_embedding_use_name'] ?? false) {
            if (!empty($product['name'])) 
                $parts[] = $product['name'];
        }

        if ( $this->settings['chat_embedding_use_desc'] ?? false) {
            if (!empty($product['description'])) 
                $parts[] = strip_tags($product['description']);
        }

        if ( $this->settings['chat_embedding_use_model'] ?? false ) {
            $model = $this->getPropertyValue($product_id, $p, ['MODEL', 'CML2_MANUFACTURER_ARTICLE']);
            if ($model !== '') 
                $parts[] = 'Модель: ' . $model;
        }

        if ( $this->settings['chat_embedding_use_sku'] ?? false ) {
            $sku = $this->getPropertyValue($product_id, $p, ['ARTICLE', 'CML2_ARTICLE', 'SKU']);
            if ($sku !== '') 
                $parts[] = 'Артикул: ' . $sku;
        }

        if ( $this->settings['chat_embedding_use_attrs'] ?? false ) {
            $attrs = $this->getAllProperties($product_id, $p);
            if (!empty($attrs)) 
                $parts[] = 'Характеристики: ' . implode(', ', $attrs);
        }

        if ( $this->settings['chat_embedding_use_options'] ?? false) {
            $offers = $this->getOfferNames($product_id, $p);
            if (!empty($offers)) 
                $parts[] = 'Торговые предложения: ' . implode('; ', $offers);
        }

        return implode('. ', array_filter(array_map('trim', $parts)));
    }

    


    public function getPages(): array {
        $p      = $this->prefix;
        $result = $this->db->query(
            "SELECT el.ID AS id, el.NAME AS name
             FROM `{$p}iblock_element` el
             LEFT JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
             WHERE el.ACTIVE = 'Y' AND cp.ID IS NULL
             ORDER BY el.ID ASC"
        );
        return $result->rows ?? [];
    }

    public function getPagesBatch(int $after_id, int $limit): array {
        $after_id = max(0, $after_id);
        $limit = max(1, min(100, $limit));
        $p = $this->prefix;
        $result = $this->db->query(
            "SELECT el.ID AS id, el.NAME AS name
             FROM `{$p}iblock_element` el
             LEFT JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
             WHERE el.ACTIVE = 'Y' AND cp.ID IS NULL AND el.ID > {$after_id}
               AND (el.ACTIVE_FROM IS NULL OR el.ACTIVE_FROM <= NOW())
               AND (el.ACTIVE_TO IS NULL OR el.ACTIVE_TO >= NOW())
             ORDER BY el.ID ASC LIMIT {$limit}"
        );
        return $result->rows ?? array();
    }

    


    public function getPageText(int $page_id): string {
        $p      = $this->prefix;
        $result = $this->db->query(
            "SELECT el.NAME AS name,
                    COALESCE(NULLIF(el.DETAIL_TEXT, ''), el.PREVIEW_TEXT, '') AS description
             FROM `{$p}iblock_element` el
             WHERE el.ID = $page_id
             LIMIT 1"
        );
        $page = $result->row ?? [];

        $parts = [];
        if (!empty($page['name']))        $parts[] = $page['name'];
        if (!empty($page['description'])) $parts[] = strip_tags($page['description']);

        return implode('. ', array_filter(array_map('trim', $parts)));
    }

    



    public function searchProducts(string $query, float $max_price = 0.0, array $options = array()): array {
        $p     = $this->prefix;
        $words = $this->splitWords($query);
        $limit = max(1, min(200, (int)($options['candidate_limit'] ?? $this->getSearchLimit()) * 2));

        $likes     = [];
        $relevance = [];
        foreach ($words as $word) {
            $esc         = $this->escapeLikeWord($word);
            $likes[] = "(LOWER(el.NAME) LIKE '%{$esc}%'
                        OR LOWER(COALESCE(pel.NAME, '')) LIKE '%{$esc}%'
                        OR LOWER(COALESCE(identifier_value.VALUE, '')) LIKE '%{$esc}%'
                        OR LOWER(COALESCE(parent_identifier_value.VALUE, '')) LIKE '%{$esc}%')";
            $relevance[] = "(CASE
                WHEN LOWER(COALESCE(identifier_value.VALUE, '')) = '{$esc}'
                  OR LOWER(COALESCE(parent_identifier_value.VALUE, '')) = '{$esc}' THEN 12
                WHEN LOWER(el.NAME) LIKE '%{$esc}%'
                  OR LOWER(COALESCE(pel.NAME, '')) LIKE '%{$esc}%' THEN 4
                WHEN LOWER(COALESCE(identifier_value.VALUE, '')) LIKE '%{$esc}%'
                  OR LOWER(COALESCE(parent_identifier_value.VALUE, '')) LIKE '%{$esc}%' THEN 6
                ELSE 0 END)";
        }

        $rel_expr = implode(' + ', $relevance);
        $compact = mb_substr($this->normalizeSearchIdentifier($query), 0, 120, 'UTF-8');
        $compact_e = $this->db->escape($compact);
        $compact_identifier = $this->compactSearchIdentifierSql('identifier_value.VALUE');
        $compact_parent_identifier = $this->compactSearchIdentifierSql('parent_identifier_value.VALUE');
        $compact_name = $this->compactSearchIdentifierSql('el.NAME');
        $compact_parent_name = $this->compactSearchIdentifierSql('pel.NAME');
        $identifier_cond = $compact_e !== ''
            ? "({$compact_identifier} = '{$compact_e}'"
                . " OR {$compact_parent_identifier} = '{$compact_e}'"
                . " OR {$compact_name} LIKE '%{$compact_e}%'"
                . " OR {$compact_parent_name} LIKE '%{$compact_e}%')"
            : '0';

        $make_sql = function (string $op) use ($p, $likes, $rel_expr, $limit, $max_price, $identifier_cond): string {
            $cond = implode(" {$op} ", $likes);
            $price_cond = !$max_price ? '1' : 'pr.PRICE <= ' . (float)$max_price;
            return "SELECT DISTINCT
                        el.ID AS product_id, el.NAME AS name, pr.PRICE AS price, pr.CURRENCY AS currency,
                        cp.AVAILABLE AS catalog_available, cp.QUANTITY AS quantity,
                        cp.QUANTITY_TRACE AS quantity_trace, cp.CAN_BUY_ZERO AS can_buy_zero,
                        identifier_prop.CODE AS identifier_code,
                        identifier_value.VALUE AS identifier_value,
                        parent_identifier_prop.CODE AS parent_identifier_code,
                        parent_identifier_value.VALUE AS parent_identifier_value,
                        COALESCE(pel.CODE, el.CODE) AS element_code,
                        COALESCE(pel.IBLOCK_ID, el.IBLOCK_ID) AS iblock_id,
                        COALESCE(pel.IBLOCK_SECTION_ID, el.IBLOCK_SECTION_ID) AS section_id,
                        sec.CODE AS section_code,
                        ib.CODE AS iblock_code,
                        ib.DETAIL_PAGE_URL AS detail_page_url,
                        ({$rel_expr}) AS relevance
                    FROM `{$p}iblock_element` el
                    LEFT JOIN (
                        SELECT price.PRODUCT_ID, MIN(price.PRICE) AS PRICE,
                               MAX(price.CURRENCY) AS CURRENCY
                        FROM `{$p}catalog_price` price
                        INNER JOIN `{$p}catalog_group` price_group
                            ON price_group.ID = price.CATALOG_GROUP_ID AND price_group.BASE = 'Y'
                        GROUP BY price.PRODUCT_ID
                    ) pr ON el.ID = pr.PRODUCT_ID
                    INNER JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
                    LEFT JOIN `{$p}iblock_property` prop
                        ON prop.IBLOCK_ID = el.IBLOCK_ID AND prop.CODE = 'CML2_LINK'
                    LEFT JOIN `{$p}iblock_element_property` el_prop
                        ON el.ID = el_prop.IBLOCK_ELEMENT_ID AND el_prop.IBLOCK_PROPERTY_ID = prop.ID
                    LEFT JOIN `{$p}iblock_element` pel ON el_prop.VALUE = pel.ID
                    LEFT JOIN `{$p}iblock_section` sec
                        ON COALESCE(pel.IBLOCK_SECTION_ID, el.IBLOCK_SECTION_ID) = sec.ID
                    LEFT JOIN `{$p}iblock` ib
                        ON COALESCE(pel.IBLOCK_ID, el.IBLOCK_ID) = ib.ID
                    LEFT JOIN `{$p}iblock_property` identifier_prop
                        ON identifier_prop.IBLOCK_ID = el.IBLOCK_ID
                       AND identifier_prop.CODE IN ('MODEL', 'CML2_MANUFACTURER_ARTICLE', 'ARTICLE', 'CML2_ARTICLE', 'SKU')
                    LEFT JOIN `{$p}iblock_element_property` identifier_value
                        ON identifier_value.IBLOCK_ELEMENT_ID = el.ID
                       AND identifier_value.IBLOCK_PROPERTY_ID = identifier_prop.ID
                    LEFT JOIN `{$p}iblock_property` parent_identifier_prop
                        ON parent_identifier_prop.IBLOCK_ID = pel.IBLOCK_ID
                       AND parent_identifier_prop.CODE IN ('MODEL', 'CML2_MANUFACTURER_ARTICLE', 'ARTICLE', 'CML2_ARTICLE', 'SKU')
                    LEFT JOIN `{$p}iblock_element_property` parent_identifier_value
                        ON parent_identifier_value.IBLOCK_ELEMENT_ID = pel.ID
                       AND parent_identifier_value.IBLOCK_PROPERTY_ID = parent_identifier_prop.ID
                    WHERE el.ACTIVE = 'Y'
                      AND (el.ACTIVE_FROM IS NULL OR el.ACTIVE_FROM <= NOW())
                      AND (el.ACTIVE_TO IS NULL OR el.ACTIVE_TO >= NOW())
                      AND (pel.ID IS NULL OR (
                          pel.ACTIVE = 'Y'
                          AND (pel.ACTIVE_FROM IS NULL OR pel.ACTIVE_FROM <= NOW())
                          AND (pel.ACTIVE_TO IS NULL OR pel.ACTIVE_TO >= NOW())
                      ))
                      AND ib.ACTIVE = 'Y'
                      AND (sec.ID IS NULL OR sec.ACTIVE = 'Y')
                      AND (({$cond}) OR {$identifier_cond})
                      AND {$price_cond}
                    ORDER BY relevance DESC, el.ID DESC
                    LIMIT {$limit}";
        };

        $multi = count($words) > 1;
        $sql   = $make_sql($multi ? 'AND' : 'OR');
        $started_at = microtime(true);

        try {
            $result = $this->db->query($sql);

            if ($multi && empty($result->rows)) {
                $result = $this->db->query($make_sql('OR'));
            }

            $products = [];
            $base_url = $this->getBaseUrl();
            foreach ($result->rows as $row) {
                $product_id = (int)$row['product_id'];
                if (!isset($products[$product_id])) {
                    $availability = $this->bitrixAvailability($row);
                    $price_value = (float)($row['price'] ?? 0);
                    $currency = strtoupper(trim((string)($row['currency'] ?? '')));
                    $products[$product_id] = [
                        'id' => (string)$product_id,
                        'name' => (string)$row['name'],
                        'model' => '',
                        'sku' => '',
                        'price' => $this->formatCatalogPrice($price_value, $currency),
                        'price_value' => $price_value,
                        'currency' => preg_match('/^[A-Z]{3}$/', $currency) ? $currency : '',
                        'link' => $this->buildBitrixUrl($row, $base_url),
                        'availability' => $availability,
                        'availability_label' => $this->bitrixAvailabilityLabel($availability),
                        'can_order' => in_array($availability, array('in_stock', 'preorder'), true),
                        'quantity' => is_numeric($row['quantity'] ?? null) ? (float)$row['quantity'] : null,
                        'relevance' => (int)($row['relevance'] ?? 0),
                    ];
                }

                $code = strtoupper(trim((string)($row['identifier_code'] ?? '')));
                $value = trim((string)($row['identifier_value'] ?? ''));
                $identifiers = array(
                    array($code, $value),
                    array(
                        strtoupper(trim((string)($row['parent_identifier_code'] ?? ''))),
                        trim((string)($row['parent_identifier_value'] ?? ''))
                    ),
                );
                foreach ($identifiers as $identifier) {
                    if ($identifier[1] !== '' && in_array($identifier[0], array('MODEL', 'CML2_MANUFACTURER_ARTICLE'), true)) {
                        $products[$product_id]['model'] = $identifier[1];
                    } elseif ($identifier[1] !== '' && in_array($identifier[0], array('ARTICLE', 'CML2_ARTICLE', 'SKU'), true)) {
                        $products[$product_id]['sku'] = $identifier[1];
                    }
                }
            }
            $products = array_values($products);
            $duration_ms = (int)round((microtime(true) - $started_at) * 1000);
            $this->log('[CmsSearch] Bitrix products mode=' . ($multi ? 'and' : 'or') . ' results=' . count($products) . ' duration_ms=' . $duration_ms);
            return $products;
        } catch (\Throwable $e) {
            $this->log('[CmsSearch] Bitrix products failed code=query_error');
            throw new CatalogSearchUnavailableException('bitrix', $e);
        }
    }

    



    public function searchPages(string $query): array {
        $p     = $this->prefix;
        $words = $this->splitWords($query);
        $limit = $this->getSearchLimit();

        $likes     = [];
        $relevance = [];
        foreach ($words as $word) {
            $esc         = $this->escapeLikeWord($word);
            $likes[]     = "(LOWER(el.NAME) LIKE '%{$esc}%' OR LOWER(el.DETAIL_TEXT) LIKE '%{$esc}%' OR LOWER(el.PREVIEW_TEXT) LIKE '%{$esc}%')";
            $relevance[] = "(CASE WHEN LOWER(el.NAME) LIKE '%{$esc}%' THEN 2 WHEN LOWER(el.DETAIL_TEXT) LIKE '%{$esc}%' THEN 1 WHEN LOWER(el.PREVIEW_TEXT) LIKE '%{$esc}%' THEN 1 ELSE 0 END)";
        }

        $rel_expr = implode(' + ', $relevance);
        $make_sql = function (string $op) use ($p, $likes, $rel_expr, $limit): string {
            $cond = implode(" {$op} ", $likes);
            return "SELECT DISTINCT el.ID, el.NAME AS name, el.CODE AS element_code,
                        COALESCE(NULLIF(el.DETAIL_TEXT, ''), el.PREVIEW_TEXT, '') AS description,
                        ib.CODE AS iblock_code,
                        ({$rel_expr}) AS relevance
                    FROM `{$p}iblock_element` el
                    JOIN `{$p}iblock` ib ON ib.ID = el.IBLOCK_ID
                    LEFT JOIN `{$p}catalog_product` cp ON cp.ID = el.ID
                    WHERE el.ACTIVE = 'Y' AND cp.ID IS NULL
                      AND (el.ACTIVE_FROM IS NULL OR el.ACTIVE_FROM <= NOW())
                      AND (el.ACTIVE_TO IS NULL OR el.ACTIVE_TO >= NOW())
                      AND ({$cond})
                    ORDER BY relevance DESC, el.ID DESC
                    LIMIT {$limit}";
        };

        $multi = count($words) > 1;
        $sql   = $make_sql($multi ? 'AND' : 'OR');
        $used_mode = $multi ? 'and' : 'or';

        try {
            $result = $this->db->query($sql);

            if ($multi && empty($result->rows)) {
                $sql = $make_sql('OR');
                $used_mode = 'or_fallback';
                $result = $this->db->query($sql);
            }

            $pages    = [];
            $base_url = $this->getBaseUrl();
            foreach ($result->rows as $row) {
                $slug    = !empty($row['element_code']) ? $row['element_code'] : $row['ID'];
                $section = !empty($row['iblock_code']) ? $row['iblock_code'] . '/' : '';
                $excerpt = mb_substr(trim(strip_tags((string)$row['description'])), 0, 200, 'UTF-8');
                $pages[] = [
                    'name'    => $row['name'],
                    'excerpt' => $excerpt,
                    'link'    => $base_url . '/' . $section . $slug . '/',
                ];
            }
            $this->log('[CmsSearch] Bitrix pages mode=' . $used_mode . ' results=' . count($pages));
            return $pages;
        } catch (\Throwable $e) {
            $this->logFailure('[CmsSearch] Bitrix pages failed', $e);
            return [];
        }
    }

    


    public function resolvePageContext(string $page_type, string $page_id): array {
        $page_type = strtolower(preg_replace('/[^a-z0-9_-]/i', '', trim($page_type)));
        $id = (int)$page_id;
        if ($id <= 0) {
            return [];
        }

        if ($page_type === 'product') {
            return $this->resolveProductPageContext($id);
        }
        if ($page_type === 'category') {
            return $this->resolveCategoryPageContext($id);
        }
        if ($page_type === 'information') {
            return $this->resolveInformationPageContext($id);
        }

        return [];
    }

     
     
     

    private function resolveProductPageContext(int $element_id): array {
        $p = $this->prefix;
        try {
            $result = $this->db->query(
                "SELECT el.ID, el.NAME AS name,
                        COALESCE(NULLIF(el.DETAIL_TEXT, ''), el.PREVIEW_TEXT, '') AS description,
                        pr.PRICE AS price,
                        ib.CODE AS iblock_code,
                        ib.DETAIL_PAGE_URL AS detail_page_url,
                        el.CODE AS element_code,
                        el.IBLOCK_ID AS iblock_id,
                        el.IBLOCK_SECTION_ID AS section_id,
                        sec.CODE AS section_code
                 FROM `{$p}iblock_element` el
                 JOIN `{$p}iblock` ib ON ib.ID = el.IBLOCK_ID
                 LEFT JOIN `{$p}catalog_price` pr ON pr.PRODUCT_ID = el.ID
                 LEFT JOIN `{$p}iblock_section` sec ON sec.ID = el.IBLOCK_SECTION_ID
                 WHERE el.ID = {$element_id}
                   AND el.ACTIVE = 'Y'
                   AND ib.IBLOCK_TYPE_ID = 'catalog'
                 LIMIT 1"
            );
        } catch (\Throwable $e) {
            $this->logFailure('[CmsContext] Bitrix product failed', $e);
            return [];
        }

        $row = $result->row ?? [];
        if (!$row) {
            return [];
        }

        $lines = ['Тип страницы: товар'];
        $this->addContextLine($lines, 'Название', (string)($row['name'] ?? ''));
        $this->addContextLine($lines, 'ID товара', (string)$element_id);
        $article = $this->getPropertyValue($element_id, $p, ['ARTICLE', 'CML2_ARTICLE', 'SKU']);
        $model = $this->getPropertyValue($element_id, $p, ['MODEL', 'CML2_MANUFACTURER_ARTICLE']);
        $this->addContextLine($lines, 'Артикул', $article);
        $this->addContextLine($lines, 'Модель', $model);
        $this->addContextLine($lines, 'Цена', $this->formatContextPrice($row['price'] ?? ''));
        $this->addContextLine($lines, 'Описание', $this->contextText((string)($row['description'] ?? ''), 620));

        $attrs = $this->getAllProperties($element_id, $p);
        if ($attrs) {
            $this->addContextLine($lines, 'Характеристики', $this->contextText(implode('; ', array_slice($attrs, 0, 12)), 700));
        }
        $offers = $this->getOfferNames($element_id, $p);
        if ($offers) {
            $this->addContextLine($lines, 'Торговые предложения', $this->contextText(implode('; ', array_slice($offers, 0, 8)), 520));
        }

        return [
            'kind' => 'product',
            'page_type' => 'product',
            'page_id' => (string)$element_id,
            'title' => (string)($row['name'] ?? ''),
            'url' => $this->buildBitrixUrl([
                'product_id' => $element_id,
                'detail_page_url' => $row['detail_page_url'] ?? '',
                'element_code' => $row['element_code'] ?? '',
                'iblock_id' => $row['iblock_id'] ?? '',
                'iblock_code' => $row['iblock_code'] ?? '',
                'section_id' => $row['section_id'] ?? '',
                'section_code' => $row['section_code'] ?? '',
            ], $this->getBaseUrl()),
            'facts' => [
                'type' => 'product',
                'element_id' => (string)$element_id,
                'name' => (string)($row['name'] ?? ''),
                'article' => $article,
                'model' => $model,
                'price' => $this->formatContextPrice($row['price'] ?? ''),
                'description_excerpt' => $this->contextText((string)($row['description'] ?? ''), 620),
                'attributes' => array_slice($attrs, 0, 12),
                'offers' => array_slice($offers, 0, 8),
            ],
            'prompt_lines' => $lines,
        ];
    }

    private function resolveCategoryPageContext(int $section_id): array {
        $p = $this->prefix;
        try {
            $result = $this->db->query(
                "SELECT sec.ID, sec.NAME AS name, sec.DESCRIPTION AS description,
                        sec.CODE AS section_code, sec.IBLOCK_ID AS iblock_id,
                        ib.CODE AS iblock_code
                 FROM `{$p}iblock_section` sec
                 LEFT JOIN `{$p}iblock` ib ON ib.ID = sec.IBLOCK_ID
                 WHERE sec.ID = {$section_id}
                   AND sec.ACTIVE = 'Y'
                 LIMIT 1"
            );
        } catch (\Throwable $e) {
            $this->logFailure('[CmsContext] Bitrix section failed', $e);
            return [];
        }

        $row = $result->row ?? [];
        if (!$row) {
            return [];
        }

        $lines = ['Тип страницы: категория'];
        $this->addContextLine($lines, 'Название', (string)($row['name'] ?? ''));
        $this->addContextLine($lines, 'ID категории', (string)$section_id);
        $this->addContextLine($lines, 'Инфоблок', (string)($row['iblock_code'] ?? ''));
        $this->addContextLine($lines, 'Описание', $this->contextText((string)($row['description'] ?? ''), 620));

        return [
            'kind' => 'category',
            'page_type' => 'category',
            'page_id' => (string)$section_id,
            'title' => (string)($row['name'] ?? ''),
            'url' => $this->getBaseUrl() . '/catalog/?SECTION_ID=' . $section_id,
            'facts' => [
                'type' => 'category',
                'section_id' => (string)$section_id,
                'name' => (string)($row['name'] ?? ''),
                'iblock' => (string)($row['iblock_code'] ?? ''),
                'description_excerpt' => $this->contextText((string)($row['description'] ?? ''), 620),
            ],
            'prompt_lines' => $lines,
        ];
    }

    private function resolveInformationPageContext(int $element_id): array {
        $p = $this->prefix;
        try {
            $result = $this->db->query(
                "SELECT el.ID, el.NAME AS name,
                        COALESCE(NULLIF(el.DETAIL_TEXT, ''), el.PREVIEW_TEXT, '') AS description,
                        el.CODE AS element_code, ib.CODE AS iblock_code
                 FROM `{$p}iblock_element` el
                 JOIN `{$p}iblock` ib ON ib.ID = el.IBLOCK_ID
                 WHERE el.ID = {$element_id}
                   AND el.ACTIVE = 'Y'
                   AND ib.IBLOCK_TYPE_ID != 'catalog'
                 LIMIT 1"
            );
        } catch (\Throwable $e) {
            $this->logFailure('[CmsContext] Bitrix information failed', $e);
            return [];
        }

        $row = $result->row ?? [];
        if (!$row) {
            return [];
        }

        $lines = ['Тип страницы: информационная страница'];
        $this->addContextLine($lines, 'Название', (string)($row['name'] ?? ''));
        $this->addContextLine($lines, 'ID страницы', (string)$element_id);
        $this->addContextLine($lines, 'Инфоблок', (string)($row['iblock_code'] ?? ''));
        $this->addContextLine($lines, 'Содержание', $this->contextText((string)($row['description'] ?? ''), 700));

        $section = !empty($row['iblock_code']) ? $row['iblock_code'] . '/' : '';
        $slug = !empty($row['element_code']) ? $row['element_code'] : $element_id;
        return [
            'kind' => 'information',
            'page_type' => 'information',
            'page_id' => (string)$element_id,
            'title' => (string)($row['name'] ?? ''),
            'url' => $this->getBaseUrl() . '/' . $section . $slug . '/',
            'facts' => [
                'type' => 'information',
                'element_id' => (string)$element_id,
                'title' => (string)($row['name'] ?? ''),
                'iblock' => (string)($row['iblock_code'] ?? ''),
                'content_excerpt' => $this->contextText((string)($row['description'] ?? ''), 700),
            ],
            'prompt_lines' => $lines,
        ];
    }

    private function bitrixAvailability(array $row): string {
        $available = strtoupper(trim((string)($row['catalog_available'] ?? '')));
        $quantity = is_numeric($row['quantity'] ?? null) ? (float)$row['quantity'] : null;
        $quantity_trace = strtoupper(trim((string)($row['quantity_trace'] ?? '')));
        $can_buy_zero = strtoupper(trim((string)($row['can_buy_zero'] ?? '')));

        if ($quantity !== null && $quantity <= 0) {
            if ($quantity_trace === 'N') {
                return 'in_stock';
            }
            if ($can_buy_zero === 'Y' || $available === 'Y') {
                return 'preorder';
            }
            if ($available === 'N') {
                return 'out_of_stock';
            }
            return 'unknown';
        }
        if ($available === 'Y') {
            return 'in_stock';
        }
        if ($available === 'N') {
            return 'out_of_stock';
        }
        return 'unknown';
    }

    private function bitrixAvailabilityLabel(string $availability): string {
        if ($availability === 'in_stock') {
            return 'В наличии';
        }
        if ($availability === 'preorder') {
            return 'Доступно под заказ';
        }
        if ($availability === 'out_of_stock') {
            return 'Нет в наличии';
        }
        return 'Наличие уточняется';
    }

    private function formatCatalogPrice(float $price, string $currency): string {
        if ($price <= 0) {
            return 'Цена по запросу';
        }
        $formatted = number_format($price, 2, '.', '');
        return preg_match('/^[A-Z]{3}$/', $currency) ? $formatted . ' ' . $currency : $formatted;
    }

    private function addContextLine(array &$lines, string $label, string $value): void {
        $value = $this->contextText($value, 900);
        if ($value !== '') {
            $lines[] = $label . ': ' . $value;
        }
    }

    private function contextText(string $value, int $limit): string {
        $value = html_entity_decode(strip_tags($value), ENT_QUOTES | ENT_HTML5, 'UTF-8');
        $value = preg_replace('/\s+/u', ' ', trim($value));
        return mb_strlen($value, 'UTF-8') > $limit ? mb_substr($value, 0, $limit, 'UTF-8') : $value;
    }

    private function formatContextPrice($value): string {
        if ($value === null || $value === '') {
            return '';
        }
        $price = (float)$value;
        return $price > 0 ? number_format($price, 2, '.', '') : '';
    }

    private function getPropertyValue(int $element_id, string $p, array $codes): string {
        foreach ($codes as $code) {
            $code_e = $this->db->escape($code);
            $sql    = "SELECT ep.VALUE
                       FROM `{$p}iblock_element_property` ep
                       JOIN `{$p}iblock_property` prop ON prop.ID = ep.IBLOCK_PROPERTY_ID
                       WHERE ep.IBLOCK_ELEMENT_ID = $element_id
                         AND prop.CODE = '$code_e'
                         AND ep.VALUE IS NOT NULL AND ep.VALUE != ''
                       LIMIT 1";
            try {
                $result = $this->db->query($sql);
                if ($result->row && !empty($result->row['VALUE'])) {
                    return (string)$result->row['VALUE'];
                }
            } catch (\Throwable $e) {
                 
            }
        }
        return '';
    }

    private function getAllProperties(int $element_id, string $p): array {
        $sql = "SELECT prop.NAME AS prop_name, ep.VALUE AS prop_value
                FROM `{$p}iblock_element_property` ep
                JOIN `{$p}iblock_property` prop ON prop.ID = ep.IBLOCK_PROPERTY_ID
                WHERE ep.IBLOCK_ELEMENT_ID = $element_id
                  AND ep.VALUE IS NOT NULL AND ep.VALUE != ''
                ORDER BY prop.NAME ASC";
        try {
            $result = $this->db->query($sql);
            $attrs  = [];
            foreach ($result->rows as $row) {
                $attrs[] = $row['prop_name'] . ': ' . $row['prop_value'];
            }
            return $attrs;
        } catch (\Throwable $e) {
            return [];
        }
    }

    private function getOfferNames(int $element_id, string $p): array {
        $sql = "SELECT offer.NAME AS offer_name
                FROM `{$p}iblock_element_property` ep
                JOIN `{$p}iblock_property` prop
                    ON prop.ID = ep.IBLOCK_PROPERTY_ID AND prop.CODE = 'CML2_LINK'
                JOIN `{$p}iblock_element` offer
                    ON offer.ID = ep.IBLOCK_ELEMENT_ID AND offer.ACTIVE = 'Y'
                WHERE ep.VALUE = $element_id
                ORDER BY offer.ID ASC
                LIMIT 20";
        try {
            $result = $this->db->query($sql);
            $names  = [];
            foreach ($result->rows as $row) {
                if (!empty($row['offer_name'])) $names[] = $row['offer_name'];
            }
            return $names;
        } catch (\Throwable $e) {
            return [];
        }
    }

     
     
     

    private function buildBitrixUrl(array $row, string $base_url): string {
        $p = $this->prefix;

        if (empty($row['detail_page_url'])) {
            return $base_url . '/catalog/?ELEMENT_ID=' . $row['product_id'];
        }

        $template = $row['detail_page_url'];

        if (strpos($template, '#PRODUCT_URL#') !== false) {
            $template = str_replace(
                '#PRODUCT_URL#',
                'catalog/#SECTION_CODE_PATH#/#ELEMENT_CODE#/',
                $template
            );
        }

        $section_code_path = '';
        if (!empty($row['section_id']) && strpos($template, '#SECTION_CODE_PATH#') !== false) {
            $path_sql = "SELECT parent.CODE
                         FROM `{$p}iblock_section` parent
                         JOIN `{$p}iblock_section` child ON child.ID = " . (int)$row['section_id'] . "
                         WHERE parent.IBLOCK_ID = " . (int)$row['iblock_id'] . "
                           AND child.LEFT_MARGIN  >= parent.LEFT_MARGIN
                           AND child.RIGHT_MARGIN <= parent.RIGHT_MARGIN
                         ORDER BY parent.LEFT_MARGIN";
            try {
                $path_result = $this->db->query($path_sql);
                $codes       = [];
                foreach ($path_result->rows as $path_row) {
                    if (!empty($path_row['CODE'])) $codes[] = $path_row['CODE'];
                }
                if (!empty($codes)) {
                    $section_code_path = implode('/', $codes);
                }
            } catch (\Throwable $e) {
                $this->logFailure('[CmsSearch] Bitrix section path failed', $e);
            }
        }

        $replacements = [
            '#SITE_DIR#'          => '/',
            '#IBLOCK_ID#'         => $row['iblock_id'],
            '#IBLOCK_CODE#'       => $row['iblock_code'] ?? '',
            '#ELEMENT_ID#'        => $row['product_id'],
            '#ELEMENT_CODE#'      => !empty($row['element_code']) ? $row['element_code'] : $row['product_id'],
            '#SECTION_ID#'        => $row['section_id'] ?? '',
            '#SECTION_CODE#'      => !empty($row['section_code']) ? $row['section_code'] : '',
            '#SECTION_CODE_PATH#' => $section_code_path ?: ($row['section_code'] ?? ''),
        ];

        $link = $base_url . '/' . ltrim(
            str_replace(array_keys($replacements), array_values($replacements), $template),
            '/'
        );
        return preg_replace('#([^:])//+#', '$1/', $link);
    }
}
