Ozon Product Parser

Парсер товаров с Ozon для анализа поисковой выдачи

Size

230.0 KB

Version

1.8.58

Created

Mar 26, 2026

Updated

21 days ago

1// ==UserScript==
2// @name		Ozon Product Parser
3// @description		Парсер товаров с Ozon для анализа поисковой выдачи
4// @version		1.8.58
5// @match		https://ozon.ru/*
6// @match		https://www.ozon.ru/*
7// @grant		GM.getValue
8// @grant		GM.setValue
9// @grant		GM.deleteValue
10// @icon		https://st.ozone.ru/assets/favicon.ico
11// ==/UserScript==
12(function() {
13    'use strict';
14    console.log('Ozon Product Parser: Extension loaded');
15
16    // Утилита для дебаунса
17    function debounce(func, wait) {
18        let timeout;
19        return function executedFunction(...args) {
20            const later = () => {
21                clearTimeout(timeout);
22                func(...args);
23            };
24            clearTimeout(timeout);
25            timeout = setTimeout(later, wait);
26        };
27    }
28
29    // Добавляем стили
30    function addStyles() {
31        const styles = `
32            .ozon-parser-container {
33                position: fixed;
34                top: 20px;
35                right: 20px;
36                z-index: 10000;
37                display: flex;
38                gap: 10px;
39                cursor: move;
40                user-select: none;
41            }
42            .ozon-parser-container.collapsed {
43                gap: 0;
44            }
45            .ozon-parser-toggle-btn {
46                background: linear-gradient(135deg, #005bff 0%, #0043c7 100%);
47                color: white;
48                border: none;
49                padding: 12px;
50                border-radius: 8px;
51                cursor: pointer;
52                font-size: 18px;
53                font-weight: 600;
54                box-shadow: 0 4px 12px rgba(0, 91, 255, 0.3);
55                transition: all 0.3s ease;
56                width: 44px;
57                height: 44px;
58                display: flex;
59                align-items: center;
60                justify-content: center;
61            }
62            .ozon-parser-toggle-btn:hover {
63                background: linear-gradient(135deg, #0043c7 0%, #002f8f 100%);
64                box-shadow: 0 6px 16px rgba(0, 91, 255, 0.4);
65                transform: translateY(-2px);
66            }
67            .ozon-parser-buttons-wrapper {
68                display: flex;
69                gap: 10px;
70                transition: all 0.3s ease;
71            }
72            .ozon-parser-buttons-wrapper.hidden {
73                display: none;
74            }
75            .ozon-parser-btn {
76                background: linear-gradient(135deg, #005bff 0%, #0043c7 100%);
77                color: white;
78                border: none;
79                padding: 12px 24px;
80                border-radius: 8px;
81                cursor: pointer;
82                font-size: 14px;
83                font-weight: 600;
84                box-shadow: 0 4px 12px rgba(0, 91, 255, 0.3);
85                transition: all 0.3s ease;
86            }
87            .ozon-parser-btn:hover {
88                background: linear-gradient(135deg, #0043c7 0%, #002f8f 100%);
89                box-shadow: 0 6px 16px rgba(0, 91, 255, 0.4);
90                transform: translateY(-2px);
91            }
92            .ozon-parser-btn:active {
93                transform: translateY(0);
94            }
95            .ozon-parser-btn.secondary {
96                background: linear-gradient(135deg, #28a745 0%, #1e7e34 100%);
97                box-shadow: 0 4px 12px rgba(40, 167, 69, 0.3);
98            }
99            .ozon-parser-btn.secondary:hover {
100                background: linear-gradient(135deg, #1e7e34 0%, #155724 100%);
101                box-shadow: 0 6px 16px rgba(40, 167, 69, 0.4);
102            }
103            .ozon-parser-modal {
104                position: fixed;
105                top: 0;
106                left: 0;
107                width: 100%;
108                height: 100%;
109                background: rgba(0, 0, 0, 0.7);
110                display: flex;
111                justify-content: center;
112                align-items: center;
113                z-index: 10001;
114            }
115            .ozon-parser-modal-content {
116                background: white;
117                padding: 30px;
118                border-radius: 12px;
119                max-width: 800px;
120                width: 90%;
121                max-height: 80vh;
122                overflow-y: auto;
123                box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
124            }
125            .ozon-parser-modal-header {
126                font-size: 24px;
127                font-weight: 700;
128                margin-bottom: 20px;
129                color: #333;
130            }
131            .ozon-parser-modal-body {
132                margin-bottom: 20px;
133            }
134            .ozon-parser-textarea {
135                width: 100%;
136                min-height: 200px;
137                padding: 12px;
138                border: 2px solid #e0e0e0;
139                border-radius: 8px;
140                font-size: 14px;
141                font-family: inherit;
142                resize: vertical;
143                box-sizing: border-box;
144            }
145            .ozon-parser-textarea:focus {
146                outline: none;
147                border-color: #005bff;
148            }
149            .ozon-parser-modal-footer {
150                display: flex;
151                gap: 10px;
152                justify-content: flex-end;
153            }
154            .ozon-parser-progress {
155                margin-top: 20px;
156                padding: 15px;
157                background: #f8f9fa;
158                border-radius: 8px;
159                font-size: 14px;
160                color: #333;
161            }
162            .ozon-parser-progress-bar {
163                width: 100%;
164                height: 8px;
165                background: #e0e0e0;
166                border-radius: 4px;
167                margin-top: 10px;
168                overflow: hidden;
169            }
170            .ozon-parser-progress-fill {
171                height: 100%;
172                background: linear-gradient(90deg, #005bff 0%, #0043c7 100%);
173                transition: width 0.3s ease;
174            }
175            .ozon-parser-results-table {
176                width: 100%;
177                border-collapse: collapse;
178                margin-top: 20px;
179                font-size: 13px;
180            }
181            .ozon-parser-results-table th,
182            .ozon-parser-results-table td {
183                padding: 12px;
184                text-align: left;
185                border-bottom: 1px solid #e0e0e0;
186                white-space: nowrap;
187            }
188            .ozon-parser-results-table th {
189                background: #f8f9fa;
190                font-weight: 600;
191                color: #333;
192                position: sticky;
193                top: 0;
194            }
195            .ozon-parser-results-table td:nth-child(3) {
196                max-width: 500px;
197                white-space: normal;
198                word-wrap: break-word;
199            }
200            .ozon-parser-results-table tr:hover {
201                background: #f8f9fa;
202            }
203            .ozon-parser-highlight {
204                background: #fff3cd !important;
205                font-weight: 600;
206            }
207            .ozon-parser-sku-link {
208                color: #005bff;
209                text-decoration: none;
210                font-weight: 600;
211            }
212            .ozon-parser-sku-link:hover {
213                text-decoration: underline;
214            }
215            .ozon-parser-tabs {
216                display: flex;
217                gap: 5px;
218                margin-bottom: 20px;
219                flex-wrap: wrap;
220            }
221            .ozon-parser-tab {
222                padding: 10px 20px;
223                background: #f8f9fa;
224                border: none;
225                border-radius: 6px;
226                cursor: pointer;
227                font-size: 14px;
228                transition: all 0.2s ease;
229            }
230            .ozon-parser-tab:hover {
231                background: #e9ecef;
232            }
233            .ozon-parser-tab.active {
234                background: #005bff;
235                color: white;
236                font-weight: 600;
237            }
238            .ozon-parser-info {
239                padding: 10px;
240                background: #e7f3ff;
241                border-left: 4px solid #005bff;
242                border-radius: 4px;
243                margin-bottom: 15px;
244                font-size: 13px;
245                color: #333;
246            }
247            .ozon-parser-search {
248                width: 100%;
249                padding: 10px 12px;
250                border: 2px solid #e0e0e0;
251                border-radius: 8px;
252                font-size: 14px;
253                margin-bottom: 15px;
254                box-sizing: border-box;
255            }
256            .ozon-parser-search:focus {
257                outline: none;
258                border-color: #005bff;
259            }
260            .ozon-parser-search::placeholder {
261                color: #999;
262            }
263            .ozon-parser-analytics {
264                margin-top: 30px;
265                padding: 20px;
266                background: #f8f9fa;
267                border-radius: 8px;
268            }
269            .ozon-parser-analytics-header {
270                font-size: 20px;
271                font-weight: 700;
272                margin-bottom: 20px;
273                color: #333;
274            }
275            .ozon-parser-analytics-section {
276                margin-bottom: 25px;
277            }
278            .ozon-parser-analytics-section-title {
279                font-size: 16px;
280                font-weight: 600;
281                margin-bottom: 12px;
282                color: #005bff;
283            }
284            .ozon-parser-analytics-grid {
285                display: grid;
286                grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
287                gap: 15px;
288                margin-bottom: 20px;
289            }
290            .ozon-parser-analytics-card {
291                background: white;
292                padding: 15px;
293                border-radius: 8px;
294                border: 1px solid #e0e0e0;
295            }
296            .ozon-parser-analytics-card-title {
297                font-size: 13px;
298                color: #666;
299                margin-bottom: 8px;
300            }
301            .ozon-parser-analytics-card-value {
302                font-size: 24px;
303                font-weight: 700;
304                color: #333;
305            }
306            .ozon-parser-analytics-table {
307                width: 100%;
308                border-collapse: collapse;
309                background: white;
310                border-radius: 8px;
311                overflow: hidden;
312            }
313            .ozon-parser-analytics-table th,
314            .ozon-parser-analytics-table td {
315                padding: 12px;
316                text-align: left;
317                border-bottom: 1px solid #e0e0e0;
318            }
319            .ozon-parser-analytics-table th {
320                background: #f8f9fa;
321                font-weight: 600;
322                color: #333;
323                font-size: 13px;
324            }
325            .ozon-parser-analytics-table td {
326                font-size: 13px;
327            }
328            .ozon-parser-analytics-bar {
329                height: 20px;
330                background: linear-gradient(90deg, #005bff 0%, #0043c7 100%);
331                border-radius: 4px;
332                transition: width 0.3s ease;
333            }
334            .ozon-parser-details-panel {
335                position: fixed;
336                top: 0;
337                right: -600px;
338                width: 600px;
339                height: 100vh;
340                background: white;
341                box-shadow: -4px 0 20px rgba(0, 0, 0, 0.3);
342                z-index: 10002;
343                overflow-y: auto;
344                transition: right 0.3s ease;
345            }
346            .ozon-parser-details-panel.open {
347                right: 0;
348            }
349            .ozon-parser-details-panel-header {
350                position: sticky;
351                top: 0;
352                background: white;
353                padding: 20px;
354                border-bottom: 2px solid #e0e0e0;
355                display: flex;
356                justify-content: space-between;
357                align-items: center;
358                z-index: 1;
359            }
360            .ozon-parser-details-panel-title {
361                font-size: 18px;
362                font-weight: 700;
363                color: #333;
364            }
365            .ozon-parser-details-panel-close {
366                background: #dc3545;
367                color: white;
368                border: none;
369                padding: 8px 16px;
370                border-radius: 6px;
371                cursor: pointer;
372                font-size: 14px;
373                font-weight: 600;
374                transition: all 0.2s ease;
375            }
376            .ozon-parser-details-panel-close:hover {
377                background: #c82333;
378            }
379            .ozon-parser-details-panel-body {
380                padding: 20px;
381            }
382            .ozon-parser-clickable-row {
383                cursor: pointer;
384                transition: background 0.2s ease;
385            }
386            .ozon-parser-clickable-row:hover {
387                background: #e7f3ff !important;
388            }
389        `;
390        const styleElement = document.createElement('style');
391        styleElement.textContent = styles;
392        document.head.appendChild(styleElement);
393        console.log('Ozon Product Parser: Styles added');
394    }
395
396    // Обновляем счетчик высоких цен
397    async function updateHighPriceCounter() {
398        const highPriceBtn = document.querySelector('#high-price-btn');
399        if (!highPriceBtn) return;
400        
401        const highPriceDataJson = await GM.getValue('ozon_parser_high_price', '[]');
402        const highPriceData = JSON.parse(highPriceDataJson);
403        
404        highPriceBtn.textContent = `Высокая цена (${highPriceData.length})`;
405    }
406    
407    // Показываем модальное окно с ценами
408    async function showPricesModal() {
409        console.log('Ozon Product Parser: Opening prices modal');
410        
411        try {
412            // Получаем результаты парсинга
413            const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
414            const allListResults = JSON.parse(allListResultsJson);
415            
416            if (Object.keys(allListResults).length === 0) {
417                alert('Нет данных для анализа цен. Сначала выполните парсинг.');
418                return;
419            }
420            
421            // Загружаем сохраненные привязки SKU к запросам
422            const skuQueryBindingsJson = await GM.getValue('ozon_parser_sku_query_bindings', '{}');
423            const skuQueryBindings = JSON.parse(skuQueryBindingsJson);
424            
425            // Собираем все товары наших брендов из всех запросов
426            const ourProductsData = [];
427            
428            for (const listName in allListResults) {
429                const listData = allListResults[listName];
430                for (const query in listData.queries) {
431                    const products = listData.queries[query];
432                    
433                    // Находим наши товары
434                    const ourProducts = products.filter(p => p.isTargetBrand);
435                    
436                    ourProducts.forEach(ourProduct => {
437                        // Проверяем, есть ли привязка для этого SKU
438                        const boundQuery = skuQueryBindings[ourProduct.sku];
439                        
440                        // Если есть привязка и это не тот запрос - пропускаем
441                        if (boundQuery && boundQuery !== query) {
442                            return;
443                        }
444                        
445                        // Находим топ-3 конкурентов по позиции в выдаче (а не по выручке)
446                        const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
447                        
448                        // Сортируем конкурентов по позиции в выдаче
449                        const sortedCompetitors = [...competitors].sort((a, b) => a.position - b.position);
450                        
451                        // Берем первых 3 конкурентов по позиции
452                        const top3Competitors = sortedCompetitors.slice(0, 3);
453                        
454                        if (top3Competitors.length > 0) {
455                            // Рассчитываем цены конкурентов пятью способами
456                            const competitorPrices = top3Competitors.map(c => c.price);
457                            const competitorRevenues = top3Competitors.map(c => c.revenue);
458                            
459                            // 1. Максимум по топ-3
460                            const competitorMaxPrice = Math.max(...competitorPrices);
461                            
462                            // 2. Среднее по топ-3
463                            const competitorAvgPrice = competitorPrices.reduce((sum, p) => sum + p, 0) / competitorPrices.length;
464                            
465                            // 3. Средневзвешенное по выручке
466                            const totalRevenue = competitorRevenues.reduce((sum, r) => sum + r, 0);
467                            const competitorWeightedPrice = top3Competitors.reduce((sum, c) => sum + (c.price * c.revenue), 0) / totalRevenue;
468                            
469                            // 4. Минимальная по топ-3
470                            const competitorMinPrice = Math.min(...competitorPrices);
471                            
472                            // 5. Медиана по топ-3
473                            const sortedPrices = [...competitorPrices].sort((a, b) => a - b);
474                            const competitorMedianPrice = sortedPrices[Math.floor(sortedPrices.length / 2)];
475                            
476                            // Рассчитываем среднюю цену (выручка / заказы)
477                            const ourAvgPrice = ourProduct.orders > 0 ? ourProduct.revenue / ourProduct.orders : 0;
478                            const competitorAvgPrices = top3Competitors.map(c => c.orders > 0 ? c.revenue / c.orders : 0);
479                            const competitorAvgPriceValue = competitorAvgPrices.reduce((sum, p) => sum + p, 0) / competitorAvgPrices.length;
480                            
481                            // Рассчитываем выручку конкурентов (среднее)
482                            const competitorRevenue = competitorRevenues.reduce((sum, r) => sum + r, 0) / competitorRevenues.length;
483                            
484                            ourProductsData.push({
485                                sku: ourProduct.sku,
486                                name: ourProduct.name,
487                                query: query,
488                                listName: listName,
489                                
490                                // Текущая цена
491                                ourCurrentPrice: ourProduct.price,
492                                competitorMaxPrice: competitorMaxPrice,
493                                competitorAvgPrice: competitorAvgPrice,
494                                competitorWeightedPrice: competitorWeightedPrice,
495                                competitorMinPrice: competitorMinPrice,
496                                competitorMedianPrice: competitorMedianPrice,
497                                
498                                // Средняя цена
499                                ourAvgPrice: ourAvgPrice,
500                                competitorAvgPriceValue: competitorAvgPriceValue,
501                                
502                                // Выручка
503                                ourRevenue: ourProduct.revenue,
504                                competitorRevenue: competitorRevenue,
505                                
506                                // Для расчета дельт
507                                top3Competitors: top3Competitors,
508                                
509                                // Флаг привязки
510                                isBound: !!boundQuery
511                            });
512                        }
513                    });
514                }
515            }
516            
517            if (ourProductsData.length === 0) {
518                alert('Нет товаров наших брендов в результатах парсинга.');
519                return;
520            }
521            
522            const modal = document.createElement('div');
523            modal.className = 'ozon-parser-modal';
524            
525            const content = document.createElement('div');
526            content.className = 'ozon-parser-modal-content';
527            content.style.maxWidth = '98vw';
528            content.style.width = '98vw';
529            
530            // Состояние сортировки
531            let sortColumn = null;
532            let sortDirection = 'asc';
533            
534            // Функция для экспорта в CSV
535            function exportToCSV(data, method, filters) {
536                // Фильтруем данные так же, как в таблице
537                let filteredData = [...data];
538                
539                // Применяем фильтр по запросу
540                if (filters.querySearch) {
541                    filteredData = filteredData.filter(item => 
542                        item.query.toLowerCase().includes(filters.querySearch.toLowerCase())
543                    );
544                }
545                
546                // Применяем фильтр по SKU
547                if (filters.skuSearch) {
548                    filteredData = filteredData.filter(item => 
549                        item.sku.includes(filters.skuSearch)
550                    );
551                }
552                
553                // Применяем фильтры по выручке
554                if (filters.revenue > 0) {
555                    filteredData = filteredData.filter(item => {
556                        const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
557                        if (filters.revenueDirection === 'less') {
558                            return revenueDelta <= -filters.revenue;
559                        } else {
560                            return revenueDelta >= filters.revenue;
561                        }
562                    });
563                }
564                
565                // Применяем фильтры по текущей цене
566                if (filters.currentPrice > 0) {
567                    filteredData = filteredData.filter(item => {
568                        let competitorPrice;
569                        if (method === 'max') competitorPrice = item.competitorMaxPrice;
570                        else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
571                        else if (method === 'min') competitorPrice = item.competitorMinPrice;
572                        else if (method === 'median') competitorPrice = item.competitorMedianPrice;
573                        else competitorPrice = item.competitorWeightedPrice;
574                        
575                        const priceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
576                        if (filters.currentPriceDirection === 'less') {
577                            return priceDelta <= -filters.currentPrice;
578                        } else {
579                            return priceDelta >= filters.currentPrice;
580                        }
581                    });
582                }
583                
584                // Применяем фильтры по средней цене
585                if (filters.avgPrice > 0) {
586                    filteredData = filteredData.filter(item => {
587                        const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
588                        if (filters.avgPriceDirection === 'less') {
589                            return avgPriceDelta <= -filters.avgPrice;
590                        } else {
591                            return avgPriceDelta >= filters.avgPrice;
592                        }
593                    });
594                }
595                
596                // Формируем CSV
597                const methodNames = {
598                    'max': 'Максимум',
599                    'avg': 'Среднее',
600                    'weighted': 'Средневзвешенное',
601                    'min': 'Минимум',
602                    'median': 'Медиана'
603                };
604                
605                let csv = '\uFEFF'; // BOM для корректного отображения кириллицы в Excel
606                csv += 'SKU;Название;Запрос;Список;';
607                csv += `Наша текущая цена;Конкуренты текущая (${methodNames[method]});Дельта текущей цены (%);`;
608                csv += 'Наша средняя цена;Конкуренты средняя;Дельта средней цены (%);';
609                csv += 'Наша выручка;Конкуренты выручка;Дельта выручки (%)\n';
610                
611                filteredData.forEach(item => {
612                    // Выбираем цену конкурентов в зависимости от метода
613                    let competitorPrice;
614                    if (method === 'max') competitorPrice = item.competitorMaxPrice;
615                    else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
616                    else if (method === 'min') competitorPrice = item.competitorMinPrice;
617                    else if (method === 'median') competitorPrice = item.competitorMedianPrice;
618                    else competitorPrice = item.competitorWeightedPrice;
619                    
620                    // Рассчитываем дельты
621                    const currentPriceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
622                    const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
623                    const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
624                    
625                    // Экранируем кавычки и переносы строк в названии
626                    const safeName = (item.name || '—').replace(/"/g, '""').replace(/\n/g, ' ');
627                    
628                    // Форматируем дельты: добавляем знак = в начале, чтобы Excel интерпретировал как формулу, возвращающую текст
629                    const currentPriceDeltaStr = `="${currentPriceDelta.toFixed(1)}"`;
630                    const avgPriceDeltaStr = `="${avgPriceDelta.toFixed(1)}"`;
631                    const revenueDeltaStr = `="${revenueDelta.toFixed(1)}"`;
632                    
633                    csv += `${item.sku};"${safeName}";${item.query};${item.listName};`;
634                    csv += `${Math.round(item.ourCurrentPrice)};${Math.round(competitorPrice)};${currentPriceDeltaStr};`;
635                    csv += `${Math.round(item.ourAvgPrice)};${Math.round(item.competitorAvgPriceValue)};${avgPriceDeltaStr};`;
636                    csv += `${Math.round(item.ourRevenue)};${Math.round(item.competitorRevenue)};${revenueDeltaStr}\n`;
637                });
638                
639                // Создаем и скачиваем файл
640                const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
641                const link = document.createElement('a');
642                const url = URL.createObjectURL(blob);
643                link.setAttribute('href', url);
644                link.setAttribute('download', `ozon_prices_${new Date().toISOString().slice(0, 10)}.csv`);
645                link.style.visibility = 'hidden';
646                document.body.appendChild(link);
647                link.click();
648                document.body.removeChild(link);
649                
650                console.log(`Ozon Product Parser: Exported ${filteredData.length} products to CSV`);
651            }
652            
653            // Функция для отображения таблицы с учетом выбранного метода и фильтров
654            function displayPricesTable(method, filters) {
655                let filteredData = [...ourProductsData];
656                
657                // Применяем фильтр по запросу
658                if (filters.querySearch) {
659                    filteredData = filteredData.filter(item => 
660                        item.query.toLowerCase().includes(filters.querySearch.toLowerCase())
661                    );
662                }
663                
664                // Применяем фильтр по SKU
665                if (filters.skuSearch) {
666                    filteredData = filteredData.filter(item => 
667                        item.sku.includes(filters.skuSearch)
668                    );
669                }
670                
671                // Применяем фильтры по выручке
672                if (filters.revenue > 0) {
673                    filteredData = filteredData.filter(item => {
674                        const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
675                        if (filters.revenueDirection === 'less') {
676                            return revenueDelta <= -filters.revenue;
677                        } else {
678                            return revenueDelta >= filters.revenue;
679                        }
680                    });
681                }
682                
683                // Применяем фильтры по текущей цене
684                if (filters.currentPrice > 0) {
685                    filteredData = filteredData.filter(item => {
686                        let competitorPrice;
687                        if (method === 'max') competitorPrice = item.competitorMaxPrice;
688                        else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
689                        else if (method === 'min') competitorPrice = item.competitorMinPrice;
690                        else if (method === 'median') competitorPrice = item.competitorMedianPrice;
691                        else competitorPrice = item.competitorWeightedPrice;
692                        
693                        const priceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
694                        if (filters.currentPriceDirection === 'less') {
695                            return priceDelta <= -filters.currentPrice;
696                        } else {
697                            return priceDelta >= filters.currentPrice;
698                        }
699                    });
700                }
701                
702                // Применяем фильтры по средней цене
703                if (filters.avgPrice > 0) {
704                    filteredData = filteredData.filter(item => {
705                        const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
706                        if (filters.avgPriceDirection === 'less') {
707                            return avgPriceDelta <= -filters.avgPrice;
708                        } else {
709                            return avgPriceDelta >= filters.avgPrice;
710                        }
711                    });
712                }
713                
714                // Применяем сортировку
715                if (sortColumn) {
716                    filteredData.sort((a, b) => {
717                        let aVal, bVal;
718                        
719                        switch(sortColumn) {
720                        case 'sku':
721                            aVal = a.sku;
722                            bVal = b.sku;
723                            break;
724                        case 'name':
725                            aVal = (a.name || '').toLowerCase();
726                            bVal = (b.name || '').toLowerCase();
727                            break;
728                        case 'query':
729                            aVal = a.query.toLowerCase();
730                            bVal = b.query.toLowerCase();
731                            break;
732                        case 'ourCurrentPrice':
733                            aVal = a.ourCurrentPrice;
734                            bVal = b.ourCurrentPrice;
735                            break;
736                        case 'competitorPrice':
737                            if (method === 'max') {
738                                aVal = a.competitorMaxPrice;
739                                bVal = b.competitorMaxPrice;
740                            } else if (method === 'avg') {
741                                aVal = a.competitorAvgPrice;
742                                bVal = b.competitorAvgPrice;
743                            } else {
744                                aVal = a.competitorWeightedPrice;
745                                bVal = b.competitorWeightedPrice;
746                            }
747                            break;
748                        case 'currentPriceDelta':
749                            let compPriceA = method === 'max' ? a.competitorMaxPrice : (method === 'avg' ? a.competitorAvgPrice : a.competitorWeightedPrice);
750                            let compPriceB = method === 'max' ? b.competitorMaxPrice : (method === 'avg' ? b.competitorAvgPrice : b.competitorWeightedPrice);
751                            aVal = ((a.ourCurrentPrice - compPriceA) / compPriceA) * 100;
752                            bVal = ((b.ourCurrentPrice - compPriceB) / compPriceB) * 100;
753                            break;
754                        case 'ourAvgPrice':
755                            aVal = a.ourAvgPrice;
756                            bVal = b.ourAvgPrice;
757                            break;
758                        case 'competitorAvgPrice':
759                            aVal = a.competitorAvgPriceValue;
760                            bVal = b.competitorAvgPriceValue;
761                            break;
762                        case 'avgPriceDelta':
763                            aVal = ((a.ourAvgPrice - a.competitorAvgPriceValue) / a.competitorAvgPriceValue) * 100;
764                            bVal = ((b.ourAvgPrice - b.competitorAvgPriceValue) / b.competitorAvgPriceValue) * 100;
765                            break;
766                        case 'ourRevenue':
767                            aVal = a.ourRevenue;
768                            bVal = b.ourRevenue;
769                            break;
770                        case 'competitorRevenue':
771                            aVal = a.competitorRevenue;
772                            bVal = b.competitorRevenue;
773                            break;
774                        case 'revenueDelta':
775                            aVal = ((a.ourRevenue - a.competitorRevenue) / a.competitorRevenue) * 100;
776                            bVal = ((b.ourRevenue - b.competitorRevenue) / b.competitorRevenue) * 100;
777                            break;
778                        default:
779                            return 0;
780                        }
781                        
782                        if (typeof aVal === 'string') {
783                            return sortDirection === 'asc' ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
784                        } else {
785                            return sortDirection === 'asc' ? aVal - bVal : bVal - aVal;
786                        }
787                    });
788                }
789                
790                let tableHTML = `
791                    <div class="ozon-parser-info">
792                        Найдено товаров: ${filteredData.length} из ${ourProductsData.length}
793                    </div>
794                    <table class="ozon-parser-results-table">
795                        <thead>
796                            <tr>
797                                <th rowspan="2" data-sort="sku" style="cursor: pointer; user-select: none;">SKU ${sortColumn === 'sku' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
798                                <th rowspan="2" data-sort="name" style="cursor: pointer; user-select: none;">Название ${sortColumn === 'name' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
799                                <th rowspan="2" data-sort="query" style="cursor: pointer; user-select: none;">Запрос ${sortColumn === 'query' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
800                                <th colspan="3">Текущая цена</th>
801                                <th colspan="3">Средняя цена</th>
802                                <th colspan="3">Выручка</th>
803                            </tr>
804                            <tr>
805                                <th data-sort="ourCurrentPrice" style="cursor: pointer; user-select: none;">Наша ${sortColumn === 'ourCurrentPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
806                                <th data-sort="competitorPrice" style="cursor: pointer; user-select: none;">Конкуренты ${sortColumn === 'competitorPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
807                                <th data-sort="currentPriceDelta" style="cursor: pointer; user-select: none;">Дельта ${sortColumn === 'currentPriceDelta' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
808                                <th data-sort="ourAvgPrice" style="cursor: pointer; user-select: none;">Наша ${sortColumn === 'ourAvgPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
809                                <th data-sort="competitorAvgPrice" style="cursor: pointer; user-select: none;">Конкуренты ${sortColumn === 'competitorAvgPrice' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
810                                <th data-sort="avgPriceDelta" style="cursor: pointer; user-select: none;">Дельта ${sortColumn === 'avgPriceDelta' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
811                                <th data-sort="ourRevenue" style="cursor: pointer; user-select: none;">Наша ${sortColumn === 'ourRevenue' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
812                                <th data-sort="competitorRevenue" style="cursor: pointer; user-select: none;">Конкуренты ${sortColumn === 'competitorRevenue' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
813                                <th data-sort="revenueDelta" style="cursor: pointer; user-select: none;">Дельта ${sortColumn === 'revenueDelta' ? (sortDirection === 'asc' ? '▲' : '▼') : ''}</th>
814                            </tr>
815                        </thead>
816                        <tbody>
817                `;
818                
819                filteredData.forEach(item => {
820                    // Выбираем цену конкурентов в зависимости от метода
821                    let competitorPrice;
822                    if (method === 'max') competitorPrice = item.competitorMaxPrice;
823                    else if (method === 'avg') competitorPrice = item.competitorAvgPrice;
824                    else if (method === 'min') competitorPrice = item.competitorMinPrice;
825                    else if (method === 'median') competitorPrice = item.competitorMedianPrice;
826                    else competitorPrice = item.competitorWeightedPrice;
827                    
828                    // Рассчитываем дельты
829                    const currentPriceDelta = ((item.ourCurrentPrice - competitorPrice) / competitorPrice) * 100;
830                    const avgPriceDelta = ((item.ourAvgPrice - item.competitorAvgPriceValue) / item.competitorAvgPriceValue) * 100;
831                    const revenueDelta = ((item.ourRevenue - item.competitorRevenue) / item.competitorRevenue) * 100;
832                    
833                    // Добавляем индикатор привязки к запросу
834                    const boundIndicator = item.isBound ? '<span style="color: #28a745; font-weight: 600; margin-left: 5px;" title="Запрос привязан к товару">🔒</span>' : '';
835                    
836                    tableHTML += `
837                        <tr>
838                            <td><a href="https://www.ozon.ru/product/${item.sku}" target="_blank" class="ozon-parser-sku-link">${item.sku}</a></td>
839                            <td style="max-width: 300px; white-space: normal;">${item.name || '—'}</td>
840                            <td><span class="ozon-parser-query-clickable" data-sku="${item.sku}" style="color: #005bff; cursor: pointer; text-decoration: underline;">${item.query}${boundIndicator}</span></td>
841                            
842                            <td style="font-weight: 600;">${Math.round(item.ourCurrentPrice).toLocaleString('ru-RU')} ₽</td>
843                            <td>${Math.round(competitorPrice).toLocaleString('ru-RU')} ₽</td>
844                            <td style="color: ${currentPriceDelta >= 0 ? '#dc3545' : '#28a745'}; font-weight: 600;">
845                                ${currentPriceDelta > 0 ? '+' : ''}${currentPriceDelta.toFixed(1)}%
846                            </td>
847                            
848                            <td style="font-weight: 600;">${Math.round(item.ourAvgPrice).toLocaleString('ru-RU')} ₽</td>
849                            <td>${Math.round(item.competitorAvgPriceValue).toLocaleString('ru-RU')} ₽</td>
850                            <td style="color: ${avgPriceDelta >= 0 ? '#dc3545' : '#28a745'}; font-weight: 600;">
851                                ${avgPriceDelta > 0 ? '+' : ''}${avgPriceDelta.toFixed(1)}%
852                            </td>
853                            
854                            <td style="font-weight: 600;">${Math.round(item.ourRevenue).toLocaleString('ru-RU')} ₽</td>
855                            <td>${Math.round(item.competitorRevenue).toLocaleString('ru-RU')} ₽</td>
856                            <td style="color: ${revenueDelta >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">
857                                ${revenueDelta > 0 ? '+' : ''}${revenueDelta.toFixed(1)}%
858                            </td>
859                        </tr>
860                    `;
861                });
862                
863                tableHTML += `
864                        </tbody>
865                    </table>
866                `;
867                
868                const tableContainer = content.querySelector('#prices-table-container');
869                tableContainer.innerHTML = tableHTML;
870                
871                // Добавляем обработчики сортировки
872                tableContainer.querySelectorAll('th[data-sort]').forEach(th => {
873                    th.addEventListener('click', () => {
874                        const column = th.getAttribute('data-sort');
875                        if (sortColumn === column) {
876                            sortDirection = sortDirection === 'asc' ? 'desc' : 'asc';
877                        } else {
878                            sortColumn = column;
879                            sortDirection = 'asc';
880                        }
881                        displayPricesTable(currentMethod, currentFilters);
882                    });
883                });
884                
885                // Добавляем обработчики кликов на запросы для выбора привязки
886                tableContainer.querySelectorAll('.ozon-parser-query-clickable').forEach(querySpan => {
887                    querySpan.addEventListener('click', async (e) => {
888                        e.preventDefault();
889                        e.stopPropagation();
890                        const sku = querySpan.getAttribute('data-sku');
891                        await showQuerySelectionModal(sku, allListResults, skuQueryBindings);
892                    });
893                });
894                
895                // Добавляем обработчики кликов на строки для открытия панели с деталями
896                tableContainer.querySelectorAll('tbody tr').forEach(row => {
897                    row.classList.add('ozon-parser-clickable-row');
898                    row.addEventListener('click', async (e) => {
899                        // Не открываем панель, если кликнули на ссылку или запрос
900                        if (e.target.tagName === 'A' || e.target.classList.contains('ozon-parser-query-clickable')) return;
901                        
902                        const skuLink = row.querySelector('.ozon-parser-sku-link');
903                        if (!skuLink) return;
904                        
905                        const sku = skuLink.textContent.trim();
906                        const productData = filteredData.find(item => item.sku === sku);
907                        
908                        if (productData) {
909                            await showProductDetailsPanel(productData, allListResults);
910                        }
911                    });
912                });
913            }
914            
915            content.innerHTML = `
916                <div class="ozon-parser-modal-header">💰 Анализ цен</div>
917                <div class="ozon-parser-modal-body">
918                    <div class="ozon-parser-info">
919                        Сравнение цен наших товаров с топ-3 конкурентами по позиции в выдаче
920                    </div>
921                    
922                    <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
923                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Метод расчета цены конкурентов:</label>
924                        <select class="ozon-parser-search" id="price-method-selector" style="margin-bottom: 0;">
925                            <option value="max">Максимум по топ-3</option>
926                            <option value="avg">Среднее по топ-3</option>
927                            <option value="weighted">Средневзвешенное по выручке</option>
928                            <option value="min">Минимальная по топ-3</option>
929                            <option value="median">Медиана по топ-3</option>
930                        </select>
931                    </div>
932                    
933                    <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
934                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 12px; display: block;">Фильтры (AND логика):</label>
935                        
936                        <div id="selected-query-info" style="display: none; padding: 10px; background: #e7f3ff; border-left: 4px solid #005bff; border-radius: 4px; margin-bottom: 15px;">
937                            <div style="display: flex; justify-content: space-between; align-items: center;">
938                                <span style="font-size: 13px; color: #333;">Фильтр по запросу: <strong id="selected-query-text"></strong></span>
939                                <button id="clear-query-filter" style="background: #dc3545; color: white; border: none; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 12px;">Очистить</button>
940                            </div>
941                        </div>
942                        
943                        <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-bottom: 15px;">
944                            <div>
945                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Поиск по запросу</label>
946                                <input type="text" class="ozon-parser-search" id="filter-query-search" placeholder="Введите запрос" style="margin-bottom: 0;">
947                            </div>
948                            <div>
949                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Поиск по SKU</label>
950                                <input type="text" class="ozon-parser-search" id="filter-sku-search" placeholder="Введите SKU" style="margin-bottom: 0;">
951                            </div>
952                        </div>
953                        
954                        <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 15px;">
955                            <div>
956                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Выручка</label>
957                                <div style="display: flex; gap: 5px;">
958                                    <select class="ozon-parser-search" id="filter-revenue-direction" style="margin-bottom: 0; flex: 0 0 auto; width: 100px;">
959                                        <option value="less">Меньше</option>
960                                        <option value="more">Больше</option>
961                                    </select>
962                                    <input type="number" class="ozon-parser-search" id="filter-revenue" placeholder="0" min="0" step="1" value="0" style="margin-bottom: 0; flex: 1;">
963                                </div>
964                                <div style="font-size: 10px; color: #999; margin-top: 2px;">на % (и более)</div>
965                            </div>
966                            <div>
967                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Текущая цена</label>
968                                <div style="display: flex; gap: 5px;">
969                                    <select class="ozon-parser-search" id="filter-current-price-direction" style="margin-bottom: 0; flex: 0 0 auto; width: 100px;">
970                                        <option value="more">Больше</option>
971                                        <option value="less">Меньше</option>
972                                    </select>
973                                    <input type="number" class="ozon-parser-search" id="filter-current-price" placeholder="0" min="0" step="1" value="0" style="margin-bottom: 0; flex: 1;">
974                                </div>
975                                <div style="font-size: 10px; color: #999; margin-top: 2px;">на % (и более)</div>
976                            </div>
977                            <div>
978                                <label style="font-size: 12px; color: #666; margin-bottom: 5px; display: block;">Средняя цена</label>
979                                <div style="display: flex; gap: 5px;">
980                                    <select class="ozon-parser-search" id="filter-avg-price-direction" style="margin-bottom: 0; flex: 0 0 auto; width: 100px;">
981                                        <option value="more">Больше</option>
982                                        <option value="less">Меньше</option>
983                                    </select>
984                                    <input type="number" class="ozon-parser-search" id="filter-avg-price" placeholder="0" min="0" step="1" value="0" style="margin-bottom: 0; flex: 1;">
985                                </div>
986                                <div style="font-size: 10px; color: #999; margin-top: 2px;">на % (и более)</div>
987                            </div>
988                        </div>
989                    </div>
990                    
991                    <div id="prices-table-container"></div>
992                </div>
993                <div class="ozon-parser-modal-footer">
994                    <button class="ozon-parser-btn secondary" id="export-csv-btn">Экспорт в CSV</button>
995                    <button class="ozon-parser-btn" id="close-prices-modal">Закрыть</button>
996                </div>
997            `;
998            
999            modal.appendChild(content);
1000            document.body.appendChild(modal);
1001            
1002            // Начальное отображение
1003            let currentMethod = 'max';
1004            let currentFilters = { 
1005                revenue: 0, 
1006                revenueDirection: 'less',
1007                currentPrice: 0, 
1008                currentPriceDirection: 'more',
1009                avgPrice: 0,
1010                avgPriceDirection: 'more',
1011                querySearch: '',
1012                skuSearch: '',
1013                selectedQuery: ''
1014            };
1015            
1016            // Загружаем сохраненный запрос
1017            const savedQuery = await GM.getValue('ozon_parser_selected_query', '');
1018            if (savedQuery) {
1019                currentFilters.selectedQuery = savedQuery;
1020            }
1021            
1022            displayPricesTable(currentMethod, currentFilters);
1023            
1024            // Обновляем информационную панель после первого рендера
1025            if (savedQuery) {
1026                const selectedQueryInfo = content.querySelector('#selected-query-info');
1027                const selectedQueryText = content.querySelector('#selected-query-text');
1028                if (selectedQueryInfo && selectedQueryText) {
1029                    selectedQueryInfo.style.display = 'block';
1030                    selectedQueryText.textContent = savedQuery;
1031                }
1032            }
1033            
1034            // Функция для обновления фильтров из полей ввода
1035            function updateFiltersFromInputs() {
1036                currentFilters = {
1037                    revenue: parseFloat(content.querySelector('#filter-revenue').value) || 0,
1038                    revenueDirection: content.querySelector('#filter-revenue-direction').value,
1039                    currentPrice: parseFloat(content.querySelector('#filter-current-price').value) || 0,
1040                    currentPriceDirection: content.querySelector('#filter-current-price-direction').value,
1041                    avgPrice: parseFloat(content.querySelector('#filter-avg-price').value) || 0,
1042                    avgPriceDirection: content.querySelector('#filter-avg-price-direction').value,
1043                    querySearch: content.querySelector('#filter-query-search').value.trim(),
1044                    skuSearch: content.querySelector('#filter-sku-search').value.trim(),
1045                    selectedQuery: currentFilters.selectedQuery
1046                };
1047                displayPricesTable(currentMethod, currentFilters);
1048            }
1049            
1050            // Обработчик очистки фильтра по запросу
1051            const clearQueryFilterBtn = content.querySelector('#clear-query-filter');
1052            if (clearQueryFilterBtn) {
1053                clearQueryFilterBtn.addEventListener('click', async () => {
1054                    currentFilters.selectedQuery = '';
1055                    await GM.setValue('ozon_parser_selected_query', '');
1056                    const selectedQueryInfo = content.querySelector('#selected-query-info');
1057                    if (selectedQueryInfo) {
1058                        selectedQueryInfo.style.display = 'none';
1059                    }
1060                    displayPricesTable(currentMethod, currentFilters);
1061                });
1062            }
1063            
1064            // Обработчик изменения метода расчета
1065            const methodSelector = content.querySelector('#price-method-selector');
1066            methodSelector.addEventListener('change', () => {
1067                currentMethod = methodSelector.value;
1068                displayPricesTable(currentMethod, currentFilters);
1069            });
1070            
1071            // Обработчики для всех фильтров - применяем автоматически
1072            content.querySelector('#filter-query-search').addEventListener('input', debounce(updateFiltersFromInputs, 300));
1073            content.querySelector('#filter-sku-search').addEventListener('input', debounce(updateFiltersFromInputs, 300));
1074            
1075            content.querySelector('#filter-revenue').addEventListener('input', debounce(updateFiltersFromInputs, 500));
1076            content.querySelector('#filter-revenue-direction').addEventListener('change', updateFiltersFromInputs);
1077            
1078            content.querySelector('#filter-current-price').addEventListener('input', debounce(updateFiltersFromInputs, 500));
1079            content.querySelector('#filter-current-price-direction').addEventListener('change', updateFiltersFromInputs);
1080            
1081            content.querySelector('#filter-avg-price').addEventListener('input', debounce(updateFiltersFromInputs, 500));
1082            content.querySelector('#filter-avg-price-direction').addEventListener('change', updateFiltersFromInputs);
1083            
1084            // Обработчик экспорта в CSV
1085            content.querySelector('#export-csv-btn').addEventListener('click', () => {
1086                exportToCSV(ourProductsData, currentMethod, currentFilters);
1087            });
1088            
1089            // Обработчик закрытия
1090            content.querySelector('#close-prices-modal').addEventListener('click', () => {
1091                modal.remove();
1092            });
1093            
1094            modal.addEventListener('click', (e) => {
1095                if (e.target === modal) {
1096                    modal.remove();
1097                }
1098            });
1099            
1100            console.log('Ozon Product Parser: Prices modal shown');
1101        } catch (error) {
1102            console.error('Ozon Product Parser: Error in showPricesModal:', error);
1103            alert('Ошибка при отображении цен: ' + error.message);
1104        }
1105    }
1106
1107    // Показываем модальное окно выбора запроса для товара
1108    async function showQuerySelectionModal(sku, allListResults, skuQueryBindings) {
1109        console.log('Ozon Product Parser: Opening query selection modal for SKU:', sku);
1110        
1111        // Собираем все запросы, где встречается этот товар
1112        const queriesWithProduct = [];
1113        for (const listName in allListResults) {
1114            const listData = allListResults[listName];
1115            for (const query in listData.queries) {
1116                const products = listData.queries[query];
1117                const product = products.find(p => p.sku === sku);
1118                if (product) {
1119                    // Находим конкурентов для этого запроса
1120                    const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
1121                    const sortedCompetitors = [...competitors].sort((a, b) => a.position - b.position);
1122                    const top3Competitors = sortedCompetitors.slice(0, 3);
1123                    
1124                    if (top3Competitors.length > 0) {
1125                        const competitorPrices = top3Competitors.map(c => c.price);
1126                        const competitorRevenues = top3Competitors.map(c => c.revenue);
1127                        
1128                        const competitorMaxPrice = Math.max(...competitorPrices);
1129                        const competitorAvgPrice = competitorPrices.reduce((sum, p) => sum + p, 0) / competitorPrices.length;
1130                        const competitorRevenue = competitorRevenues.reduce((sum, r) => sum + r, 0) / competitorRevenues.length;
1131                        
1132                        queriesWithProduct.push({
1133                            listName: listName,
1134                            query: query,
1135                            product: product,
1136                            competitorMaxPrice: competitorMaxPrice,
1137                            competitorAvgPrice: competitorAvgPrice,
1138                            competitorRevenue: competitorRevenue
1139                        });
1140                    }
1141                }
1142            }
1143        }
1144        
1145        if (queriesWithProduct.length === 0) {
1146            alert('Товар не найден ни в одном запросе');
1147            return;
1148        }
1149        
1150        const modal = document.createElement('div');
1151        modal.className = 'ozon-parser-modal';
1152        modal.style.zIndex = '10003';
1153        
1154        const content = document.createElement('div');
1155        content.className = 'ozon-parser-modal-content';
1156        content.style.maxWidth = '900px';
1157        
1158        const currentBoundQuery = skuQueryBindings[sku];
1159        
1160        let modalHTML = `
1161            <div class="ozon-parser-modal-header">Выбор запроса для SKU ${sku}</div>
1162            <div class="ozon-parser-modal-body">
1163                <div class="ozon-parser-info">
1164                    Нажмите на значок замка, чтобы закрепить запрос для этого товара.
1165                    ${currentBoundQuery ? `<br><strong>Текущая привязка:</strong> ${currentBoundQuery}` : ''}
1166                </div>
1167                <table class="ozon-parser-results-table">
1168                    <thead>
1169                        <tr>
1170                            <th style="width: 40px;"></th>
1171                            <th>Запрос</th>
1172                            <th>Список</th>
1173                            <th>Позиция</th>
1174                            <th>Выручка товара</th>
1175                            <th>Макс. цена конкурентов</th>
1176                            <th>Средняя цена конкурентов</th>
1177                            <th>Средняя выручка конкурентов</th>
1178                        </tr>
1179                    </thead>
1180                    <tbody>
1181        `;
1182        
1183        queriesWithProduct.forEach(item => {
1184            const isBound = currentBoundQuery === item.query;
1185            const rowStyle = isBound ? 'background: #d4edda;' : '';
1186            const lockIcon = isBound ? '🔒' : '🔓';
1187            const lockColor = isBound ? '#28a745' : '#999';
1188            
1189            modalHTML += `
1190                <tr style="${rowStyle}">
1191                    <td style="text-align: center;">
1192                        <span class="query-lock-icon" data-query="${item.query}" style="cursor: pointer; font-size: 20px; color: ${lockColor}; user-select: none;" title="${isBound ? 'Отвязать запрос' : 'Закрепить запрос'}">${lockIcon}</span>
1193                    </td>
1194                    <td style="font-weight: ${isBound ? '600' : 'normal'};">${item.query}</td>
1195                    <td>${item.listName}</td>
1196                    <td>${item.product.position}</td>
1197                    <td>${Math.round(item.product.revenue).toLocaleString('ru-RU')} ₽</td>
1198                    <td>${Math.round(item.competitorMaxPrice).toLocaleString('ru-RU')} ₽</td>
1199                    <td>${Math.round(item.competitorAvgPrice).toLocaleString('ru-RU')} ₽</td>
1200                    <td>${Math.round(item.competitorRevenue).toLocaleString('ru-RU')} ₽</td>
1201                </tr>
1202            `;
1203        });
1204        
1205        modalHTML += `
1206                    </tbody>
1207                </table>
1208            </div>
1209            <div class="ozon-parser-modal-footer">
1210                <button class="ozon-parser-btn" id="close-query-selection-modal">Закрыть</button>
1211            </div>
1212        `;
1213        
1214        content.innerHTML = modalHTML;
1215        modal.appendChild(content);
1216        document.body.appendChild(modal);
1217        
1218        // Обработчики кликов на замки
1219        content.querySelectorAll('.query-lock-icon').forEach(lockIcon => {
1220            lockIcon.addEventListener('click', async () => {
1221                const selectedQuery = lockIcon.getAttribute('data-query');
1222                const isBound = currentBoundQuery === selectedQuery;
1223                
1224                if (isBound) {
1225                    // Отвязываем запрос
1226                    if (confirm(`Отвязать запрос "${selectedQuery}" от товара ${sku}?`)) {
1227                        delete skuQueryBindings[sku];
1228                        await GM.setValue('ozon_parser_sku_query_bindings', JSON.stringify(skuQueryBindings));
1229                        
1230                        console.log(`Ozon Product Parser: Unbound SKU ${sku} from query "${selectedQuery}"`);
1231                        
1232                        modal.remove();
1233                        
1234                        // Перезагружаем таблицу цен
1235                        await showPricesModal();
1236                    }
1237                } else {
1238                    // Привязываем запрос
1239                    skuQueryBindings[sku] = selectedQuery;
1240                    await GM.setValue('ozon_parser_sku_query_bindings', JSON.stringify(skuQueryBindings));
1241                    
1242                    console.log(`Ozon Product Parser: Bound SKU ${sku} to query "${selectedQuery}"`);
1243                    
1244                    modal.remove();
1245                    
1246                    // Перезагружаем таблицу цен
1247                    await showPricesModal();
1248                }
1249            });
1250        });
1251        
1252        // Закрытие
1253        content.querySelector('#close-query-selection-modal').addEventListener('click', () => {
1254            modal.remove();
1255        });
1256        
1257        modal.addEventListener('click', (e) => {
1258            if (e.target === modal) {
1259                modal.remove();
1260            }
1261        });
1262    }
1263    
1264    // Показываем панель с деталями товара
1265    async function showProductDetailsPanel(productData, allListResults) {
1266        console.log('Ozon Product Parser: Opening product details panel for SKU:', productData.sku);
1267        
1268        // Удаляем существующую панель, если она есть
1269        const existingPanel = document.querySelector('.ozon-parser-details-panel');
1270        if (existingPanel) {
1271            existingPanel.remove();
1272        }
1273        
1274        // Создаем панель
1275        const panel = document.createElement('div');
1276        panel.className = 'ozon-parser-details-panel';
1277        
1278        // Загружаем привязки SKU к запросам
1279        const skuQueryBindingsJson = await GM.getValue('ozon_parser_sku_query_bindings', '{}');
1280        const skuQueryBindings = JSON.parse(skuQueryBindingsJson);
1281        const boundQuery = skuQueryBindings[productData.sku];
1282        
1283        // Собираем все запросы, где встречается этот товар
1284        const queriesWithProduct = [];
1285        for (const listName in allListResults) {
1286            const listData = allListResults[listName];
1287            for (const query in listData.queries) {
1288                const products = listData.queries[query];
1289                const product = products.find(p => p.sku === productData.sku);
1290                if (product) {
1291                    // Если есть привязка и это не тот запрос - пропускаем
1292                    if (boundQuery && boundQuery !== query) {
1293                        continue;
1294                    }
1295                    
1296                    queriesWithProduct.push({
1297                        listName: listName,
1298                        query: query,
1299                        product: product,
1300                        allProducts: products
1301                    });
1302                }
1303            }
1304        }
1305        
1306        // Функция для генерации HTML панели с аналитикой
1307        async function generatePanelHTML(queryData) {
1308            const analytics = await analyzeProducts(queryData.allProducts);
1309            
1310            return `
1311                <div class="ozon-parser-details-panel-header">
1312                    <div class="ozon-parser-details-panel-title">SKU: ${productData.sku}</div>
1313                    <button class="ozon-parser-details-panel-close">Закрыть</button>
1314                </div>
1315                <div class="ozon-parser-details-panel-body">
1316                    <div style="margin-bottom: 20px;">
1317                        <div style="font-size: 16px; font-weight: 600; margin-bottom: 10px;">${productData.name || 'Название не найдено'}</div>
1318                        <div style="font-size: 14px; color: #666; margin-bottom: 5px;">Текущая цена: <strong>${Math.round(productData.ourCurrentPrice).toLocaleString('ru-RU')} ₽</strong></div>
1319                        <div style="font-size: 14px; color: #666; margin-bottom: 5px;">Средняя цена: <strong>${Math.round(productData.ourAvgPrice).toLocaleString('ru-RU')} ₽</strong></div>
1320                        <div style="font-size: 14px; color: #666;">Выручка: <strong>${Math.round(productData.ourRevenue).toLocaleString('ru-RU')} ₽</strong></div>
1321                    </div>
1322                    
1323                    ${analytics && analytics.elasticity ? `
1324                    <div style="margin-bottom: 20px; padding: 15px; background: #e7f3ff; border-radius: 8px; border-left: 4px solid #005bff;">
1325                        <div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: #005bff;">📊 Эластичность запроса</div>
1326                        <div style="font-size: 24px; font-weight: 700; color: #333; margin-bottom: 8px;">${analytics.elasticity.value}</div>
1327                        <div style="font-size: 13px; color: #666; line-height: 1.5;">
1328                            ${analytics.elasticity.interpretation}
1329                        </div>
1330                    </div>
1331                    ` : ''}
1332                    
1333                    ${analytics && analytics.recommendedPrices ? `
1334                    <div style="margin-bottom: 20px;">
1335                        <div style="font-size: 15px; font-weight: 600; margin-bottom: 12px; color: #005bff;">💰 Стратегии ценообразования</div>
1336                        
1337                        <div style="background: #f8f9fa; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #6c757d;">
1338                            <div style="font-size: 14px; font-weight: 600; color: #6c757d; margin-bottom: 5px;">⚡ Захват рынка</div>
1339                            <div style="font-size: 20px; font-weight: 700; color: #333; margin-bottom: 5px;">${analytics.recommendedPrices.marketCapture.price.toLocaleString('ru-RU')}</div>
1340                            <div style="font-size: 12px; color: #666; margin-bottom: 8px;">${analytics.recommendedPrices.marketCapture.description}</div>
1341                            ${analytics.brandRecommendations && (analytics.brandRecommendations.gls || analytics.brandRecommendations.skinphoria) ? `
1342                            <div style="font-size: 11px; color: #666; margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
1343                                ${(() => {
1344        const ourProduct = (analytics.brandRecommendations.gls?.currentProducts || [])
1345            .concat(analytics.brandRecommendations.skinphoria?.currentProducts || [])
1346            .find(p => p.sku === productData.sku);
1347        if (!ourProduct || !ourProduct.recommendations || !ourProduct.recommendations.forecast || !ourProduct.recommendations.forecast.marketCapture) return '';
1348        const forecast = ourProduct.recommendations.forecast.marketCapture;
1349        return `
1350                                        <div style="color: ${forecast.revenueChange >= 0 ? '#28a745' : '#dc3545'};">Выручка: ${forecast.revenueChange > 0 ? '+' : ''}${forecast.revenueChange}%</div>
1351                                        <div style="color: ${forecast.ordersChange >= 0 ? '#28a745' : '#dc3545'};">Заказы: ${forecast.ordersChange > 0 ? '+' : ''}${forecast.ordersChange}%</div>
1352                                        ${forecast.profit !== null ? `<div style="color: ${forecast.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">Прибыль: ${forecast.profitChange > 0 ? '+' : ''}${forecast.profitChange}% (${forecast.profit.toFixed(0)} ₽)</div>` : ''}
1353                                    `;
1354    })()}
1355                            </div>
1356                            ` : ''}
1357                        </div>
1358                        
1359                        <div style="background: #fff3cd; padding: 12px; border-radius: 8px; margin-bottom: 10px; border-left: 4px solid #dc3545;">
1360                            <div style="font-size: 14px; font-weight: 600; color: #dc3545; margin-bottom: 5px;">✅ Оптимальная</div>
1361                            <div style="font-size: 20px; font-weight: 700; color: #333; margin-bottom: 5px;">${analytics.recommendedPrices.aggressive.price.toLocaleString('ru-RU')}</div>
1362                            <div style="font-size: 12px; color: #666; margin-bottom: 8px;">${analytics.recommendedPrices.aggressive.description}</div>
1363                            ${analytics.brandRecommendations && (analytics.brandRecommendations.gls || analytics.brandRecommendations.skinphoria) ? `
1364                            <div style="font-size: 11px; color: #666; margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
1365                                ${(() => {
1366        const ourProduct = (analytics.brandRecommendations.gls?.currentProducts || [])
1367            .concat(analytics.brandRecommendations.skinphoria?.currentProducts || [])
1368            .find(p => p.sku === productData.sku);
1369        if (!ourProduct || !ourProduct.recommendations || !ourProduct.recommendations.forecast || !ourProduct.recommendations.forecast.aggressive) return '';
1370        const forecast = ourProduct.recommendations.forecast.aggressive;
1371        return `
1372                                        <div style="color: ${forecast.revenueChange >= 0 ? '#28a745' : '#dc3545'};">Выручка: ${forecast.revenueChange > 0 ? '+' : ''}${forecast.revenueChange}%</div>
1373                                        <div style="color: ${forecast.ordersChange >= 0 ? '#28a745' : '#dc3545'};">Заказы: ${forecast.ordersChange > 0 ? '+' : ''}${forecast.ordersChange}%</div>
1374                                        ${forecast.profit !== null ? `<div style="color: ${forecast.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">Прибыль: ${forecast.profitChange > 0 ? '+' : ''}${forecast.profitChange}% (${forecast.profit.toFixed(0)} ₽)</div>` : ''}
1375                                    `;
1376    })()}
1377                            </div>
1378                            ` : ''}
1379                        </div>
1380                        
1381                        <div style="background: #d4edda; padding: 12px; border-radius: 8px; border-left: 4px solid #28a745;">
1382                            <div style="font-size: 14px; font-weight: 600; color: #28a745; margin-bottom: 5px;">🔥 Агрессивная</div>
1383                            <div style="font-size: 20px; font-weight: 700; color: #333; margin-bottom: 5px;">${analytics.recommendedPrices.optimal.price.toLocaleString('ru-RU')}</div>
1384                            <div style="font-size: 12px; color: #666; margin-bottom: 8px;">${analytics.recommendedPrices.optimal.description}</div>
1385                            ${analytics.brandRecommendations && (analytics.brandRecommendations.gls || analytics.brandRecommendations.skinphoria) ? `
1386                            <div style="font-size: 11px; color: #666; margin-top: 8px; padding-top: 8px; border-top: 1px solid #e0e0e0;">
1387                                ${(() => {
1388        const ourProduct = (analytics.brandRecommendations.gls?.currentProducts || [])
1389            .concat(analytics.brandRecommendations.skinphoria?.currentProducts || [])
1390            .find(p => p.sku === productData.sku);
1391        if (!ourProduct || !ourProduct.recommendations || !ourProduct.recommendations.forecast || !ourProduct.recommendations.forecast.optimal) return '';
1392        const forecast = ourProduct.recommendations.forecast.optimal;
1393        return `
1394                                        <div style="color: ${forecast.revenueChange >= 0 ? '#28a745' : '#dc3545'};">Выручка: ${forecast.revenueChange > 0 ? '+' : ''}${forecast.revenueChange}%</div>
1395                                        <div style="color: ${forecast.ordersChange >= 0 ? '#28a745' : '#dc3545'};">Заказы: ${forecast.ordersChange > 0 ? '+' : ''}${forecast.ordersChange}%</div>
1396                                        ${forecast.profit !== null ? `<div style="color: ${forecast.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600;">Прибыль: ${forecast.profitChange > 0 ? '+' : ''}${forecast.profitChange}% (${forecast.profit.toFixed(0)} ₽)</div>` : ''}
1397                                    `;
1398    })()}
1399                            </div>
1400                            ` : ''}
1401                        </div>
1402                    </div>
1403                    ` : ''}
1404                    
1405                    <div style="margin-bottom: 20px;">
1406                        <div style="font-size: 15px; font-weight: 600; margin-bottom: 10px; color: #005bff;">Запросы с этим товаром (${queriesWithProduct.length})</div>
1407                        <select class="ozon-parser-search" id="query-selector-panel" style="margin-bottom: 15px;">
1408                            ${queriesWithProduct.map((item, index) => `
1409                                <option value="${index}">${item.query} (${item.listName})</option>
1410                            `).join('')}
1411                        </select>
1412                        <div id="query-details-container"></div>
1413                    </div>
1414                </div>
1415            `;
1416        }
1417        
1418        // Получаем аналитику для первого запроса и формируем HTML
1419        const firstQueryData = queriesWithProduct[0];
1420        const initialHTML = await generatePanelHTML(firstQueryData);
1421        
1422        panel.innerHTML = initialHTML;
1423        document.body.appendChild(panel);
1424        
1425        // Функция для отображения деталей выбранного запроса
1426        function displayQueryDetails(queryIndex) {
1427            const queryData = queriesWithProduct[queryIndex];
1428            const product = queryData.product;
1429            const allProducts = queryData.allProducts;
1430            const query = queryData.query;
1431            
1432            // Находим топ-3 конкурентов по выручке
1433            const competitors = allProducts.filter(p => !p.isTargetBrand && p.price > 0 && p.revenue > 0);
1434            const sortedCompetitors = [...competitors].sort((a, b) => b.revenue - a.revenue);
1435            const top3Competitors = sortedCompetitors.slice(0, 3);
1436            
1437            // Позиция товара по выручке
1438            const sortedByRevenue = [...allProducts].sort((a, b) => b.revenue - a.revenue);
1439            const revenuePosition = sortedByRevenue.findIndex(p => p.sku === product.sku) + 1;
1440            
1441            // URL для поиска на Ozon
1442            const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(query)}&from_global=true`;
1443            
1444            let detailsHTML = `
1445                <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px;">
1446                    <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Позиции в выдаче</div>
1447                    <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
1448                        <div>
1449                            <div style="font-size: 12px; color: #666;">По позиции</div>
1450                            <div style="font-size: 18px; font-weight: 700; color: #005bff;">${product.position} место</div>
1451                        </div>
1452                        <div>
1453                            <div style="font-size: 12px; color: #666;">По выручке</div>
1454                            <div style="font-size: 18px; font-weight: 700; color: #005bff;">${revenuePosition} место</div>
1455                        </div>
1456                    </div>
1457                    <div style="margin-top: 10px;">
1458                        <a href="${searchUrl}" target="_blank" class="ozon-parser-sku-link" style="font-size: 13px;">🔍 Открыть запрос на Ozon</a>
1459                    </div>
1460                </div>
1461                
1462                <div style="padding: 15px; background: #f8f9fa; border-radius: 8px; margin-bottom: 15px;">
1463                    <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Метрики товара</div>
1464                    <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Цена: <strong>${product.price.toLocaleString('ru-RU')} ₽</strong></div>
1465                    <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Выручка: <strong>${product.revenue.toLocaleString('ru-RU')} ₽</strong></div>
1466                    <div style="font-size: 13px; color: #666;">Заказы: <strong>${product.orders.toLocaleString('ru-RU')}</strong></div>
1467                </div>
1468            `;
1469            
1470            if (top3Competitors.length > 0) {
1471                detailsHTML += `
1472                    <div style="padding: 15px; background: #fff3cd; border-radius: 8px;">
1473                        <div style="font-size: 14px; font-weight: 600; margin-bottom: 10px;">Топ-3 конкурента по выручке</div>
1474                        <table class="ozon-parser-results-table" style="margin-top: 0;">
1475                            <thead>
1476                                <tr>
1477                                    <th>Позиция</th>
1478                                    <th>Бренд</th>
1479                                    <th>Текущая цена</th>
1480                                    <th>Средняя цена</th>
1481                                    <th>Выручка</th>
1482                                    <th>Заказы</th>
1483                                </tr>
1484                            </thead>
1485                            <tbody>
1486                `;
1487                
1488                top3Competitors.forEach(comp => {
1489                    const avgPrice = comp.orders > 0 ? comp.revenue / comp.orders : 0;
1490                    detailsHTML += `
1491                        <tr>
1492                            <td>${comp.position}</td>
1493                            <td><a href="https://www.ozon.ru/product/${comp.sku}" target="_blank" class="ozon-parser-sku-link">${comp.brand || '—'}</a></td>
1494                            <td>${comp.price.toLocaleString('ru-RU')} ₽</td>
1495                            <td>${Math.round(avgPrice).toLocaleString('ru-RU')} ₽</td>
1496                            <td>${comp.revenue.toLocaleString('ru-RU')} ₽</td>
1497                            <td>${comp.orders.toLocaleString('ru-RU')}</td>
1498                        </tr>
1499                    `;
1500                });
1501                
1502                detailsHTML += `
1503                            </tbody>
1504                        </table>
1505                    </div>
1506                `;
1507            }
1508            
1509            const container = panel.querySelector('#query-details-container');
1510            container.innerHTML = detailsHTML;
1511        }
1512        
1513        // Функция для обновления панели при смене запроса
1514        async function updatePanelForQuery(queryIndex) {
1515            const selectedQueryData = queriesWithProduct[queryIndex];
1516            
1517            // Пересчитываем аналитику для выбранного запроса
1518            const newHTML = await generatePanelHTML(selectedQueryData);
1519            panel.innerHTML = newHTML;
1520            
1521            // Переподключаем обработчики после обновления HTML
1522            const newQuerySelector = panel.querySelector('#query-selector-panel');
1523            newQuerySelector.value = queryIndex;
1524            
1525            // Переподключаем обработчик изменения запроса
1526            newQuerySelector.addEventListener('change', async () => {
1527                const newSelectedIndex = parseInt(newQuerySelector.value);
1528                await updatePanelForQuery(newSelectedIndex);
1529            });
1530            
1531            // Переподключаем обработчик закрытия
1532            const newCloseBtn = panel.querySelector('.ozon-parser-details-panel-close');
1533            newCloseBtn.addEventListener('click', () => {
1534                panel.classList.remove('open');
1535                setTimeout(() => panel.remove(), 300);
1536            });
1537            
1538            // Отображаем детали выбранного запроса
1539            displayQueryDetails(queryIndex);
1540        }
1541        
1542        // Отображаем детали первого запроса
1543        displayQueryDetails(0);
1544        
1545        // Обработчик изменения запроса
1546        const querySelector = panel.querySelector('#query-selector-panel');
1547        querySelector.addEventListener('change', async () => {
1548            const selectedIndex = parseInt(querySelector.value);
1549            await updatePanelForQuery(selectedIndex);
1550        });
1551        
1552        // Обработчик закрытия панели
1553        const closeBtn = panel.querySelector('.ozon-parser-details-panel-close');
1554        closeBtn.addEventListener('click', () => {
1555            panel.classList.remove('open');
1556            setTimeout(() => panel.remove(), 300);
1557        });
1558        
1559        // Открываем панель с анимацией
1560        setTimeout(() => {
1561            panel.classList.add('open');
1562        }, 10);
1563        
1564        console.log('Ozon Product Parser: Product details panel opened');
1565    }
1566
1567    // Создаем UI кнопок
1568    function createUI() {
1569        const container = document.createElement('div');
1570        container.className = 'ozon-parser-container';
1571        
1572        // Загружаем сохраненную позицию
1573        GM.getValue('ozon_parser_position', JSON.stringify({ top: 20, right: 20 })).then(posJson => {
1574            const pos = JSON.parse(posJson);
1575            container.style.top = pos.top + 'px';
1576            container.style.right = pos.right + 'px';
1577        });
1578        
1579        // Загружаем состояние сворачивания
1580        GM.getValue('ozon_parser_collapsed', 'false').then(collapsed => {
1581            if (collapsed === 'true') {
1582                container.classList.add('collapsed');
1583                buttonsWrapper.classList.add('hidden');
1584            }
1585        });
1586        
1587        // Кнопка сворачивания/разворачивания
1588        const toggleBtn = document.createElement('button');
1589        toggleBtn.className = 'ozon-parser-toggle-btn';
1590        toggleBtn.innerHTML = '📊';
1591        toggleBtn.title = 'Свернуть/Развернуть панель';
1592        
1593        // Обертка для кнопок
1594        const buttonsWrapper = document.createElement('div');
1595        buttonsWrapper.className = 'ozon-parser-buttons-wrapper';
1596        
1597        const parseBtn = document.createElement('button');
1598        parseBtn.className = 'ozon-parser-btn';
1599        parseBtn.textContent = 'Парсинг';
1600        parseBtn.addEventListener('click', (e) => {
1601            e.stopPropagation();
1602            showParseModal();
1603        });
1604        
1605        const pricesBtn = document.createElement('button');
1606        pricesBtn.className = 'ozon-parser-btn';
1607        pricesBtn.id = 'prices-btn';
1608        pricesBtn.style.background = 'linear-gradient(135deg, #dc3545 0%, #c82333 100%)';
1609        pricesBtn.style.boxShadow = '0 4px 12px rgba(220, 53, 69, 0.3)';
1610        pricesBtn.textContent = 'Цены';
1611        pricesBtn.addEventListener('click', (e) => {
1612            e.stopPropagation();
1613            showPricesModal();
1614        });
1615        
1616        const resultsBtn = document.createElement('button');
1617        resultsBtn.className = 'ozon-parser-btn';
1618        resultsBtn.id = 'results-btn';
1619        resultsBtn.style.background = 'linear-gradient(135deg, #28a745 0%, #1e7e34 100%)';
1620        resultsBtn.style.boxShadow = '0 4px 12px rgba(40, 167, 69, 0.3)';
1621        resultsBtn.textContent = 'Результаты';
1622        resultsBtn.addEventListener('click', (e) => {
1623            e.stopPropagation();
1624            showResultsModal();
1625        });
1626        
1627        buttonsWrapper.appendChild(parseBtn);
1628        buttonsWrapper.appendChild(pricesBtn);
1629        buttonsWrapper.appendChild(resultsBtn);
1630        
1631        container.appendChild(toggleBtn);
1632        container.appendChild(buttonsWrapper);
1633        
1634        // Обработчик сворачивания/разворачивания
1635        toggleBtn.addEventListener('click', async (e) => {
1636            e.stopPropagation();
1637            const isCollapsed = container.classList.toggle('collapsed');
1638            buttonsWrapper.classList.toggle('hidden');
1639            await GM.setValue('ozon_parser_collapsed', isCollapsed ? 'true' : 'false');
1640        });
1641        
1642        // Перетаскивание панели
1643        let isDragging = false;
1644        let currentX;
1645        let currentY;
1646        let initialX;
1647        let initialY;
1648        
1649        container.addEventListener('mousedown', (e) => {
1650            // Не начинаем перетаскивание, если кликнули на кнопку
1651            if (e.target.tagName === 'BUTTON') return;
1652            
1653            isDragging = true;
1654            initialX = e.clientX - container.offsetLeft;
1655            initialY = e.clientY - container.offsetTop;
1656            container.style.cursor = 'grabbing';
1657        });
1658        
1659        document.addEventListener('mousemove', (e) => {
1660            if (!isDragging) return;
1661            
1662            e.preventDefault();
1663            currentX = e.clientX - initialX;
1664            currentY = e.clientY - initialY;
1665            
1666            // Ограничиваем перемещение в пределах окна
1667            const maxX = window.innerWidth - container.offsetWidth;
1668            const maxY = window.innerHeight - container.offsetHeight;
1669            
1670            currentX = Math.max(0, Math.min(currentX, maxX));
1671            currentY = Math.max(0, Math.min(currentY, maxY));
1672            
1673            container.style.left = currentX + 'px';
1674            container.style.top = currentY + 'px';
1675            container.style.right = 'auto';
1676        });
1677        
1678        document.addEventListener('mouseup', async () => {
1679            if (isDragging) {
1680                isDragging = false;
1681                container.style.cursor = 'move';
1682                
1683                // Сохраняем позицию
1684                const rect = container.getBoundingClientRect();
1685                await GM.setValue('ozon_parser_position', JSON.stringify({
1686                    top: rect.top,
1687                    right: window.innerWidth - rect.right
1688                }));
1689            }
1690        });
1691        
1692        document.body.appendChild(container);
1693        console.log('Ozon Product Parser: UI created');
1694    }
1695
1696    // Показываем модальное окно для ввода запросов
1697    function showParseModal() {
1698        const modal = document.createElement('div');
1699        modal.className = 'ozon-parser-modal';
1700        
1701        const content = document.createElement('div');
1702        content.className = 'ozon-parser-modal-content';
1703        content.innerHTML = `
1704            <div class="ozon-parser-modal-header">Парсинг товаров Ozon</div>
1705            <div class="ozon-parser-modal-body">
1706                <div class="ozon-parser-info">
1707                    Введите поисковые запросы (каждый с новой строки). Парсер извлечет топ-16 товаров для каждого запроса.
1708                </div>
1709                <div style="margin-bottom: 15px;">
1710                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Скидка Ozon (%):</label>
1711                    <div style="display: flex; gap: 10px; align-items: center;">
1712                        <input type="number" class="ozon-parser-search" id="ozon-discount-input" placeholder="50" value="50" min="0" max="100" step="1" style="margin-bottom: 0; flex: 1;">
1713                        <button class="ozon-parser-btn" id="calculate-discount-btn" style="background: linear-gradient(135deg, #ff6b00 0%, #e65100 100%); box-shadow: 0 4px 12px rgba(255, 107, 0, 0.3); margin-bottom: 0;">Рассчитать автоматически</button>
1714                    </div>
1715                    <div style="font-size: 12px; color: #666; margin-top: 5px;">
1716                        Укажите среднюю скидку Ozon для расчета базовой цены (цены поручения). Например, если товар продается по 500₽ со скидкой 50%, то базовая цена = 1000₽.
1717                    </div>
1718                </div>
1719                <div style="margin-bottom: 15px;">
1720                    <button class="ozon-parser-btn" id="load-list-btn" style="width: 100%; margin-bottom: 10px;">Загрузить сохраненный список</button>
1721                </div>
1722                <textarea class="ozon-parser-textarea" placeholder="Например:&#10;гинкго билоба&#10;аргинин&#10;витамин д"></textarea>
1723                <div style="margin-top: 15px; display: flex; gap: 10px; align-items: center;">
1724                    <input type="text" class="ozon-parser-search" id="list-name-input" placeholder="Название списка (например: БАДы март 2024)" style="flex: 1; margin-bottom: 0;">
1725                    <button class="ozon-parser-btn secondary" id="save-list-btn">Сохранить список</button>
1726                </div>
1727                <div style="margin-top: 15px;">
1728                    <button class="ozon-parser-btn secondary" id="manage-costs-btn" style="width: 100%;">Управление расходами (себестоимость, комиссия, доставка)</button>
1729                </div>
1730                <div class="ozon-parser-progress" style="display: none;">
1731                    <div class="ozon-parser-progress-text">Обработка запросов...</div>
1732                    <div class="ozon-parser-progress-bar">
1733                        <div class="ozon-parser-progress-fill" style="width: 0%"></div>
1734                    </div>
1735                </div>
1736            </div>
1737            <div class="ozon-parser-modal-footer">
1738                <button class="ozon-parser-btn" id="cancel-parse-btn">Отмена</button>
1739                <button class="ozon-parser-btn" id="start-parsing-btn">Начать парсинг</button>
1740            </div>
1741        `;
1742        
1743        modal.appendChild(content);
1744        document.body.appendChild(modal);
1745
1746        // Закрытие по клику на фон
1747        modal.addEventListener('click', (e) => {
1748            if (e.target === modal) {
1749                modal.remove();
1750            }
1751        });
1752        
1753        // Обработчик кнопки отмены
1754        const cancelBtn = content.querySelector('#cancel-parse-btn');
1755        cancelBtn.addEventListener('click', () => {
1756            modal.remove();
1757        });
1758        
1759        // Обработчик кнопки расчета скидки
1760        const calculateDiscountBtn = content.querySelector('#calculate-discount-btn');
1761        calculateDiscountBtn.addEventListener('click', async () => {
1762            calculateDiscountBtn.disabled = true;
1763            calculateDiscountBtn.textContent = 'Расчет...';
1764            
1765            try {
1766                // Сохраняем флаг для автоматического расчета
1767                await GM.setValue('ozon_parser_calculate_discount', 'true');
1768                
1769                // Открываем страницу в новой вкладке
1770                await GM.openInTab('https://seller.ozon.ru/app/prices/control', false);
1771                
1772                // Ждем результата расчета
1773                let attempts = 0;
1774                const maxAttempts = 60; // 60 секунд максимум
1775                
1776                const checkInterval = setInterval(async () => {
1777                    attempts++;
1778                    const calculatedDiscount = await GM.getValue('ozon_parser_calculated_discount', null);
1779                    const calculateFlag = await GM.getValue('ozon_parser_calculate_discount', 'false');
1780                    
1781                    if (calculatedDiscount !== null && calculateFlag === 'false') {
1782                        // Расчет завершен
1783                        clearInterval(checkInterval);
1784                        
1785                        // Вставляем значение в поле
1786                        const discountInput = content.querySelector('#ozon-discount-input');
1787                        discountInput.value = parseFloat(calculatedDiscount).toFixed(1);
1788                        
1789                        // Очищаем временное значение
1790                        await GM.deleteValue('ozon_parser_calculated_discount');
1791                        
1792                        calculateDiscountBtn.disabled = false;
1793                        calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1794                        
1795                        alert(`Скидка Ozon успешно рассчитана: ${parseFloat(calculatedDiscount).toFixed(1)}%`);
1796                    } else if (attempts >= maxAttempts) {
1797                        // Таймаут
1798                        clearInterval(checkInterval);
1799                        calculateDiscountBtn.disabled = false;
1800                        calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1801                        alert('Не удалось рассчитать скидку. Попробуйте еще раз.');
1802                    }
1803                }, 1000);
1804            } catch (error) {
1805                console.error('Ozon Product Parser: Error calculating discount:', error);
1806                calculateDiscountBtn.disabled = false;
1807                calculateDiscountBtn.textContent = 'Рассчитать автоматически';
1808                alert('Ошибка при расчете скидки: ' + error.message);
1809            }
1810        });
1811
1812        // Обработчик кнопки сохранения списка
1813        const saveListBtn = content.querySelector('#save-list-btn');
1814        saveListBtn.addEventListener('click', async () => {
1815            const textarea = content.querySelector('.ozon-parser-textarea');
1816            const listNameInput = content.querySelector('#list-name-input');
1817            const queries = textarea.value.split('\n').filter(q => q.trim());
1818            const listName = listNameInput.value.trim();
1819            
1820            if (queries.length === 0) {
1821                alert('Пожалуйста, введите хотя бы один запрос');
1822                return;
1823            }
1824            
1825            if (!listName) {
1826                alert('Пожалуйста, введите название списка');
1827                return;
1828            }
1829            
1830            // Сохраняем список
1831            const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
1832            const savedLists = JSON.parse(savedListsJson);
1833            
1834            savedLists[listName] = {
1835                queries: queries,
1836                createdAt: new Date().toISOString(),
1837                updatedAt: new Date().toISOString()
1838            };
1839            
1840            await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
1841            
1842            alert(`Список "${listName}" успешно сохранен!`);
1843            console.log(`Ozon Product Parser: List "${listName}" saved with ${queries.length} queries`);
1844        });
1845        
1846        // Обработчик кнопки загрузки списка
1847        const loadListBtn = content.querySelector('#load-list-btn');
1848        loadListBtn.addEventListener('click', async () => {
1849            await showLoadListModal(content);
1850        });
1851        
1852        // Обработчик кнопки управления расходами
1853        const manageCostsBtn = content.querySelector('#manage-costs-btn');
1854        manageCostsBtn.addEventListener('click', async () => {
1855            await showManageCostsModal();
1856        });
1857        
1858        // Обработчик кнопки парсинга
1859        const startBtn = content.querySelector('#start-parsing-btn');
1860        startBtn.addEventListener('click', async () => {
1861            const textarea = content.querySelector('.ozon-parser-textarea');
1862            const listNameInput = content.querySelector('#list-name-input');
1863            const ozonDiscountInput = content.querySelector('#ozon-discount-input');
1864            const queries = textarea.value.split('\n').filter(q => q.trim());
1865            const listName = listNameInput.value.trim() || 'Без названия';
1866            const ozonDiscount = parseFloat(ozonDiscountInput.value) || 50;
1867            
1868            if (queries.length === 0) {
1869                alert('Пожалуйста, введите хотя бы один запрос');
1870                return;
1871            }
1872            
1873            if (ozonDiscount < 0 || ozonDiscount > 100) {
1874                alert('Скидка Ozon должна быть от 0 до 100%');
1875                return;
1876            }
1877            
1878            // Сохраняем скидку Ozon
1879            await GM.setValue('ozon_parser_discount', ozonDiscount);
1880            
1881            startBtn.disabled = true;
1882            startBtn.textContent = 'Парсинг...';
1883            await startParsing(queries, listName, content);
1884        });
1885        
1886        console.log('Ozon Product Parser: Parse modal shown');
1887    }
1888
1889    // Показываем модальное окно для загрузки сохраненного списка
1890    async function showLoadListModal(parentContent) {
1891        const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
1892        const savedLists = JSON.parse(savedListsJson);
1893        const listNames = Object.keys(savedLists);
1894        
1895        if (listNames.length === 0) {
1896            alert('Нет сохраненных списков');
1897            return;
1898        }
1899        
1900        const loadModal = document.createElement('div');
1901        loadModal.className = 'ozon-parser-modal';
1902        loadModal.style.zIndex = '10002';
1903        
1904        const loadContent = document.createElement('div');
1905        loadContent.className = 'ozon-parser-modal-content';
1906        loadContent.style.maxWidth = '600px';
1907        
1908        let listsHTML = '<div class="ozon-parser-modal-header">Выберите список</div><div class="ozon-parser-modal-body">';
1909        
1910        listNames.forEach(listName => {
1911            const list = savedLists[listName];
1912            const date = new Date(list.createdAt).toLocaleDateString('ru-RU');
1913            const queriesCount = list.queries.length;
1914            listsHTML += `
1915                <div style="padding: 15px; background: white; border: 2px solid #e0e0e0; border-radius: 8px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center; transition: all 0.2s;" 
1916                     class="saved-list-item" data-list-name="${listName}">
1917                    <div style="flex: 1; cursor: pointer;" class="saved-list-info">
1918                        <div style="font-weight: 600; font-size: 16px; margin-bottom: 5px;">${listName}</div>
1919                        <div style="font-size: 13px; color: #666;">Запросов: ${queriesCount} | Создан: ${date}</div>
1920                    </div>
1921                    <button class="ozon-parser-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); padding: 8px 16px; font-size: 13px; margin-left: 10px;" data-delete-list="${listName}">Удалить</button>
1922                </div>
1923            `;
1924        });
1925        
1926        listsHTML += '</div><div class="ozon-parser-modal-footer"><button class="ozon-parser-btn" id="close-load-modal">Отмена</button></div>';
1927        
1928        loadContent.innerHTML = listsHTML;
1929        loadModal.appendChild(loadContent);
1930        document.body.appendChild(loadModal);
1931        
1932        // Обработчик выбора списка (только для info блока)
1933        loadContent.querySelectorAll('.saved-list-info').forEach(info => {
1934            info.addEventListener('click', () => {
1935                const listItem = info.closest('.saved-list-item');
1936                const listName = listItem.getAttribute('data-list-name');
1937                const list = savedLists[listName];
1938                
1939                // Заполняем textarea
1940                const textarea = parentContent.querySelector('.ozon-parser-textarea');
1941                const listNameInput = parentContent.querySelector('#list-name-input');
1942                textarea.value = list.queries.join('\n');
1943                listNameInput.value = listName;
1944                
1945                loadModal.remove();
1946            });
1947            
1948            // Hover эффект
1949            const listItem = info.closest('.saved-list-item');
1950            info.addEventListener('mouseenter', () => {
1951                listItem.style.borderColor = '#005bff';
1952                listItem.style.background = '#f8f9fa';
1953            });
1954            info.addEventListener('mouseleave', () => {
1955                listItem.style.borderColor = '#e0e0e0';
1956                listItem.style.background = 'white';
1957            });
1958        });
1959        
1960        // Обработчик удаления списка
1961        loadContent.querySelectorAll('[data-delete-list]').forEach(deleteBtn => {
1962            deleteBtn.addEventListener('click', async (e) => {
1963                e.stopPropagation();
1964                const listName = deleteBtn.getAttribute('data-delete-list');
1965                
1966                if (!confirm(`Вы уверены, что хотите удалить список "${listName}"?`)) {
1967                    return;
1968                }
1969                
1970                // Удаляем список из savedLists
1971                delete savedLists[listName];
1972                await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
1973                
1974                // Также удаляем результаты парсинга для этого списка
1975                const listResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
1976                const listResults = JSON.parse(listResultsJson);
1977                delete listResults[listName];
1978                await GM.setValue('ozon_parser_list_results', JSON.stringify(listResults));
1979                
1980                console.log(`Ozon Product Parser: List "${listName}" deleted from load modal`);
1981                
1982                // Удаляем элемент из DOM
1983                const listItem = deleteBtn.closest('.saved-list-item');
1984                listItem.remove();
1985                
1986                // Если списков не осталось, закрываем модальное окно
1987                const remainingLists = loadContent.querySelectorAll('.saved-list-item');
1988                if (remainingLists.length === 0) {
1989                    alert('Все списки удалены');
1990                    loadModal.remove();
1991                }
1992            });
1993        });
1994        
1995        // Закрытие
1996        loadContent.querySelector('#close-load-modal').addEventListener('click', () => {
1997            loadModal.remove();
1998        });
1999        
2000        loadModal.addEventListener('click', (e) => {
2001            if (e.target === loadModal) {
2002                loadModal.remove();
2003            }
2004        });
2005    }
2006
2007    // Показываем модальное окно управления расходами
2008    async function showManageCostsModal() {
2009        const costsJson = await GM.getValue('ozon_parser_costs', '{}');
2010        const costs = JSON.parse(costsJson);
2011        
2012        const modal = document.createElement('div');
2013        modal.className = 'ozon-parser-modal';
2014        modal.style.zIndex = '10002';
2015        
2016        const content = document.createElement('div');
2017        content.className = 'ozon-parser-modal-content';
2018        content.style.maxWidth = '800px';
2019        
2020        content.innerHTML = `
2021            <div class="ozon-parser-modal-header">Управление расходами</div>
2022            <div class="ozon-parser-modal-body">
2023                <div class="ozon-parser-info">
2024                    Укажите расходы для ваших товаров (SKU). Эти данные будут использоваться для расчета прибыли и оптимальной цены.
2025                </div>
2026                
2027                <!-- Блок загрузки файла -->
2028                <div style="margin-bottom: 20px; padding: 15px; background: #f8f9fa; border-radius: 8px;">
2029                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Загрузить данные из файла:</label>
2030                    <div style="display: flex; gap: 10px; align-items: center; margin-bottom: 10px;">
2031                        <input type="file" id="cost-file-input" accept=".csv,.json,.txt" style="flex: 1; padding: 8px; border: 2px solid #e0e0e0; border-radius: 8px;">
2032                        <button class="ozon-parser-btn" id="upload-costs-btn">Загрузить</button>
2033                    </div>
2034                    <details style="margin-top: 10px;">
2035                        <summary style="cursor: pointer; font-size: 12px; color: #666;">Формат файлов</summary>
2036                        <div style="font-size: 12px; color: #666; margin-top: 8px; line-height: 1.6;">
2037                            <strong>CSV/TXT:</strong> SKU,Себестоимость,Комиссия,Доставка<br>
2038                            Пример: 320244429,158.4,50,90<br><br>
2039                            <strong>JSON:</strong> {"SKU": {"cost": 158.4, "commission": 0.5, "delivery": 90}}<br>
2040                            Примечание: Комиссия в CSV указывается в процентах (50), в JSON - как десятичная дробь (0.5)
2041                        </div>
2042                    </details>
2043                </div>
2044                
2045                <div style="margin-bottom: 15px;">
2046                    <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Добавить новый товар:</label>
2047                    <div style="display: grid; grid-template-columns: 2fr 1fr 1fr 1fr auto; gap: 10px; align-items: end;">
2048                        <div>
2049                            <label style="font-size: 12px; color: #666;">SKU</label>
2050                            <input type="text" class="ozon-parser-search" id="new-sku" placeholder="320244429" style="margin-bottom: 0;">
2051                        </div>
2052                        <div>
2053                            <label style="font-size: 12px; color: #666;">Себестоимость (₽)</label>
2054                            <input type="number" class="ozon-parser-search" id="new-cost" placeholder="158.4" step="0.01" style="margin-bottom: 0;">
2055                        </div>
2056                        <div>
2057                            <label style="font-size: 12px; color: #666;">Комиссия (%)</label>
2058                            <input type="number" class="ozon-parser-search" id="new-commission" placeholder="50" step="0.1" style="margin-bottom: 0;">
2059                        </div>
2060                        <div>
2061                            <label style="font-size: 12px; color: #666;">Доставка (₽)</label>
2062                            <input type="number" class="ozon-parser-search" id="new-delivery" placeholder="90" step="0.01" style="margin-bottom: 0;">
2063                        </div>
2064                        <button class="ozon-parser-btn" id="add-cost-btn" style="margin-bottom: 0;">Добавить</button>
2065                    </div>
2066                </div>
2067                <div id="costs-list" style="max-height: 400px; overflow-y: auto;">
2068                    ${Object.keys(costs).length === 0 ? '<p style="text-align: center; color: #999;">Нет добавленных товаров</p>' : ''}
2069                </div>
2070            </div>
2071            <div class="ozon-parser-modal-footer">
2072                <button class="ozon-parser-btn" id="close-costs-modal">Закрыть</button>
2073            </div>
2074        `;
2075        
2076        modal.appendChild(content);
2077        document.body.appendChild(modal);
2078        
2079        // Функция для парсинга CSV
2080        function parseCSV(text) {
2081            const lines = text.trim().split('\n');
2082            const result = {};
2083            
2084            for (let i = 0; i < lines.length; i++) {
2085                const line = lines[i].trim();
2086                if (!line || line.startsWith('SKU')) continue; // Пропускаем заголовок
2087                
2088                const parts = line.split(',').map(p => p.trim());
2089                if (parts.length < 4) continue;
2090                
2091                // Удаляем кавычки из SKU, если они есть
2092                let sku = parts[0].replace(/^["']|["']$/g, '');
2093                const cost = parseFloat(parts[1]);
2094                const commission = parseFloat(parts[2]);
2095                const delivery = parseFloat(parts[3]);
2096                
2097                if (sku && !isNaN(cost) && !isNaN(commission) && !isNaN(delivery)) {
2098                    result[sku] = {
2099                        cost: cost,
2100                        commission: commission / 100, // Конвертируем проценты в десятичную дробь
2101                        delivery: delivery
2102                    };
2103                }
2104            }
2105            
2106            return result;
2107        }
2108        
2109        // Функция для парсинга JS-объекта из txt файла
2110        function parseJSObject(text) {
2111            try {
2112                // Удаляем возможные комментарии и лишние пробелы
2113                let cleanText = text.trim();
2114                
2115                // Если текст не начинается с {, добавляем открывающую скобку
2116                if (!cleanText.startsWith('{')) {
2117                    cleanText = '{' + cleanText;
2118                }
2119                
2120                // Если текст не заканчивается на }, добавляем закрывающую скобку
2121                if (!cleanText.endsWith('}')) {
2122                    // Удаляем последнюю запятую, если она есть
2123                    cleanText = cleanText.replace(/,\s*$/, '');
2124                    cleanText = cleanText + '}';
2125                }
2126                
2127                console.log('Ozon Product Parser: Attempting to parse JS object, length:', cleanText.length);
2128                
2129                // Используем eval для парсинга JS объекта (безопасно, т.к. это локальный файл пользователя)
2130                const result = eval('(' + cleanText + ')');
2131                
2132                // Валидация формата
2133                if (typeof result !== 'object' || result === null) {
2134                    throw new Error('Неверный формат: ожидается объект');
2135                }
2136                
2137                // Проверяем структуру данных
2138                for (const sku in result) {
2139                    const item = result[sku];
2140                    if (typeof item !== 'object' || item === null) {
2141                        throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается объект с полями cost, commission, delivery`);
2142                    }
2143                    if (typeof item.cost !== 'number' || typeof item.commission !== 'number' || typeof item.delivery !== 'number') {
2144                        throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается: { cost: число, commission: число (0-1), delivery: число }. Получено: ${JSON.stringify(item)}`);
2145                    }
2146                }
2147                
2148                console.log('Ozon Product Parser: Successfully parsed JS object with', Object.keys(result).length, 'items');
2149                return result;
2150            } catch (error) {
2151                console.error('Ozon Product Parser: JS object parse error:', error);
2152                throw new Error(`Ошибка парсинга JS-объекта: ${error.message}`);
2153            }
2154        }
2155        
2156        // Обработчик загрузки файла
2157        const uploadBtn = content.querySelector('#upload-costs-btn');
2158        uploadBtn.addEventListener('click', async () => {
2159            const fileInput = content.querySelector('#cost-file-input');
2160            const file = fileInput.files[0];
2161            
2162            if (!file) {
2163                alert('Пожалуйста, выберите файл');
2164                return;
2165            }
2166            
2167            try {
2168                const text = await file.text();
2169                let newCosts = {};
2170                
2171                console.log('Ozon Product Parser: Processing file:', file.name, 'Size:', file.size, 'bytes');
2172                
2173                if (file.name.endsWith('.json')) {
2174                    // Парсим JSON
2175                    try {
2176                        newCosts = JSON.parse(text);
2177                        console.log('Ozon Product Parser: Parsed JSON successfully');
2178                    } catch (jsonError) {
2179                        console.error('Ozon Product Parser: JSON parse error:', jsonError);
2180                        throw new Error(`Ошибка парсинга JSON: ${jsonError.message}. Проверьте, что файл содержит корректный JSON формат.`);
2181                    }
2182                    
2183                    // Валидация JSON формата
2184                    for (const sku in newCosts) {
2185                        const item = newCosts[sku];
2186                        if (typeof item !== 'object' || item === null) {
2187                            throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается объект с полями cost, commission, delivery`);
2188                        }
2189                        if (typeof item.cost !== 'number' || typeof item.commission !== 'number' || typeof item.delivery !== 'number') {
2190                            throw new Error(`Неверный формат данных для SKU ${sku}. Ожидается: {"cost": число, "commission": число, "delivery": число}. Получено: ${JSON.stringify(item)}`);
2191                        }
2192                    }
2193                } else if (file.name.endsWith('.txt')) {
2194                    // Пытаемся определить формат: JS-объект или CSV
2195                    const trimmedText = text.trim();
2196                    console.log('Ozon Product Parser: Processing TXT file, first 100 chars:', trimmedText.substring(0, 100));
2197                    
2198                    if (trimmedText.startsWith('{') || trimmedText.includes('{')) {
2199                        // JS-объект формат
2200                        console.log('Ozon Product Parser: Detected JS object format');
2201                        newCosts = parseJSObject(text);
2202                    } else {
2203                        // CSV формат
2204                        console.log('Ozon Product Parser: Detected CSV format');
2205                        newCosts = parseCSV(text);
2206                    }
2207                } else {
2208                    // CSV формат для других расширений
2209                    console.log('Ozon Product Parser: Processing as CSV format');
2210                    newCosts = parseCSV(text);
2211                }
2212                
2213                console.log('Ozon Product Parser: Extracted', Object.keys(newCosts).length, 'items from file');
2214                
2215                if (Object.keys(newCosts).length === 0) {
2216                    alert('Не удалось извлечь данные из файла. Проверьте формат.\n\nОжидаемые форматы:\n\nCSV: SKU,Себестоимость,Комиссия,Доставка\nПример: 320244429,158.4,50,90\n\nJSON: {"SKU": {"cost": 158.4, "commission": 0.5, "delivery": 90}}');
2217                    return;
2218                }
2219                
2220                // Объединяем с существующими данными
2221                const mergedCosts = { ...costs, ...newCosts };
2222                await GM.setValue('ozon_parser_costs', JSON.stringify(mergedCosts));
2223                
2224                // Обновляем costs переменную
2225                Object.assign(costs, newCosts);
2226                
2227                alert(`Успешно загружено ${Object.keys(newCosts).length} товаров`);
2228                console.log('Ozon Product Parser: Successfully saved costs data');
2229                displayCostsList();
2230                
2231                // Очищаем input
2232                fileInput.value = '';
2233            } catch (error) {
2234                console.error('Ozon Product Parser: Error uploading costs file:', error);
2235                console.error('Ozon Product Parser: Error stack:', error.stack);
2236                console.error('Ozon Product Parser: Error name:', error.name);
2237                console.error('Ozon Product Parser: Error message:', error.message);
2238                alert('Ошибка при загрузке файла:\n\n' + error.message + '\n\nПроверьте формат файла и попробуйте снова.');
2239            }
2240        });
2241        
2242        // Функция для отображения списка расходов
2243        function displayCostsList() {
2244            const costsList = content.querySelector('#costs-list');
2245            
2246            if (Object.keys(costs).length === 0) {
2247                costsList.innerHTML = '<p style="text-align: center; color: #999;">Нет добавленных товаров</p>';
2248                return;
2249            }
2250            
2251            let html = '<div style="margin-bottom: 15px; display: flex; justify-content: space-between; align-items: center;">';
2252            html += '<div style="font-size: 14px; font-weight: 600;">Всего товаров: ' + Object.keys(costs).length + '</div>';
2253            html += '<button class="ozon-parser-btn" id="delete-all-costs-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); padding: 8px 16px; font-size: 13px;">Удалить все расходы</button>';
2254            html += '</div>';
2255            html += '<table class="ozon-parser-results-table"><thead><tr><th>SKU</th><th>Себестоимость</th><th>Комиссия</th><th>Доставка</th><th>Действия</th></tr></thead><tbody>';
2256            
2257            Object.keys(costs).forEach(sku => {
2258                const cost = costs[sku];
2259                html += `
2260                    <tr>
2261                        <td><a href="https://www.ozon.ru/product/${sku}" target="_blank" class="ozon-parser-sku-link">${sku}</a></td>
2262                        <td>${cost.cost.toFixed(2)} ₽</td>
2263                        <td>${(cost.commission * 100).toFixed(2)}%</td>
2264                        <td>${cost.delivery.toFixed(2)} ₽</td>
2265                        <td>
2266                            <button class="ozon-parser-btn" data-edit-sku="${sku}" style="padding: 6px 12px; font-size: 12px; margin-right: 5px;">Изменить</button>
2267                            <button class="ozon-parser-btn" data-delete-sku="${sku}" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%); padding: 6px 12px; font-size: 12px;">Удалить</button>
2268                        </td>
2269                    </tr>
2270                `;
2271            });
2272            
2273            html += '</tbody></table>';
2274            costsList.innerHTML = html;
2275            
2276            // Обработчик для кнопки удаления всех расходов
2277            const deleteAllBtn = costsList.querySelector('#delete-all-costs-btn');
2278            if (deleteAllBtn) {
2279                deleteAllBtn.addEventListener('click', async () => {
2280                    if (confirm('Вы уверены, что хотите удалить ВСЕ данные о расходах? Это действие нельзя отменить.')) {
2281                        // Очищаем все данные
2282                        Object.keys(costs).forEach(key => delete costs[key]);
2283                        await GM.setValue('ozon_parser_costs', JSON.stringify({}));
2284                        displayCostsList();
2285                        alert('Все данные о расходах удалены');
2286                    }
2287                });
2288            }
2289            
2290            // Обработчики для кнопок удаления
2291            costsList.querySelectorAll('[data-delete-sku]').forEach(btn => {
2292                btn.addEventListener('click', async () => {
2293                    const sku = btn.getAttribute('data-delete-sku');
2294                    if (confirm(`Удалить данные о расходах для SKU ${sku}?`)) {
2295                        delete costs[sku];
2296                        await GM.setValue('ozon_parser_costs', JSON.stringify(costs));
2297                        displayCostsList();
2298                    }
2299                });
2300            });
2301            
2302            // Обработчики для кнопок изменения
2303            costsList.querySelectorAll('[data-edit-sku]').forEach(btn => {
2304                btn.addEventListener('click', () => {
2305                    const sku = btn.getAttribute('data-edit-sku');
2306                    const cost = costs[sku];
2307                    
2308                    content.querySelector('#new-sku').value = sku;
2309                    content.querySelector('#new-cost').value = cost.cost;
2310                    content.querySelector('#new-commission').value = cost.commission * 100;
2311                    content.querySelector('#new-delivery').value = cost.delivery;
2312                    
2313                    content.querySelector('#new-sku').scrollIntoView({ behavior: 'smooth', block: 'center' });
2314                });
2315            });
2316        }
2317        
2318        displayCostsList();
2319        
2320        // Обработчик добавления нового товара
2321        const addBtn = content.querySelector('#add-cost-btn');
2322        addBtn.addEventListener('click', async () => {
2323            const sku = content.querySelector('#new-sku').value.trim();
2324            const cost = parseFloat(content.querySelector('#new-cost').value);
2325            const commission = parseFloat(content.querySelector('#new-commission').value);
2326            const delivery = parseFloat(content.querySelector('#new-delivery').value);
2327            
2328            if (!sku) {
2329                alert('Пожалуйста, введите SKU');
2330                return;
2331            }
2332            
2333            if (isNaN(cost) || cost < 0) {
2334                alert('Пожалуйста, введите корректную себестоимость');
2335                return;
2336            }
2337            
2338            if (isNaN(commission) || commission < 0 || commission > 100) {
2339                alert('Пожалуйста, введите корректную комиссию (0-100%)');
2340                return;
2341            }
2342            
2343            if (isNaN(delivery) || delivery < 0) {
2344                alert('Пожалуйста, введите корректную стоимость доставки');
2345                return;
2346            }
2347            
2348            costs[sku] = {
2349                cost: cost,
2350                commission: commission / 100, // Сохраняем как десятичную дробь
2351                delivery: delivery
2352            };
2353            
2354            await GM.setValue('ozon_parser_costs', JSON.stringify(costs));
2355            
2356            // Очищаем поля
2357            content.querySelector('#new-sku').value = '';
2358            content.querySelector('#new-cost').value = '';
2359            content.querySelector('#new-commission').value = '';
2360            content.querySelector('#new-delivery').value = '';
2361            
2362            displayCostsList();
2363        });
2364        
2365        // Закрытие
2366        content.querySelector('#close-costs-modal').addEventListener('click', () => {
2367            modal.remove();
2368        });
2369        
2370        modal.addEventListener('click', (e) => {
2371            if (e.target === modal) {
2372                modal.remove();
2373            }
2374        });
2375    }
2376
2377    // Извлекаем скидку со страницы управления ценами
2378    async function extractDiscountFromPage() {
2379        console.log('Ozon Product Parser: Extracting discount from prices page');
2380        
2381        // Ждем загрузки таблицы с ценами
2382        await waitForPricesTable();
2383        
2384        // Прокручиваем страницу вниз для загрузки цен (ленивая загрузка)
2385        console.log('Ozon Product Parser: Scrolling to load prices');
2386        for (let i = 0; i < 3; i++) {
2387            window.scrollBy(0, 500);
2388            await new Promise(resolve => setTimeout(resolve, 2000));
2389        }
2390        
2391        try {
2392            // Ищем цены по правильным классам
2393            // Цена продажи (с картой Ozon): index_priceByOzonCardCurrency_3DLKf
2394            // Базовая цена (цена поручения): index_priceAmount_3dfpL
2395            
2396            const salePriceElements = document.querySelectorAll('.index_priceByOzonCardCurrency_3DLKf');
2397            const basePriceElements = document.querySelectorAll('.index_priceAmount_3dfpL');
2398            
2399            console.log(`Ozon Product Parser: Found ${salePriceElements.length} sale prices and ${basePriceElements.length} base prices`);
2400            
2401            if (salePriceElements.length === 0 || basePriceElements.length === 0) {
2402                console.error('Ozon Product Parser: Could not find price elements on the page');
2403                alert('Не удалось найти цены на странице. Убедитесь, что у вас есть товары с ценами.');
2404                await GM.setValue('ozon_parser_calculate_discount', 'false');
2405                return;
2406            }
2407            
2408            // Собираем пары цен (базовая и продажная)
2409            let validDiscounts = [];
2410            
2411            // Ищем строки таблицы с обеими ценами
2412            const rows = document.querySelectorAll('tr');
2413            for (const row of rows) {
2414                const salePrice = row.querySelector('.index_priceByOzonCardCurrency_3DLKf');
2415                const basePrice = row.querySelector('.index_priceAmount_3dfpL');
2416                
2417                if (salePrice && basePrice) {
2418                    const salePriceText = salePrice.textContent.trim();
2419                    const basePriceText = basePrice.textContent.trim();
2420                    
2421                    // Парсим цены (убираем пробелы и символ рубля)
2422                    const salePriceValue = parseFloat(salePriceText.replace(/\s/g, '').replace('₽', '').replace(',', '.'));
2423                    const basePriceValue = parseFloat(basePriceText.replace(/\s/g, '').replace('₽', '').replace(',', '.'));
2424                    
2425                    if (!isNaN(salePriceValue) && !isNaN(basePriceValue) && basePriceValue > 0 && salePriceValue > 0) {
2426                        // Рассчитываем скидку: (базовая цена - цена продажи) / базовая цена * 100
2427                        const discountAmount = basePriceValue - salePriceValue;
2428                        const discountPercent = (discountAmount / basePriceValue) * 100;
2429                        
2430                        if (discountPercent > 0 && discountPercent < 100) {
2431                            validDiscounts.push({
2432                                basePrice: basePriceValue,
2433                                salePrice: salePriceValue,
2434                                discount: discountPercent
2435                            });
2436                            
2437                            console.log(`Ozon Product Parser: Base price: ${basePriceValue}₽, Sale price: ${salePriceValue}₽, Discount: ${discountPercent.toFixed(1)}%`);
2438                        }
2439                    }
2440                }
2441            }
2442            
2443            if (validDiscounts.length === 0) {
2444                console.error('Ozon Product Parser: No valid price pairs found');
2445                alert('Не удалось найти валидные пары цен на странице.');
2446                await GM.setValue('ozon_parser_calculate_discount', 'false');
2447                return;
2448            }
2449            
2450            // Рассчитываем среднюю скидку
2451            const avgDiscount = validDiscounts.reduce((sum, item) => sum + item.discount, 0) / validDiscounts.length;
2452            
2453            console.log(`Ozon Product Parser: Calculated average discount from ${validDiscounts.length} products: ${avgDiscount.toFixed(1)}%`);
2454            
2455            // Сохраняем рассчитанную скидку для передачи в основное окно
2456            await GM.setValue('ozon_parser_calculated_discount', avgDiscount);
2457            
2458            // Сбрасываем флаг расчета
2459            await GM.setValue('ozon_parser_calculate_discount', 'false');
2460            
2461            // Закрываем текущую вкладку
2462            window.close();
2463        } catch (error) {
2464            console.error('Ozon Product Parser: Error extracting discount:', error);
2465            alert('Ошибка при извлечении данных о скидке: ' + error.message);
2466            
2467            // Сбрасываем флаг расчета
2468            await GM.setValue('ozon_parser_calculate_discount', 'false');
2469        }
2470    }
2471
2472    // Ждем появления таблицы с ценами
2473    function waitForPricesTable() {
2474        return new Promise((resolve) => {
2475            const checkTable = () => {
2476                const rows = document.querySelectorAll('tr');
2477                if (rows.length > 0) {
2478                    console.log('Ozon Product Parser: Prices table found');
2479                    // Дополнительная задержка для полной загрузки данных
2480                    setTimeout(resolve, 3000);
2481                } else {
2482                    setTimeout(checkTable, 1000);
2483                }
2484            };
2485            checkTable();
2486        });
2487    }
2488
2489    // Прокручиваем страницу для подгрузки товаров
2490    async function scrollToLoadProducts(targetCount = 20) {
2491        console.log(`Ozon Product Parser: Scrolling to load ${targetCount} products`);
2492        
2493        const table = document.querySelector('#mpstat-ozone-search-result table');
2494        if (!table) {
2495            console.error('Ozon Product Parser: Table not found for scrolling');
2496            return;
2497        }
2498        
2499        let previousRowCount = 0;
2500        let attempts = 0;
2501        const maxAttempts = 10;
2502        
2503        while (attempts < maxAttempts) {
2504            const rows = table.querySelectorAll('tbody tr');
2505            const currentRowCount = rows.length;
2506            
2507            console.log(`Ozon Product Parser: Current row count: ${currentRowCount}, target: ${targetCount}`);
2508            
2509            if (currentRowCount >= targetCount) {
2510                console.log(`Ozon Product Parser: Loaded ${currentRowCount} products`);
2511                break;
2512            }
2513            
2514            // Если количество строк не изменилось, значит больше товаров нет
2515            if (currentRowCount === previousRowCount && attempts > 2) {
2516                console.log(`Ozon Product Parser: No more products to load (${currentRowCount} total)`);
2517                break;
2518            }
2519            
2520            previousRowCount = currentRowCount;
2521            
2522            // Прокручиваем к последней строке таблицы
2523            const lastRow = rows[rows.length - 1];
2524            if (lastRow) {
2525                lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2526            }
2527            
2528            // Также прокручиваем окно вниз
2529            window.scrollBy(0, 500);
2530            
2531            // Ждем подгрузки новых товаров
2532            await new Promise(resolve => setTimeout(resolve, 2000));
2533            attempts++;
2534        }
2535        
2536        console.log(`Ozon Product Parser: Scrolling completed after ${attempts} attempts`);
2537    }
2538
2539    // Начинаем парсинг
2540    async function startParsing(queries, listName, modalContent) {
2541        const progressDiv = modalContent.querySelector('.ozon-parser-progress');
2542        progressDiv.style.display = 'block';
2543        
2544        // Сохраняем список запросов и название списка
2545        await GM.setValue('ozon_parser_queries', JSON.stringify(queries));
2546        await GM.setValue('ozon_parser_current_list_name', listName);
2547        await GM.setValue('ozon_parser_current_index', 0);
2548        await GM.setValue('ozon_parser_active', 'true');
2549        console.log(`Ozon Product Parser: Starting parsing process for list "${listName}"`);
2550        
2551        // Переходим к первому запросу
2552        const firstQuery = queries[0].trim();
2553        const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(firstQuery)}&from_global=true`;
2554        window.location.href = searchUrl;
2555    }
2556
2557    // Продолжаем парсинг после загрузки страницы
2558    async function continueParsingIfActive() {
2559        const isActive = await GM.getValue('ozon_parser_active', 'false');
2560        if (isActive !== 'true') {
2561            return;
2562        }
2563        
2564        console.log('Ozon Product Parser: Continuing parsing process');
2565        
2566        // Ждем появления таблицы
2567        await waitForTable();
2568        
2569        // Получаем текущее состояние
2570        const queriesJson = await GM.getValue('ozon_parser_queries', '[]');
2571        const queries = JSON.parse(queriesJson);
2572        const currentIndex = await GM.getValue('ozon_parser_current_index', 0);
2573        const currentListName = await GM.getValue('ozon_parser_current_list_name', 'Без названия');
2574        
2575        // Получаем результаты для текущего списка
2576        const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
2577        const allListResults = JSON.parse(allListResultsJson);
2578        
2579        if (!allListResults[currentListName]) {
2580            allListResults[currentListName] = {
2581                queries: {},
2582                createdAt: new Date().toISOString(),
2583                updatedAt: new Date().toISOString()
2584            };
2585        }
2586        
2587        if (currentIndex >= queries.length) {
2588            // Парсинг завершен
2589            await GM.setValue('ozon_parser_active', 'false');
2590            
2591            // Обновляем дату последнего обновления списка
2592            allListResults[currentListName].updatedAt = new Date().toISOString();
2593            await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2594            
2595            console.log('Ozon Product Parser: Parsing completed');
2596            return;
2597        }
2598        
2599        const currentQuery = queries[currentIndex].trim();
2600        console.log(`Ozon Product Parser: Processing query ${currentIndex + 1}/${queries.length}: "${currentQuery}"`);
2601        
2602        // Парсим данные текущей страницы
2603        const products = await parseProducts();
2604        allListResults[currentListName].queries[currentQuery] = products;
2605        
2606        // Анализируем высокие цены
2607        await analyzeHighPrices(currentQuery, products);
2608        
2609        // Сохраняем результаты
2610        await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2611        console.log(`Ozon Product Parser: Parsed ${products.length} products for "${currentQuery}" in list "${currentListName}"`);
2612        
2613        // Переходим к следующему запросу
2614        const nextIndex = currentIndex + 1;
2615        await GM.setValue('ozon_parser_current_index', nextIndex);
2616        
2617        if (nextIndex < queries.length) {
2618            // Есть еще запросы - переходим к следующему
2619            const nextQuery = queries[nextIndex].trim();
2620            const searchUrl = `https://www.ozon.ru/search/?text=${encodeURIComponent(nextQuery)}&from_global=true`;
2621            setTimeout(() => {
2622                window.location.href = searchUrl;
2623            }, 2000); // Небольшая задержка между запросами
2624        } else {
2625            // Все запросы обработаны
2626            await GM.setValue('ozon_parser_active', 'false');
2627            
2628            // Обновляем дату последнего обновления списка
2629            allListResults[currentListName].updatedAt = new Date().toISOString();
2630            await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
2631            
2632            console.log('Ozon Product Parser: All queries processed');
2633            
2634            // Показываем результаты
2635            setTimeout(() => {
2636                showResultsModal();
2637            }, 1000);
2638        }
2639    }
2640
2641    // Продолжаем расчет скидки после загрузки страницы
2642    async function continueDiscountCalculationIfActive() {
2643        const shouldCalculate = await GM.getValue('ozon_parser_calculate_discount', 'false');
2644        if (shouldCalculate === 'true') {
2645            await GM.setValue('ozon_parser_calculate_discount', 'false');
2646            console.log('Ozon Product Parser: Continuing discount calculation');
2647            await extractDiscountFromPage();
2648        }
2649    }
2650
2651    // Ждем появления таблицы
2652    function waitForTable() {
2653        return new Promise((resolve) => {
2654            const checkTable = () => {
2655                const table = document.querySelector('#mpstat-ozone-search-result table tbody');
2656                if (table && table.querySelectorAll('tr').length > 0) {
2657                    console.log('Ozon Product Parser: Table found');
2658                    // Дополнительная задержка для полной загрузки данных
2659                    setTimeout(resolve, 5000);
2660                } else {
2661                    setTimeout(checkTable, 1000);
2662                }
2663            };
2664            checkTable();
2665        });
2666    }
2667
2668    // Прокручиваем страницу для подгрузки товаров
2669    async function scrollToLoadProducts(targetCount = 20) {
2670        console.log(`Ozon Product Parser: Scrolling to load ${targetCount} products`);
2671        
2672        const table = document.querySelector('#mpstat-ozone-search-result table');
2673        if (!table) {
2674            console.error('Ozon Product Parser: Table not found for scrolling');
2675            return;
2676        }
2677        
2678        let previousRowCount = 0;
2679        let attempts = 0;
2680        const maxAttempts = 10;
2681        
2682        while (attempts < maxAttempts) {
2683            const rows = table.querySelectorAll('tbody tr');
2684            const currentRowCount = rows.length;
2685            
2686            console.log(`Ozon Product Parser: Current row count: ${currentRowCount}, target: ${targetCount}`);
2687            
2688            if (currentRowCount >= targetCount) {
2689                console.log(`Ozon Product Parser: Loaded ${currentRowCount} products`);
2690                break;
2691            }
2692            
2693            // Если количество строк не изменилось, значит больше товаров нет
2694            if (currentRowCount === previousRowCount && attempts > 2) {
2695                console.log(`Ozon Product Parser: No more products to load (${currentRowCount} total)`);
2696                break;
2697            }
2698            
2699            previousRowCount = currentRowCount;
2700            
2701            // Прокручиваем к последней строке таблицы
2702            const lastRow = rows[rows.length - 1];
2703            if (lastRow) {
2704                lastRow.scrollIntoView({ behavior: 'smooth', block: 'end' });
2705            }
2706            
2707            // Также прокручиваем окно вниз
2708            window.scrollBy(0, 500);
2709            
2710            // Ждем подгрузки новых товаров
2711            await new Promise(resolve => setTimeout(resolve, 2000));
2712            attempts++;
2713        }
2714        
2715        console.log(`Ozon Product Parser: Scrolling completed after ${attempts} attempts`);
2716    }
2717
2718    // Парсим товары из таблицы
2719    async function parseProducts() {
2720        const table = document.querySelector('#mpstat-ozone-search-result table');
2721        if (!table) {
2722            console.error('Ozon Product Parser: Table not found');
2723            return [];
2724        }
2725        
2726        // Прокручиваем страницу для подгрузки товаров
2727        await scrollToLoadProducts(20);
2728        
2729        const rows = table.querySelectorAll('tbody tr');
2730        const products = [];
2731        const maxProducts = Math.min(20, rows.length);
2732        
2733        // Получаем названия товаров из карточек на странице
2734        const productLinks = document.querySelectorAll('a[href*="/product/"]');
2735        const productNames = new Map();
2736        
2737        console.log(`Ozon Product Parser: Found ${productLinks.length} product links`);
2738        
2739        productLinks.forEach(link => {
2740            const href = link.getAttribute('href');
2741            const skuMatch = href.match(/\/product\/[^/]+-(\d+)/);
2742            if (skuMatch) {
2743                const sku = skuMatch[1];
2744                // Ищем название в родительском элементе
2745                const parent = link.closest('.tile-root') || link.closest('[data-index]');
2746                if (parent) {
2747                    const nameElement = parent.querySelector('.tsBody500Medium');
2748                    if (nameElement && nameElement.textContent.trim().length > 10) {
2749                        productNames.set(sku, nameElement.textContent.trim());
2750                    }
2751                }
2752            }
2753        });
2754        
2755        console.log(`Ozon Product Parser: Extracted ${productNames.size} product names`);
2756        
2757        // Функция для извлечения количества единиц из названия
2758        function extractQuantity(name) {
2759            if (!name) return null;
2760            
2761            // Ищем паттерны: "120 капсул", "60 таблеток", "180шт", "90 шт"
2762            const patterns = [
2763                /(\d+)\s*(?:капсул|капс|caps)/i,
2764                /(\d+)\s*(?:таблеток|табл|tablets|tabs)/i,
2765                /(\d+)\s*(?:штук|шт|pcs|pieces)/i,
2766                /(\d+)\s*(?:порций|servings)/i
2767            ];
2768            
2769            for (const pattern of patterns) {
2770                const match = name.match(pattern);
2771                if (match) {
2772                    const quantity = parseInt(match[1]);
2773                    if (quantity > 0 && quantity <= 1000) { // Разумные пределы
2774                        return quantity;
2775                    }
2776                }
2777            }
2778            
2779            return null;
2780        }
2781        
2782        for (let i = 0; i < maxProducts; i++) {
2783            const row = rows[i];
2784            const cells = row.querySelectorAll('td');
2785            if (cells.length < 8) continue;
2786            
2787            const position = cells[0]?.textContent.trim() || '';
2788            const sku = cells[2]?.textContent.trim() || '';
2789            const brand = cells[3]?.textContent.trim() || '';
2790            const priceText = cells[4]?.textContent.trim() || '';
2791            const revenueText = cells[6]?.textContent.trim() || '';
2792            const ordersText = cells[7]?.textContent.trim() || '';
2793            
2794            // Получаем название товара из карточки
2795            const name = productNames.get(sku) || '';
2796            
2797            // Извлекаем количество единиц
2798            const quantity = extractQuantity(name);
2799            
2800            // Проверяем, есть ли товар от GLS или Skinphoria
2801            const isTargetBrand = brand.includes('GLS Pharmaceuticals') || brand.includes('Skinphoria');
2802            
2803            // Парсим числовые значения
2804            const price = parseFloat(priceText.replace(/[^\d]/g, '')) || 0;
2805            const revenue = parseFloat(revenueText.replace(/[^\d]/g, '')) || 0;
2806            const orders = parseInt(ordersText.replace(/[^\d]/g, '')) || 0;
2807            
2808            // Рассчитываем цену за единицу
2809            const pricePerUnit = quantity && price > 0 ? price / quantity : null;
2810            
2811            products.push({
2812                position: parseInt(position) || (i + 1),
2813                sku,
2814                name,
2815                brand,
2816                price,
2817                revenue,
2818                orders,
2819                isTargetBrand,
2820                quantity,
2821                pricePerUnit
2822            });
2823        }
2824        
2825        // Сортируем по убыванию выручки
2826        products.sort((a, b) => b.revenue - a.revenue);
2827        console.log(`Ozon Product Parser: Parsed ${products.length} products, ${products.filter(p => p.isTargetBrand).length} from target brands`);
2828        return products;
2829    }
2830    
2831    // Анализируем высокие цены после парсинга
2832    async function analyzeHighPrices(query, products) {
2833        console.log(`Ozon Product Parser: Analyzing high prices for query "${query}"`);
2834        
2835        // Получаем топ-5 конкурентов по выручке
2836        const competitors = products.filter(p => !p.isTargetBrand && p.price > 0);
2837        const top5Competitors = competitors.slice(0, 5);
2838        
2839        if (top5Competitors.length === 0) {
2840            console.log('Ozon Product Parser: No competitors found for high price analysis');
2841            return;
2842        }
2843        
2844        // Находим минимальную цену среди топ-5 конкурентов
2845        const minCompetitorPrice = Math.min(...top5Competitors.map(p => p.price));
2846        console.log(`Ozon Product Parser: Min competitor price in top-5: ${minCompetitorPrice}`);
2847        
2848        // Проверяем наши товары
2849        const ourProducts = products.filter(p => p.isTargetBrand);
2850        const highPriceProducts = [];
2851        
2852        for (const product of ourProducts) {
2853            if (product.price > 0) {
2854                const priceDiff = ((product.price - minCompetitorPrice) / minCompetitorPrice) * 100;
2855                
2856                if (priceDiff > 10) {
2857                    console.log(`Ozon Product Parser: High price detected - SKU ${product.sku}: ${product.price}₽ vs ${minCompetitorPrice}₽ (+${priceDiff.toFixed(1)}%)`);
2858                    
2859                    highPriceProducts.push({
2860                        sku: product.sku,
2861                        name: product.name,
2862                        ourPrice: product.price,
2863                        competitorPrice: minCompetitorPrice,
2864                        query: query
2865                    });
2866                }
2867            }
2868        }
2869        
2870        if (highPriceProducts.length > 0) {
2871            // Добавляем в общий список высоких цен
2872            const existingDataJson = await GM.getValue('ozon_parser_high_price', '[]');
2873            const existingData = JSON.parse(existingDataJson);
2874            
2875            // Удаляем дубликаты по SKU + query
2876            const existingKeys = new Set(existingData.map(item => `${item.sku}_${item.query}`));
2877            const newProducts = highPriceProducts.filter(item => !existingKeys.has(`${item.sku}_${item.query}`));
2878            
2879            if (newProducts.length > 0) {
2880                const updatedData = [...existingData, ...newProducts];
2881                await GM.setValue('ozon_parser_high_price', JSON.stringify(updatedData));
2882                console.log(`Ozon Product Parser: Added ${newProducts.length} products to high price list`);
2883                
2884                // Обновляем счетчик
2885                await updateHighPriceCounter();
2886            }
2887        }
2888    }
2889
2890    // Анализируем данные для запроса
2891    async function analyzeProducts(products) {
2892        console.log('Ozon Product Parser: Starting analysis for', products.length, 'products');
2893        
2894        if (!products || products.length === 0) {
2895            console.error('Ozon Product Parser: No products to analyze');
2896            return null;
2897        }
2898
2899        try {
2900            // Получаем данные о расходах и скидке Ozon
2901            const costsJson = await GM.getValue('ozon_parser_costs', '{}');
2902            const costs = JSON.parse(costsJson);
2903            const ozonDiscount = await GM.getValue('ozon_parser_discount', 50);
2904            console.log(`Ozon Product Parser: Loaded costs for ${Object.keys(costs).length} SKUs, Ozon discount: ${ozonDiscount}%`);
2905
2906            // Общая выручка и заказы
2907            const totalRevenue = products.reduce((sum, p) => sum + p.revenue, 0);
2908            const totalOrders = products.reduce((sum, p) => sum + p.orders, 0);
2909
2910            // Топ-5 товаров по выручке
2911            const topProducts = products.slice(0, 5).map(p => ({
2912                name: p.name,
2913                brand: p.brand,
2914                price: p.price,
2915                revenue: p.revenue,
2916                orders: p.orders,
2917                position: p.position,
2918                revenueShare: ((p.revenue / totalRevenue) * 100).toFixed(1)
2919            }));
2920
2921            // Ценовые сегменты
2922            const prices = products.map(p => p.price).filter(p => p > 0);
2923            const minPrice = Math.min(...prices);
2924            const maxPrice = Math.max(...prices);
2925            const priceRange = maxPrice - minPrice;
2926            const segmentSize = priceRange / 4;
2927
2928            const priceSegments = [
2929                { min: minPrice, max: minPrice + segmentSize, name: 'Низкий' },
2930                { min: minPrice + segmentSize, max: minPrice + segmentSize * 2, name: 'Средний-' },
2931                { min: minPrice + segmentSize * 2, max: minPrice + segmentSize * 3, name: 'Средний+' },
2932                { min: minPrice + segmentSize * 3, max: maxPrice, name: 'Высокий' }
2933            ];
2934
2935            const segments = priceSegments.map(segment => {
2936                const segmentProducts = products.filter(p => 
2937                    p.price >= segment.min && p.price <= segment.max
2938                );
2939                const segmentRevenue = segmentProducts.reduce((sum, p) => sum + p.revenue, 0);
2940                const segmentOrders = segmentProducts.reduce((sum, p) => sum + p.orders, 0);
2941
2942                return {
2943                    name: segment.name,
2944                    priceRange: `${Math.round(segment.min)} - ${Math.round(segment.max)}`,
2945                    count: segmentProducts.length,
2946                    revenue: segmentRevenue,
2947                    orders: segmentOrders,
2948                    revenueShare: ((segmentRevenue / totalRevenue) * 100).toFixed(1),
2949                    avgPrice: segmentProducts.length > 0 
2950                        ? Math.round(segmentProducts.reduce((sum, p) => sum + p.price, 0) / segmentProducts.length)
2951                        : 0
2952                };
2953            }).filter(s => s.count > 0);
2954
2955            // Расчет эластичности запроса
2956            const competitors = products.filter(p => !p.isTargetBrand);
2957            let elasticity = null;
2958            let elasticityInterpretation = '';
2959            
2960            if (competitors.length >= 5) {
2961                const logPrices = competitors.map(p => Math.log(p.price + 1));
2962                const logOrders = competitors.map(p => Math.log(p.orders + 1));
2963                
2964                function simpleRegression(x, y) {
2965                    const n = x.length;
2966                    const sumX = x.reduce((a, b) => a + b, 0);
2967                    const sumY = y.reduce((a, b) => a + b, 0);
2968                    const sumXY = x.reduce((a, b, i) => a + b * y[i], 0);
2969                    const sumX2 = x.reduce((a, b) => a + b * b, 0);
2970                    const slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
2971                    return slope;
2972                }
2973                
2974                const rawElasticity = simpleRegression(logPrices, logOrders);
2975                const priorElasticity = -1.2;
2976                const priorWeight = 0.2;
2977                elasticity = rawElasticity * (1 - priorWeight) + priorElasticity * priorWeight;
2978                
2979                console.log(`Ozon Product Parser: Raw elasticity: ${rawElasticity.toFixed(2)}, Bayesian adjusted: ${elasticity.toFixed(2)}`);
2980                
2981                if (elasticity < -1.5) {
2982                    elasticityInterpretation = 'Высокая эластичность - спрос очень чувствителен к цене. Снижение цены сильно увеличит продажи.';
2983                } else if (elasticity < -0.8) {
2984                    elasticityInterpretation = 'Средняя эластичность - спрос умеренно реагирует на изменение цены.';
2985                } else if (elasticity < 0) {
2986                    elasticityInterpretation = 'Низкая эластичность - спрос слабо зависит от цены. Можно повышать цену.';
2987                } else {
2988                    elasticityInterpretation = 'Аномальная эластичность - возможно недостаточно данных для анализа.';
2989                }
2990            }
2991
2992            // Функция для расчета прогноза
2993            function calculateForecast(newPrice, currentPrice, currentOrders, currentRevenue, productCosts) {
2994                const priceChange = (newPrice - currentPrice) / currentPrice;
2995                const usedElasticity = elasticity !== null ? elasticity : -1.2;
2996                const ordersChange = usedElasticity * priceChange;
2997                
2998                const forecastOrders = Math.round(currentOrders * (1 + ordersChange));
2999                const forecastRevenue = Math.round(forecastOrders * newPrice);
3000                
3001                let forecastProfit = null;
3002                let currentProfit = null;
3003                let profitChange = null;
3004                
3005                if (productCosts) {
3006                    const basePrice = newPrice / (1 - ozonDiscount / 100);
3007                    const sellerRevenue = basePrice * (1 - productCosts.commission) * forecastOrders;
3008                    const expenses = (productCosts.cost + productCosts.delivery) * forecastOrders;
3009                    forecastProfit = sellerRevenue - expenses;
3010                    
3011                    const currentBasePrice = currentPrice / (1 - ozonDiscount / 100);
3012                    const currentSellerRevenue = currentBasePrice * (1 - productCosts.commission) * currentOrders;
3013                    const currentExpenses = (productCosts.cost + productCosts.delivery) * currentOrders;
3014                    currentProfit = currentSellerRevenue - currentExpenses;
3015                    
3016                    profitChange = currentProfit > 0 ? Math.round(((forecastProfit - currentProfit) / currentProfit) * 100) : 0;
3017                }
3018                
3019                return {
3020                    orders: forecastOrders,
3021                    ordersChange: Math.round(ordersChange * 100),
3022                    revenue: forecastRevenue,
3023                    revenueChange: Math.round(((forecastRevenue - currentRevenue) / currentRevenue) * 100),
3024                    profit: forecastProfit,
3025                    profitChange: profitChange
3026                };
3027            }
3028
3029            // Функция для расчета рекомендованной цены для конкретного товара
3030            function calculatePriceForProduct(targetProduct, allProducts) {
3031                console.log(`Calculating price for SKU ${targetProduct.sku}`);
3032                
3033                const productCosts = costs[targetProduct.sku];
3034                
3035                // Находим конкурентов
3036                const positionRange = 5;
3037                const nearbyProducts = allProducts.filter(p => 
3038                    !p.isTargetBrand && 
3039                    Math.abs(p.position - targetProduct.position) <= positionRange &&
3040                    p.price > 0 && p.orders > 0
3041                );
3042                
3043                const referenceProducts = nearbyProducts.length >= 3 
3044                    ? nearbyProducts 
3045                    : allProducts.filter(p => !p.isTargetBrand && p.price > 0 && p.orders > 0).slice(0, 10);
3046                
3047                if (referenceProducts.length === 0) {
3048                    return null;
3049                }
3050                
3051                // Рассчитываем базовую оптимальную цену на основе конкурентов
3052                let weightedSum = 0;
3053                let weightSum = 0;
3054                referenceProducts.forEach(p => {
3055                    const weight = p.revenue;
3056                    weightedSum += p.price * weight;
3057                    weightSum += weight;
3058                });
3059                const weightedPrice = weightSum > 0 ? weightedSum / weightSum : 0;
3060                
3061                const productsWithConversion = referenceProducts.map(p => ({
3062                    ...p,
3063                    conversion: p.orders / p.price
3064                })).sort((a, b) => b.conversion - a.conversion);
3065                const topConversionPrice = productsWithConversion.length > 0 
3066                    ? productsWithConversion.slice(0, 3).reduce((sum, p) => sum + p.price, 0) / 3
3067                    : 0;
3068                
3069                const top10Prices = referenceProducts.slice(0, 10).map(p => p.price).sort((a, b) => a - b);
3070                const medianPrice = top10Prices.length > 0 
3071                    ? top10Prices[Math.floor(top10Prices.length / 2)]
3072                    : 0;
3073
3074                const marketOptimalPrice = (weightedPrice * 0.4 + topConversionPrice * 0.3 + medianPrice * 0.3);
3075                
3076                // Рассчитываем RPI
3077                const competitorPrices = allProducts.filter(p => !p.isTargetBrand && p.price > 0);
3078                let avgCompetitorPrice = 0;
3079                if (competitorPrices.length > 0) {
3080                    let weightedPriceSum = 0;
3081                    let weightSum = 0;
3082                    competitorPrices.forEach(comp => {
3083                        const weight = comp.revenue;
3084                        weightedPriceSum += comp.price * weight;
3085                        weightSum += weight;
3086                    });
3087                    avgCompetitorPrice = weightSum > 0 ? weightedPriceSum / weightSum : targetProduct.price;
3088                }
3089                
3090                const rpi = avgCompetitorPrice > 0 ? (targetProduct.price / avgCompetitorPrice) * 100 : 100;
3091                
3092                // Используем эластичность для оптимизации прибыли
3093                const usedElasticity = elasticity !== null ? elasticity : -1.2;
3094                
3095                // Функция для расчета ожидаемой прибыли при заданной цене
3096                function calculateExpectedProfit(price) {
3097                    if (!productCosts) return null;
3098                    
3099                    const priceChange = (price - targetProduct.price) / targetProduct.price;
3100                    const ordersChange = usedElasticity * priceChange;
3101                    const expectedOrders = Math.max(1, targetProduct.orders * (1 + ordersChange));
3102                    
3103                    const basePrice = price / (1 - ozonDiscount / 100);
3104                    const sellerRevenue = basePrice * (1 - productCosts.commission) * expectedOrders;
3105                    const expenses = (productCosts.cost + productCosts.delivery) * expectedOrders;
3106                    
3107                    return sellerRevenue - expenses;
3108                }
3109                
3110                // Рассчитываем 4 варианта цен с учетом эластичности и максимизации прибыли
3111                // Захват рынка: ищем оптимальную цену ниже текущей для РОСТА прибыли
3112                // Цена ОБЯЗАТЕЛЬНО ниже текущей, но прибыль должна РАСТИ за счет увеличения объема
3113                let marketCapturePrice = targetProduct.price;
3114                if (productCosts) {
3115                    const currentProfit = calculateExpectedProfit(targetProduct.price);
3116                    let bestPrice = targetProduct.price;
3117                    let maxProfit = currentProfit;
3118                    
3119                    // Ищем оптимум между -30% и -1% от текущей цены
3120                    for (let multiplier = 0.70; multiplier < 0.99; multiplier += 0.01) {
3121                        const testPrice = targetProduct.price * multiplier;
3122                        const testProfit = calculateExpectedProfit(testPrice);
3123                        
3124                        // Выбираем цену с максимальной прибылью (больше текущей)
3125                        if (testProfit > maxProfit) {
3126                            maxProfit = testProfit;
3127                            bestPrice = testPrice;
3128                        }
3129                    }
3130                    
3131                    // Если нашли цену с большей прибылью - используем её
3132                    if (maxProfit > currentProfit) {
3133                        marketCapturePrice = Math.round(bestPrice);
3134                    } else {
3135                        // Если не нашли - используем безопасное снижение на 5%
3136                        marketCapturePrice = Math.round(targetProduct.price * 0.95);
3137                    }
3138                } else {
3139                    // Если нет данных о расходах, снижаем на 5%
3140                    marketCapturePrice = Math.round(targetProduct.price * 0.95);
3141                }
3142                
3143                // Агрессивная: ищем цену для максимизации прибыли с небольшим снижением
3144                // Допускаем падение прибыли до -2%, но стремимся к росту
3145                let aggressivePrice = targetProduct.price;
3146                if (productCosts) {
3147                    const currentProfit = calculateExpectedProfit(targetProduct.price);
3148                    let maxProfit = currentProfit;
3149                    const minAcceptableProfit = currentProfit * 0.98; // Допускаем падение до -2%
3150                    
3151                    // Ищем оптимум между -10% и +5% от текущей цены
3152                    for (let multiplier = 0.90; multiplier <= 1.05; multiplier += 0.01) {
3153                        const testPrice = targetProduct.price * multiplier;
3154                        const testProfit = calculateExpectedProfit(testPrice);
3155                        
3156                        // Выбираем цену с максимальной прибылью, но не ниже минимально допустимой
3157                        if (testProfit >= minAcceptableProfit && testProfit > maxProfit) {
3158                            maxProfit = testProfit;
3159                            aggressivePrice = testPrice;
3160                        }
3161                    }
3162                    aggressivePrice = Math.round(aggressivePrice);
3163                } else {
3164                    // Если нет данных о расходах, умеренное снижение
3165                    aggressivePrice = Math.round(targetProduct.price * 0.95);
3166                }
3167                
3168                // Оптимальная: цена для максимизации прибыли (выше текущей)
3169                // Ищем оптимум между текущей ценой и +30%
3170                let optimalPrice = targetProduct.price;
3171                if (productCosts) {
3172                    let maxProfit = calculateExpectedProfit(targetProduct.price);
3173                    for (let multiplier = 1.05; multiplier <= 1.30; multiplier += 0.01) {
3174                        const testPrice = targetProduct.price * multiplier;
3175                        const testProfit = calculateExpectedProfit(testPrice);
3176                        if (testProfit > maxProfit) {
3177                            maxProfit = testProfit;
3178                            optimalPrice = testPrice;
3179                        }
3180                    }
3181                    optimalPrice = Math.round(optimalPrice);
3182                } else {
3183                    // Если нет данных о расходах, используем рыночную цену
3184                    optimalPrice = Math.round(Math.max(targetProduct.price * 1.10, marketOptimalPrice));
3185                }
3186                
3187                // Рассчитываем базовые цены (цены поручения)
3188                const currentBasePrice = targetProduct.price / (1 - ozonDiscount / 100);
3189                const marketCaptureBasePrice = marketCapturePrice / (1 - ozonDiscount / 100);
3190                const aggressiveBasePrice = aggressivePrice / (1 - ozonDiscount / 100);
3191                const optimalBasePrice = optimalPrice / (1 - ozonDiscount / 100);
3192                
3193                return {
3194                    marketCapture: marketCapturePrice,
3195                    aggressive: aggressivePrice,
3196                    optimal: optimalPrice,
3197                    currentPrice: targetProduct.price,
3198                    currentBasePrice: Math.round(currentBasePrice),
3199                    marketCaptureBasePrice: Math.round(marketCaptureBasePrice),
3200                    aggressiveBasePrice: Math.round(aggressiveBasePrice),
3201                    optimalBasePrice: Math.round(optimalBasePrice),
3202                    currentPosition: targetProduct.position,
3203                    currentProfit: null,
3204                    rpi: rpi.toFixed(1),
3205                    priceChange: {
3206                        marketCapture: Math.round(((marketCapturePrice - targetProduct.price) / targetProduct.price * 100)),
3207                        aggressive: Math.round(((aggressivePrice - targetProduct.price) / targetProduct.price * 100)),
3208                        optimal: Math.round(((optimalPrice - targetProduct.price) / targetProduct.price * 100))
3209                    },
3210                    forecast: {
3211                        marketCapture: calculateForecast(marketCapturePrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts),
3212                        aggressive: calculateForecast(aggressivePrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts),
3213                        optimal: calculateForecast(optimalPrice, targetProduct.price, targetProduct.orders, targetProduct.revenue, productCosts)
3214                    }
3215                };
3216            }
3217
3218            // Общие рекомендации
3219            let weightedSum = 0;
3220            let weightSum = 0;
3221            products.forEach(p => {
3222                const positionBonus = p.position <= 5 ? 2 : 1;
3223                const weight = p.revenue * positionBonus;
3224                weightedSum += p.price * weight;
3225                weightSum += weight;
3226            });
3227            const weightedPrice = weightSum > 0 ? weightedSum / weightSum : 0;
3228            
3229            const productsWithConversion = products.map(p => ({
3230                ...p,
3231                conversion: p.orders / p.price
3232            })).sort((a, b) => b.conversion - a.conversion);
3233            const topConversionPrice = productsWithConversion.length > 0 
3234                ? productsWithConversion.slice(0, 3).reduce((sum, p) => sum + p.price, 0) / 3
3235                : 0;
3236            
3237            const top10Prices = products.slice(0, 10).map(p => p.price).sort((a, b) => a - b);
3238            const medianPrice = top10Prices.length > 0 
3239                ? top10Prices[Math.floor(top10Prices.length / 2)]
3240                : 0;
3241
3242            const baseOptimalPrice = (weightedPrice * 0.4 + topConversionPrice * 0.3 + medianPrice * 0.3);
3243
3244            const recommendedPrices = {
3245                marketCapture: {
3246                    price: Math.round(baseOptimalPrice * 0.70),
3247                    strategy: 'Захват рынка',
3248                    description: 'Оптимальная цена ниже текущей для максимизации прибыли'
3249                },
3250                aggressive: {
3251                    price: Math.round(baseOptimalPrice * 0.85),
3252                    strategy: 'Агрессивная',
3253                    description: 'Низкая цена для максимальных продаж и быстрого роста позиций'
3254                },
3255                optimal: {
3256                    price: Math.round(baseOptimalPrice),
3257                    strategy: 'Оптимальная',
3258                    description: 'Баланс между прибылью и объемом продаж'
3259                }
3260            };
3261
3262            // Раздельные рекомендации для целевых брендов
3263            const glsProducts = products.filter(p => p.brand.includes('GLS Pharmaceuticals'));
3264            const skinphoriaProducts = products.filter(p => p.brand.includes('Skinphoria'));
3265            
3266            const brandRecommendations = {};
3267            
3268            // Рекомендации для GLS Pharmaceuticals
3269            if (glsProducts.length > 0) {
3270                const glsAvgPosition = glsProducts.reduce((sum, p) => sum + p.position, 0) / glsProducts.length;
3271                const glsAvgPrice = glsProducts.reduce((sum, p) => sum + p.price, 0) / glsProducts.length;
3272                
3273                brandRecommendations.gls = {
3274                    brand: 'GLS Pharmaceuticals',
3275                    currentProducts: glsProducts.map(p => {
3276                        const priceRec = calculatePriceForProduct(p, products);
3277                        return {
3278                            sku: p.sku,
3279                            name: p.name,
3280                            position: p.position,
3281                            price: p.price,
3282                            revenue: p.revenue,
3283                            orders: p.orders,
3284                            recommendations: priceRec
3285                        };
3286                    }),
3287                    avgPosition: Math.round(glsAvgPosition),
3288                    avgPrice: Math.round(glsAvgPrice)
3289                };
3290            }
3291            
3292            // Рекомендации для Skinphoria
3293            if (skinphoriaProducts.length > 0) {
3294                const skinphoriaAvgPosition = skinphoriaProducts.reduce((sum, p) => sum + p.position, 0) / skinphoriaProducts.length;
3295                const skinphoriaAvgPrice = skinphoriaProducts.reduce((sum, p) => sum + p.price, 0) / skinphoriaProducts.length;
3296                
3297                brandRecommendations.skinphoria = {
3298                    brand: 'Skinphoria',
3299                    currentProducts: skinphoriaProducts.map(p => {
3300                        const priceRec = calculatePriceForProduct(p, products);
3301                        return {
3302                            sku: p.sku,
3303                            name: p.name,
3304                            position: p.position,
3305                            price: p.price,
3306                            revenue: p.revenue,
3307                            orders: p.orders,
3308                            recommendations: priceRec
3309                        };
3310                    }),
3311                    avgPosition: Math.round(skinphoriaAvgPosition),
3312                    avgPrice: Math.round(skinphoriaAvgPrice)
3313                };
3314            }
3315
3316            console.log('Ozon Product Parser: Analysis completed successfully');
3317            
3318            return {
3319                totalRevenue,
3320                totalOrders,
3321                avgPrice: Math.round(totalRevenue / totalOrders),
3322                topProducts,
3323                priceSegments: segments,
3324                elasticity: elasticity !== null ? {
3325                    value: elasticity.toFixed(2),
3326                    interpretation: elasticityInterpretation
3327                } : null,
3328                recommendedPrices,
3329                brandRecommendations
3330            };
3331        } catch (error) {
3332            console.error('Ozon Product Parser: Error in analyzeProducts:', error);
3333            return null;
3334        }
3335    }
3336
3337    // Показываем результаты
3338    async function showResultsModal() {
3339        console.log('Ozon Product Parser: Opening results modal');
3340        
3341        try {
3342            // Получаем результаты по спискам
3343            const allListResultsJson = await GM.getValue('ozon_parser_list_results', '{}');
3344            const allListResults = JSON.parse(allListResultsJson);
3345            const listNames = Object.keys(allListResults);
3346
3347            if (listNames.length === 0) {
3348                alert('Нет сохраненных результатов. Сначала выполните парсинг.');
3349                return;
3350            }
3351
3352            const modal = document.createElement('div');
3353            modal.className = 'ozon-parser-modal';
3354
3355            const content = document.createElement('div');
3356            content.className = 'ozon-parser-modal-content';
3357            content.style.maxWidth = '95vw';
3358            content.style.width = '95vw';
3359
3360            content.innerHTML = `
3361                <div class="ozon-parser-modal-header">Результаты парсинга</div>
3362                <div class="ozon-parser-modal-body">
3363                    <div style="margin-bottom: 20px;">
3364                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Выберите список:</label>
3365                        <div style="display: flex; gap: 10px; align-items: center;">
3366                            <select class="ozon-parser-search" id="list-selector" style="flex: 1; margin-bottom: 0;">
3367                                ${listNames.map(listName => {
3368        const list = allListResults[listName];
3369        const date = new Date(list.updatedAt).toLocaleDateString('ru-RU');
3370        const queriesCount = Object.keys(list.queries).length;
3371        return `<option value="${listName}">${listName} (${queriesCount} запросов, обновлен: ${date})</option>`;
3372    }).join('')}
3373                            </select>
3374                            <button class="ozon-parser-btn" id="delete-list-btn" style="background: linear-gradient(135deg, #dc3545 0%, #c82333 100%);">Удалить список</button>
3375                        </div>
3376                    </div>
3377                    <div style="margin-bottom: 15px;">
3378                        <label style="font-size: 14px; font-weight: 600; margin-bottom: 8px; display: block;">Сортировка запросов:</label>
3379                        <select class="ozon-parser-search" id="query-sort-selector" style="margin-bottom: 0;">
3380                            <option value="revenue">По нашей выручке (убывание)</option>
3381                            <option value="alphabet">По алфавиту (А-Я)</option>
3382                        </select>
3383                    </div>
3384                    <input type="text" class="ozon-parser-search" id="query-search" placeholder="Поиск по запросам">
3385                    <input type="text" class="ozon-parser-search" id="sku-search" placeholder="Поиск по SKU">
3386                    <div class="ozon-parser-tabs" id="query-tabs"></div>
3387                    <div id="results-container"></div>
3388                </div>
3389                <div class="ozon-parser-modal-footer">
3390                    <button class="ozon-parser-btn" id="close-results-btn">Закрыть</button>
3391                </div>
3392            `;
3393
3394            modal.appendChild(content);
3395            document.body.appendChild(modal);
3396
3397            // Текущий выбранный список
3398            let currentListName = listNames[0];
3399            let currentResults = allListResults[currentListName].queries;
3400            let currentSortMode = 'revenue';
3401
3402            // Функция для сортировки запросов
3403            function sortQueries(queries, results, sortMode) {
3404                if (sortMode === 'alphabet') {
3405                    return queries.sort((a, b) => a.localeCompare(b, 'ru'));
3406                } else {
3407                    // Сортировка по нашей выручке
3408                    return queries.sort((a, b) => {
3409                        const productsA = results[a];
3410                        const productsB = results[b];
3411                        
3412                        // Находим наши товары и их выручку
3413                        const ourProductsA = productsA.filter(p => p.isTargetBrand);
3414                        const ourProductsB = productsB.filter(p => p.isTargetBrand);
3415                        
3416                        const revenueA = ourProductsA.reduce((sum, p) => sum + p.revenue, 0);
3417                        const revenueB = ourProductsB.reduce((sum, p) => sum + p.revenue, 0);
3418                        
3419                        return revenueB - revenueA; // По убыванию
3420                    });
3421                }
3422            }
3423
3424            // Функция для обновления отображения
3425            async function updateDisplay() {
3426                const queries = Object.keys(currentResults);
3427                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3428                
3429                // Создаем вкладки
3430                createTabs(sortedQueries, currentResults, content);
3431                
3432                // Показываем результаты первого запроса
3433                if (sortedQueries.length > 0) {
3434                    await displayResults(sortedQueries[0], currentResults[sortedQueries[0]], content);
3435                }
3436            }
3437
3438            // Обработчик выбора списка
3439            const listSelector = content.querySelector('#list-selector');
3440            listSelector.addEventListener('change', () => {
3441                currentListName = listSelector.value;
3442                currentResults = allListResults[currentListName].queries;
3443                updateDisplay();
3444            });
3445
3446            // Обработчик изменения сортировки
3447            const querySortSelector = content.querySelector('#query-sort-selector');
3448            querySortSelector.addEventListener('change', () => {
3449                currentSortMode = querySortSelector.value;
3450                updateDisplay();
3451            });
3452
3453            // Обработчик удаления списка
3454            const deleteListBtn = content.querySelector('#delete-list-btn');
3455            deleteListBtn.addEventListener('click', async () => {
3456                if (!confirm(`Вы уверены, что хотите удалить список "${currentListName}"?`)) {
3457                    return;
3458                }
3459                
3460                // Удаляем список
3461                delete allListResults[currentListName];
3462                await GM.setValue('ozon_parser_list_results', JSON.stringify(allListResults));
3463                
3464                // Также удаляем из сохраненных списков
3465                const savedListsJson = await GM.getValue('ozon_parser_saved_lists', '{}');
3466                const savedLists = JSON.parse(savedListsJson);
3467                delete savedLists[currentListName];
3468                await GM.setValue('ozon_parser_saved_lists', JSON.stringify(savedLists));
3469                
3470                console.log(`Ozon Product Parser: List "${currentListName}" deleted`);
3471                
3472                // Обновляем UI
3473                const remainingLists = Object.keys(allListResults);
3474                if (remainingLists.length === 0) {
3475                    alert('Все списки удалены');
3476                    modal.remove();
3477                    return;
3478                }
3479                
3480                // Переключаемся на первый оставшийся список
3481                currentListName = remainingLists[0];
3482                currentResults = allListResults[currentListName].queries;
3483                
3484                // Обновляем селектор
3485                listSelector.innerHTML = remainingLists.map(listName => {
3486                    const list = allListResults[listName];
3487                    const date = new Date(list.updatedAt).toLocaleDateString('ru-RU');
3488                    const queriesCount = Object.keys(list.queries).length;
3489                    return `<option value="${listName}">${listName} (${queriesCount} запросов, обновлен: ${date})</option>`;
3490                }).join('');
3491                
3492                updateDisplay();
3493            });
3494
3495            // Обработчик закрытия модального окна
3496            const closeBtn = content.querySelector('#close-results-btn');
3497            closeBtn.addEventListener('click', () => {
3498                modal.remove();
3499            });
3500
3501            // Обработчик поиска по запросам
3502            const querySearchInput = content.querySelector('#query-search');
3503            querySearchInput.addEventListener('input', debounce(() => {
3504                const searchValue = querySearchInput.value.trim().toLowerCase();
3505                const queries = Object.keys(currentResults);
3506                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3507                filterQueriesByQuery(searchValue, sortedQueries, currentResults, content);
3508            }, 300));
3509
3510            // Обработчик поиска по SKU
3511            const skuSearchInput = content.querySelector('#sku-search');
3512            skuSearchInput.addEventListener('input', debounce(() => {
3513                const searchValue = skuSearchInput.value.trim();
3514                const queries = Object.keys(currentResults);
3515                const sortedQueries = sortQueries(queries, currentResults, currentSortMode);
3516                filterQueriesBySKU(searchValue, sortedQueries, currentResults, content);
3517            }, 300));
3518
3519            // Создаем вкладки для запросов
3520            const initialQueries = Object.keys(currentResults);
3521            const sortedInitialQueries = sortQueries(initialQueries, currentResults, currentSortMode);
3522            createTabs(sortedInitialQueries, currentResults, content);
3523
3524            // Показываем результаты первого запроса
3525            await displayResults(sortedInitialQueries[0], currentResults[sortedInitialQueries[0]], content);
3526
3527            // Закрытие по клику на фон
3528            modal.addEventListener('click', (e) => {
3529                if (e.target === modal) {
3530                    modal.remove();
3531                }
3532            });
3533
3534            console.log('Ozon Product Parser: Results modal shown');
3535        } catch (error) {
3536            console.error('Ozon Product Parser: Error in showResultsModal:', error);
3537            alert('Ошибка при отображении результатов: ' + error.message);
3538        }
3539    }
3540
3541    // Фильтруем запросы по названию запроса
3542    async function filterQueriesByQuery(queryText, allQueries, results, modalContent) {
3543        if (!queryText) {
3544            createTabs(allQueries, results, modalContent);
3545            await displayResults(allQueries[0], results[allQueries[0]], modalContent);
3546            return;
3547        }
3548
3549        const filteredQueries = allQueries.filter(q => 
3550            q.toLowerCase().includes(queryText)
3551        );
3552
3553        if (filteredQueries.length === 0) {
3554            const container = modalContent.querySelector('#results-container');
3555            container.innerHTML = '<p>Запросы не найдены</p>';
3556            const tabsContainer = modalContent.querySelector('#query-tabs');
3557            tabsContainer.innerHTML = '';
3558            return;
3559        }
3560
3561        createTabs(filteredQueries, results, modalContent);
3562        await displayResults(filteredQueries[0], results[filteredQueries[0]], modalContent);
3563    }
3564
3565    // Фильтруем запросы по SKU
3566    async function filterQueriesBySKU(sku, allQueries, results, modalContent) {
3567        if (!sku) {
3568            createTabs(allQueries, results, modalContent);
3569            await displayResults(allQueries[0], results[allQueries[0]], modalContent);
3570            return;
3571        }
3572
3573        const filteredQueries = allQueries.filter(query => {
3574            const products = results[query];
3575            return products.some(product => product.sku.includes(sku));
3576        });
3577
3578        if (filteredQueries.length === 0) {
3579            const container = modalContent.querySelector('#results-container');
3580            container.innerHTML = '<p>Товары с таким SKU не найдены ни в одном запросе</p>';
3581            const tabsContainer = modalContent.querySelector('#query-tabs');
3582            tabsContainer.innerHTML = '';
3583            return;
3584        }
3585
3586        createTabs(filteredQueries, results, modalContent);
3587        await displayResults(filteredQueries[0], results[filteredQueries[0]], modalContent);
3588    }
3589
3590    // Создаем вкладки для запросов
3591    function createTabs(queries, results, modalContent) {
3592        const tabsContainer = modalContent.querySelector('#query-tabs');
3593        tabsContainer.innerHTML = '';
3594        
3595        queries.forEach((query, index) => {
3596            const tab = document.createElement('button');
3597            tab.className = 'ozon-parser-tab' + (index === 0 ? ' active' : '');
3598            
3599            // Получаем позицию по выручке
3600            const products = results[query];
3601            const position = getOurRevenuePosition(query, products);
3602            
3603            // Формируем текст вкладки
3604            if (position !== null) {
3605                tab.textContent = `${query} (${position} место)`;
3606            } else {
3607                tab.textContent = `${query} (нет наших)`;
3608            }
3609            
3610            tab.addEventListener('click', async () => {
3611                tabsContainer.querySelectorAll('.ozon-parser-tab').forEach(t => t.classList.remove('active'));
3612                tab.classList.add('active');
3613                await displayResults(query, results[query], modalContent);
3614            });
3615            tabsContainer.appendChild(tab);
3616        });
3617        
3618        // Функция для получения нашей позиции по выручке в запросе
3619        function getOurRevenuePosition(query, products) {
3620            // Сортируем все товары по выручке
3621            const sortedByRevenue = [...products].sort((a, b) => b.revenue - a.revenue);
3622            
3623            // Находим первый наш товар
3624            const ourProductIndex = sortedByRevenue.findIndex(p => p.isTargetBrand);
3625            
3626            if (ourProductIndex === -1) {
3627                return null; // Наших товаров нет
3628            }
3629            
3630            return ourProductIndex + 1; // Позиция (1-based)
3631        }
3632    }
3633
3634    // Отображаем результаты для конкретного запроса
3635    async function displayResults(query, productsData, modalContent) {
3636        console.log('Ozon Product Parser: Displaying results for query:', query);
3637        
3638        const container = modalContent.querySelector('#results-container');
3639        
3640        try {
3641            let products;
3642            
3643            if (Array.isArray(productsData)) {
3644                products = productsData;
3645            } else {
3646                container.innerHTML = '<p>Запросы не найдены</p>';
3647                return;
3648            }
3649            
3650            if (!products || products.length === 0) {
3651                container.innerHTML = '<p>Нет данных для этого запроса</p>';
3652                return;
3653            }
3654            
3655            // Получаем аналитику
3656            const analytics = await analyzeProducts(products);
3657            
3658            if (!analytics) {
3659                container.innerHTML = '<p>Ошибка при анализе данных</p>';
3660                return;
3661            }
3662            
3663            // Получаем скидку Ozon для расчета базовой цены
3664            const ozonDiscount = await GM.getValue('ozon_parser_discount', 50);
3665            
3666            let tableHTML = `
3667                <table class="ozon-parser-results-table">
3668                    <thead>
3669                        <tr>
3670                            <th>Позиция</th>
3671                            <th>SKU</th>
3672                            <th>Название</th>
3673                            <th>Бренд</th>
3674                            <th>Текущая цена</th>
3675                            <th>Средняя цена</th>
3676                            <th>Выручка</th>
3677                            <th>Заказы</th>
3678                        </tr>
3679                    </thead>
3680                    <tbody>
3681            `;
3682            
3683            products.forEach(product => {
3684                const rowClass = product.isTargetBrand ? ' class="ozon-parser-highlight"' : '';
3685                const skuLink = `https://www.ozon.ru/product/${product.sku}`;
3686                const basePrice = product.price / (1 - ozonDiscount / 100);
3687                const avgPrice = product.orders > 0 ? product.revenue / product.orders : 0;
3688                
3689                tableHTML += `
3690                    <tr${rowClass}>
3691                        <td>${product.position}</td>
3692                        <td><a href="${skuLink}" target="_blank" class="ozon-parser-sku-link">${product.sku}</a></td>
3693                        <td>${product.name || '—'}</td>
3694                        <td>${product.brand}</td>
3695                        <td>
3696                            <div style="font-weight: 600;">${product.price.toLocaleString('ru-RU')} ₽</div>
3697                            <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${Math.round(basePrice).toLocaleString('ru-RU')} ₽</div>
3698                        </td>
3699                        <td>
3700                            <div style="font-weight: 600;">${Math.round(avgPrice).toLocaleString('ru-RU')} ₽</div>
3701                        </td>
3702                        <td>${product.revenue.toLocaleString('ru-RU')} ₽</td>
3703                        <td>${product.orders.toLocaleString('ru-RU')}</td>
3704                    </tr>
3705                `;
3706            });
3707            
3708            tableHTML += `
3709                    </tbody>
3710                </table>
3711            `;
3712            
3713            // Добавляем аналитику
3714            tableHTML += `
3715                <div class="ozon-parser-analytics">
3716                    <div class="ozon-parser-analytics-header">📊 Аналитика запроса</div>
3717                    
3718                    ${analytics.elasticity ? `
3719                    <div class="ozon-parser-analytics-section">
3720                        <div class="ozon-parser-analytics-card">
3721                            <div class="ozon-parser-analytics-card-title">Эластичность запроса</div>
3722                            <div class="ozon-parser-analytics-card-value">${analytics.elasticity.value}</div>
3723                            <div style="font-size: 12px; color: #666; margin-top: 8px;">
3724                                ${analytics.elasticity.interpretation}
3725                            </div>
3726                        </div>
3727                    </div>
3728                    ` : ''}
3729                    
3730                    <div class="ozon-parser-analytics-section">
3731                        <div class="ozon-parser-analytics-section-title">Рекомендованные цены</div>
3732                        <div class="ozon-parser-analytics-grid">
3733                            <div class="ozon-parser-analytics-card" style="border: 2px solid #6c757d;">
3734                                <div class="ozon-parser-analytics-card-title">⚡ Захват рынка</div>
3735                                <div class="ozon-parser-analytics-card-value" style="color: #6c757d;">${analytics.recommendedPrices.marketCapture.price.toLocaleString('ru-RU')} ₽</div>
3736                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Оптимальная цена ниже текущей для максимизации прибыли</div>
3737                            </div>
3738                            <div class="ozon-parser-analytics-card" style="border: 2px solid #dc3545;">
3739                                <div class="ozon-parser-analytics-card-title">✅ Оптимальная</div>
3740                                <div class="ozon-parser-analytics-card-value" style="color: #dc3545;">${analytics.recommendedPrices.aggressive.price.toLocaleString('ru-RU')} ₽</div>
3741                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Низкая цена для максимальных продаж и быстрого роста позиций</div>
3742                            </div>
3743                            <div class="ozon-parser-analytics-card" style="border: 2px solid #28a745;">
3744                                <div class="ozon-parser-analytics-card-title">🔥 Агрессивная</div>
3745                                <div class="ozon-parser-analytics-card-value" style="color: #28a745;">${analytics.recommendedPrices.optimal.price.toLocaleString('ru-RU')} ₽</div>
3746                                <div style="font-size: 12px; color: #666; margin-top: 8px;">Баланс между прибылью и объемом продаж</div>
3747                            </div>
3748                        </div>
3749                        <div class="ozon-parser-info">
3750                            💡 Расчет учитывает: средневзвешенную цену по выручке (вес 40%), оптимальную конверсию цена/заказы (вес 30%), медианную цену топ-10 (вес 30%). Товары из топ-5 позиций имеют удвоенный вес.
3751                        </div>
3752                    </div>
3753                    
3754                    ${analytics.brandRecommendations && Object.keys(analytics.brandRecommendations).length > 0 ? `
3755                    <div class="ozon-parser-analytics-section">
3756                        <div class="ozon-parser-analytics-section-title">🎯 Рекомендации для ваших брендов</div>
3757                        ${analytics.brandRecommendations.gls ? `
3758                        <div style="background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; border: 2px solid #ffc107;">
3759                            <div style="font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333;">
3760                                ${analytics.brandRecommendations.gls.brand}
3761                            </div>
3762                            <div style="margin-bottom: 15px;">
3763                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Текущие товары в выдаче: ${analytics.brandRecommendations.gls.currentProducts.length}</div>
3764                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Средняя позиция: ${analytics.brandRecommendations.gls.avgPosition}</div>
3765                                <div style="font-size: 13px; color: #666;">Средняя цена: ${analytics.brandRecommendations.gls.avgPrice.toLocaleString('ru-RU')}</div>
3766                            </div>
3767                            <details style="margin-top: 10px;" open>
3768                                <summary style="cursor: pointer; font-size: 13px; color: #005bff; font-weight: 600;">Показать товары</summary>
3769                                <table class="ozon-parser-analytics-table" style="margin-top: 10px;">
3770                                    <thead>
3771                                        <tr>
3772                                            <th>SKU</th>
3773                                            <th>Название</th>
3774                                            <th>Позиция</th>
3775                                            <th>Текущая цена</th>
3776                                            <th>⚡ Захват рынка</th>
3777                                            <th>✅ Оптимальная</th>
3778                                            <th>🔥 Агрессивная</th>
3779                                        </tr>
3780                                    </thead>
3781                                    <tbody>
3782                                        ${analytics.brandRecommendations.gls.currentProducts.map(p => {
3783        const rec = p.recommendations;
3784        if (!rec) return `
3785                                            <tr>
3786                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3787                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3788                                                <td>${p.position}</td>
3789                                                <td>${p.price.toLocaleString('ru-RU')} ₽</td>
3790                                                <td>—</td>
3791                                                <td>—</td>
3792                                                <td>—</td>
3793                                            </tr>
3794                                            `;
3795        return `
3796                                            <tr>
3797                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3798                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3799                                                <td>${p.position}</td>
3800                                                <td>
3801                                                    <div style="font-weight: 600;">${p.price.toLocaleString('ru-RU')}</div>
3802                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.currentBasePrice.toLocaleString('ru-RU')}</div>
3803                                                    <div style="font-size: 11px; color: #666; margin-top: 4px;">Выручка: ${p.revenue.toLocaleString('ru-RU')}</div>
3804                                                    <div style="font-size: 11px; color: #666;">Заказы: ${p.orders.toLocaleString('ru-RU')}</div>
3805                                                    ${rec.skuDiscount ? `
3806                                                    <div style="font-size: 11px; color: #ff6b00; font-weight: 600; margin-top: 4px;">
3807                                                        Скидка Ozon: ${rec.skuDiscount}%
3808                                                    </div>
3809                                                    ` : ''}
3810                                                    ${rec.currentProfit !== null ? `
3811                                                    <div style="font-size: 11px; color: #005bff; font-weight: 600; margin-top: 4px;">
3812                                                        Прибыль: ${rec.currentProfit.toFixed(0)}3813                                                    </div>
3814                                                    ` : ''}
3815                                                </td>
3816                                                <td style="background: #e9ecef;">
3817                                                    <div style="font-weight: 600; color: #6c757d;">${rec.marketCapture.toLocaleString('ru-RU')}</div>
3818                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.marketCaptureBasePrice.toLocaleString('ru-RU')}</div>
3819                                                    <div style="font-size: 11px; color: ${rec.priceChange.marketCapture < 0 ? '#dc3545' : '#28a745'};">
3820                                                        ${rec.priceChange.marketCapture > 0 ? '+' : ''}${rec.priceChange.marketCapture}%
3821                                                    </div>
3822                                                    ${rec.forecast && rec.forecast.marketCapture ? `
3823                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3824                                                        Выручка: ${rec.forecast.marketCapture.revenueChange > 0 ? '+' : ''}${rec.forecast.marketCapture.revenueChange}%
3825                                                    </div>
3826                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3827                                                        Заказы: ${rec.forecast.marketCapture.ordersChange > 0 ? '+' : ''}${rec.forecast.marketCapture.ordersChange}%
3828                                                    </div>
3829                                                     ${rec.forecast.marketCapture.profit !== null ? `
3830                                                     <div style="font-size: 11px; color: ${rec.forecast.marketCapture.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3831                                                         Прибыль: ${rec.forecast.marketCapture.profitChange > 0 ? '+' : ''}${rec.forecast.marketCapture.profitChange}%
3832                                                     </div>
3833                                                     <div style="font-size: 10px; color: #666;">
3834                                                         ${rec.forecast.marketCapture.profit.toFixed(0)}3835                                                     </div>
3836                                                     ` : ''}
3837                                                     ` : ''}
3838                                                </td>
3839                                                <td style="background: #d4edda;">
3840                                                    <div style="font-weight: 600; color: #28a745;">${rec.aggressive.toLocaleString('ru-RU')} ₽</div>
3841                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.aggressiveBasePrice.toLocaleString('ru-RU')} ₽</div>
3842                                                    <div style="font-size: 11px; color: ${rec.priceChange.aggressive < 0 ? '#dc3545' : '#28a745'};">
3843                                                        ${rec.priceChange.aggressive > 0 ? '+' : ''}${rec.priceChange.aggressive}%
3844                                                    </div>
3845                                                    ${rec.forecast && rec.forecast.aggressive ? `
3846                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3847                                                        Выручка: ${rec.forecast.aggressive.revenueChange > 0 ? '+' : ''}${rec.forecast.aggressive.revenueChange}%
3848                                                    </div>
3849                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3850                                                        Заказы: ${rec.forecast.aggressive.ordersChange > 0 ? '+' : ''}${rec.forecast.aggressive.ordersChange}%
3851                                                    </div>
3852                                                     ${rec.forecast.aggressive.profit !== null ? `
3853                                                     <div style="font-size: 11px; color: ${rec.forecast.aggressive.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3854                                                         Прибыль: ${rec.forecast.aggressive.profitChange > 0 ? '+' : ''}${rec.forecast.aggressive.profitChange}%
3855                                                     </div>
3856                                                     <div style="font-size: 10px; color: #666;">
3857                                                         ${rec.forecast.aggressive.profit.toFixed(0)}3858                                                     </div>
3859                                                     ` : ''}
3860                                                     ` : ''}
3861                                                </td>
3862                                                <td style="background: #fff3cd;">
3863                                                    <div style="font-weight: 600; color: #dc3545;">${rec.optimal.toLocaleString('ru-RU')} ₽</div>
3864                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.optimalBasePrice.toLocaleString('ru-RU')} ₽</div>
3865                                                    <div style="font-size: 11px; color: ${rec.priceChange.optimal < 0 ? '#dc3545' : '#28a745'};">
3866                                                        ${rec.priceChange.optimal > 0 ? '+' : ''}${rec.priceChange.optimal}%
3867                                                    </div>
3868                                                    ${rec.forecast && rec.forecast.optimal ? `
3869                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3870                                                        Выручка: ${rec.forecast.optimal.revenueChange > 0 ? '+' : ''}${rec.forecast.optimal.revenueChange}%
3871                                                    </div>
3872                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3873                                                        Заказы: ${rec.forecast.optimal.ordersChange > 0 ? '+' : ''}${rec.forecast.optimal.ordersChange}%
3874                                                    </div>
3875                                                     ${rec.forecast.optimal.profit !== null ? `
3876                                                     <div style="font-size: 11px; color: ${rec.forecast.optimal.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3877                                                         Прибыль: ${rec.forecast.optimal.profitChange > 0 ? '+' : ''}${rec.forecast.optimal.profitChange}%
3878                                                     </div>
3879                                                     <div style="font-size: 10px; color: #666;">
3880                                                         ${rec.forecast.optimal.profit.toFixed(0)}3881                                                     </div>
3882                                                     ` : ''}
3883                                                     ` : ''}
3884                                                </td>
3885                                            </tr>
3886                                            `;
3887    }).join('')}
3888                                    </tbody>
3889                                </table>
3890                            </details>
3891                        </div>
3892                        ` : ''}
3893                        ${analytics.brandRecommendations.skinphoria ? `
3894                        <div style="background: white; padding: 20px; border-radius: 8px; border: 2px solid #ffc107;">
3895                            <div style="font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333;">
3896                                ${analytics.brandRecommendations.skinphoria.brand}
3897                            </div>
3898                            <div style="margin-bottom: 15px;">
3899                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Текущие товары в выдаче: ${analytics.brandRecommendations.skinphoria.currentProducts.length}</div>
3900                                <div style="font-size: 13px; color: #666; margin-bottom: 5px;">Средняя позиция: ${analytics.brandRecommendations.skinphoria.avgPosition}</div>
3901                                <div style="font-size: 13px; color: #666;">Средняя цена: ${analytics.brandRecommendations.skinphoria.avgPrice.toLocaleString('ru-RU')}</div>
3902                            </div>
3903                            <details style="margin-top: 10px;" open>
3904                                <summary style="cursor: pointer; font-size: 13px; color: #005bff; font-weight: 600;">Показать товары</summary>
3905                                <table class="ozon-parser-analytics-table" style="margin-top: 10px;">
3906                                    <thead>
3907                                        <tr>
3908                                            <th>SKU</th>
3909                                            <th>Название</th>
3910                                            <th>Позиция</th>
3911                                            <th>Текущая цена</th>
3912                                            <th>⚡ Захват рынка</th>
3913                                            <th>✅ Оптимальная</th>
3914                                            <th>🔥 Агрессивная</th>
3915                                        </tr>
3916                                    </thead>
3917                                    <tbody>
3918                                        ${analytics.brandRecommendations.skinphoria.currentProducts.map(p => {
3919        const rec = p.recommendations;
3920        if (!rec) return `
3921                                            <tr>
3922                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3923                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3924                                                <td>${p.position}</td>
3925                                                <td>${p.price.toLocaleString('ru-RU')} ₽</td>
3926                                                <td>—</td>
3927                                                <td>—</td>
3928                                                <td>—</td>
3929                                            </tr>
3930                                            `;
3931        return `
3932                                            <tr>
3933                                                <td><a href="https://www.ozon.ru/product/${p.sku}" target="_blank" class="ozon-parser-sku-link">${p.sku}</a></td>
3934                                                <td style="max-width: 200px; white-space: normal;">${p.name || '—'}</td>
3935                                                <td>${p.position}</td>
3936                                                <td>
3937                                                    <div style="font-weight: 600;">${p.price.toLocaleString('ru-RU')}</div>
3938                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.currentBasePrice.toLocaleString('ru-RU')}</div>
3939                                                    <div style="font-size: 11px; color: #666; margin-top: 4px;">Выручка: ${p.revenue.toLocaleString('ru-RU')}</div>
3940                                                    <div style="font-size: 11px; color: #666;">Заказы: ${p.orders.toLocaleString('ru-RU')}</div>
3941                                                    ${rec.skuDiscount ? `
3942                                                    <div style="font-size: 11px; color: #ff6b00; font-weight: 600; margin-top: 4px;">
3943                                                        Скидка Ozon: ${rec.skuDiscount}%
3944                                                    </div>
3945                                                    ` : ''}
3946                                                    ${rec.currentProfit !== null ? `
3947                                                    <div style="font-size: 11px; color: #005bff; font-weight: 600; margin-top: 4px;">
3948                                                        Прибыль: ${rec.currentProfit.toFixed(0)}3949                                                    </div>
3950                                                    ` : ''}
3951                                                </td>
3952                                                <td style="background: #e9ecef;">
3953                                                    <div style="font-weight: 600; color: #6c757d;">${rec.marketCapture.toLocaleString('ru-RU')}</div>
3954                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.marketCaptureBasePrice.toLocaleString('ru-RU')}</div>
3955                                                    <div style="font-size: 11px; color: ${rec.priceChange.marketCapture < 0 ? '#dc3545' : '#28a745'};">
3956                                                        ${rec.priceChange.marketCapture > 0 ? '+' : ''}${rec.priceChange.marketCapture}%
3957                                                    </div>
3958                                                    ${rec.forecast && rec.forecast.marketCapture ? `
3959                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3960                                                        Выручка: ${rec.forecast.marketCapture.revenueChange > 0 ? '+' : ''}${rec.forecast.marketCapture.revenueChange}%
3961                                                    </div>
3962                                                    <div style="font-size: 11px; color: ${rec.forecast.marketCapture.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3963                                                        Заказы: ${rec.forecast.marketCapture.ordersChange > 0 ? '+' : ''}${rec.forecast.marketCapture.ordersChange}%
3964                                                    </div>
3965                                                     ${rec.forecast.marketCapture.profit !== null ? `
3966                                                     <div style="font-size: 11px; color: ${rec.forecast.marketCapture.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3967                                                         Прибыль: ${rec.forecast.marketCapture.profitChange > 0 ? '+' : ''}${rec.forecast.marketCapture.profitChange}%
3968                                                     </div>
3969                                                     <div style="font-size: 10px; color: #666;">
3970                                                         ${rec.forecast.marketCapture.profit.toFixed(0)}3971                                                     </div>
3972                                                     ` : ''}
3973                                                     ` : ''}
3974                                                </td>
3975                                                <td style="background: #d4edda;">
3976                                                    <div style="font-weight: 600; color: #28a745;">${rec.aggressive.toLocaleString('ru-RU')} ₽</div>
3977                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.aggressiveBasePrice.toLocaleString('ru-RU')} ₽</div>
3978                                                    <div style="font-size: 11px; color: ${rec.priceChange.aggressive < 0 ? '#dc3545' : '#28a745'};">
3979                                                        ${rec.priceChange.aggressive > 0 ? '+' : ''}${rec.priceChange.aggressive}%
3980                                                    </div>
3981                                                    ${rec.forecast && rec.forecast.aggressive ? `
3982                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
3983                                                        Выручка: ${rec.forecast.aggressive.revenueChange > 0 ? '+' : ''}${rec.forecast.aggressive.revenueChange}%
3984                                                    </div>
3985                                                    <div style="font-size: 11px; color: ${rec.forecast.aggressive.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
3986                                                        Заказы: ${rec.forecast.aggressive.ordersChange > 0 ? '+' : ''}${rec.forecast.aggressive.ordersChange}%
3987                                                    </div>
3988                                                     ${rec.forecast.aggressive.profit !== null ? `
3989                                                     <div style="font-size: 11px; color: ${rec.forecast.aggressive.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
3990                                                         Прибыль: ${rec.forecast.aggressive.profitChange > 0 ? '+' : ''}${rec.forecast.aggressive.profitChange}%
3991                                                     </div>
3992                                                     <div style="font-size: 10px; color: #666;">
3993                                                         ${rec.forecast.aggressive.profit.toFixed(0)}3994                                                     </div>
3995                                                     ` : ''}
3996                                                     ` : ''}
3997                                                </td>
3998                                                <td style="background: #fff3cd;">
3999                                                    <div style="font-weight: 600; color: #dc3545;">${rec.optimal.toLocaleString('ru-RU')} ₽</div>
4000                                                    <div style="font-size: 10px; color: #999; margin-top: 2px;">База: ${rec.optimalBasePrice.toLocaleString('ru-RU')} ₽</div>
4001                                                    <div style="font-size: 11px; color: ${rec.priceChange.optimal < 0 ? '#dc3545' : '#28a745'};">
4002                                                        ${rec.priceChange.optimal > 0 ? '+' : ''}${rec.priceChange.optimal}%
4003                                                    </div>
4004                                                    ${rec.forecast && rec.forecast.optimal ? `
4005                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.revenueChange >= 0 ? '#28a745' : '#dc3545'}; margin-top: 4px;">
4006                                                        Выручка: ${rec.forecast.optimal.revenueChange > 0 ? '+' : ''}${rec.forecast.optimal.revenueChange}%
4007                                                    </div>
4008                                                    <div style="font-size: 11px; color: ${rec.forecast.optimal.ordersChange >= 0 ? '#28a745' : '#dc3545'};">
4009                                                        Заказы: ${rec.forecast.optimal.ordersChange > 0 ? '+' : ''}${rec.forecast.optimal.ordersChange}%
4010                                                    </div>
4011                                                     ${rec.forecast.optimal.profit !== null ? `
4012                                                     <div style="font-size: 11px; color: ${rec.forecast.optimal.profitChange >= 0 ? '#28a745' : '#dc3545'}; font-weight: 600; margin-top: 2px;">
4013                                                         Прибыль: ${rec.forecast.optimal.profitChange > 0 ? '+' : ''}${rec.forecast.optimal.profitChange}%
4014                                                     </div>
4015                                                     <div style="font-size: 10px; color: #666;">
4016                                                         ${rec.forecast.optimal.profit.toFixed(0)}4017                                                     </div>
4018                                                     ` : ''}
4019                                                     ` : ''}
4020                                                </td>
4021                                            </tr>
4022                                            `;
4023    }).join('')}
4024                                    </tbody>
4025                                </table>
4026                            </details>
4027                        </div>
4028                        ` : ''}
4029                    </div>
4030                    ` : ''}
4031                    
4032                    <div class="ozon-parser-analytics-section">
4033                        <div class="ozon-parser-analytics-section-title">Общая статистика</div>
4034                        <div class="ozon-parser-analytics-grid">
4035                            <div class="ozon-parser-analytics-card">
4036                                <div class="ozon-parser-analytics-card-title">Общая выручка</div>
4037                                <div class="ozon-parser-analytics-card-value">${analytics.totalRevenue.toLocaleString('ru-RU')}</div>
4038                            </div>
4039                            <div class="ozon-parser-analytics-card">
4040                                <div class="ozon-parser-analytics-card-title">Всего заказов</div>
4041                                <div class="ozon-parser-analytics-card-value">${analytics.totalOrders.toLocaleString('ru-RU')}</div>
4042                            </div>
4043                            <div class="ozon-parser-analytics-card">
4044                                <div class="ozon-parser-analytics-card-title">Средний чек</div>
4045                                <div class="ozon-parser-analytics-card-value">${analytics.avgPrice.toLocaleString('ru-RU')}</div>
4046                            </div>
4047                        </div>
4048                    </div>
4049                    
4050                    <div class="ozon-parser-analytics-section">
4051                        <div class="ozon-parser-analytics-section-title">Топ-5 товаров по выручке</div>
4052                        <table class="ozon-parser-analytics-table">
4053                            <thead>
4054                                <tr>
4055                                    <th>Товар</th>
4056                                    <th>Бренд</th>
4057                                    <th>Цена</th>
4058                                    <th>Выручка</th>
4059                                    <th>Доля</th>
4060                                </tr>
4061                            </thead>
4062                            <tbody>
4063            `;
4064            
4065            analytics.topProducts.forEach(product => {
4066                tableHTML += `
4067                    <tr>
4068                        <td style="max-width: 300px; white-space: normal;">${product.name || '—'}</td>
4069                        <td>${product.brand}</td>
4070                        <td>${product.price.toLocaleString('ru-RU')}</td>
4071                        <td>${product.revenue.toLocaleString('ru-RU')}</td>
4072                        <td>${product.revenueShare}%</td>
4073                    </tr>
4074                `;
4075            });
4076            
4077            tableHTML += `
4078                            </tbody>
4079                        </table>
4080                    </div>
4081                    
4082                    <div class="ozon-parser-analytics-section">
4083                        <div class="ozon-parser-analytics-section-title">Ценовые сегменты</div>
4084                        <table class="ozon-parser-analytics-table">
4085                            <thead>
4086                                <tr>
4087                                    <th>Сегмент</th>
4088                                    <th>Диапазон цен</th>
4089                                    <th>Товаров</th>
4090                                    <th>Средняя цена</th>
4091                                    <th>Выручка</th>
4092                                    <th>Доля выручки</th>
4093                                </tr>
4094                            </thead>
4095                            <tbody>
4096            `;
4097            
4098            analytics.priceSegments.forEach(segment => {
4099                tableHTML += `
4100                    <tr>
4101                        <td>${segment.name}</td>
4102                        <td>${segment.priceRange}</td>
4103                        <td>${segment.count}</td>
4104                        <td>${segment.avgPrice.toLocaleString('ru-RU')}</td>
4105                        <td>${segment.revenue.toLocaleString('ru-RU')}</td>
4106                        <td>
4107                            <div style="display: flex; align-items: center; gap: 10px;">
4108                                <div class="ozon-parser-analytics-bar" style="width: ${segment.revenueShare}%; min-width: 20px;"></div>
4109                                <span>${segment.revenueShare}%</span>
4110                            </div>
4111                        </td>
4112                    </tr>
4113                `;
4114            });
4115            
4116            tableHTML += `
4117                            </tbody>
4118                        </table>
4119                    </div>
4120                </div>
4121            `;
4122            
4123            container.innerHTML = tableHTML;
4124            console.log('Ozon Product Parser: Results displayed successfully');
4125        } catch (error) {
4126            console.error('Ozon Product Parser: Error in displayResults:', error);
4127            container.innerHTML = '<p>Ошибка при отображении результатов: ' + error.message + '</p>';
4128        }
4129    }
4130
4131    // Инициализация
4132    function init() {
4133        console.log('Ozon Product Parser: Initializing...');
4134        
4135        if (document.readyState === 'loading') {
4136            document.addEventListener('DOMContentLoaded', () => {
4137                addStyles();
4138                createUI();
4139                continueParsingIfActive();
4140                continueDiscountCalculationIfActive();
4141            });
4142        } else {
4143            addStyles();
4144            createUI();
4145            continueParsingIfActive();
4146            continueDiscountCalculationIfActive();
4147        }
4148    }
4149
4150    init();
4151})();
Ozon Product Parser | Robomonkey