Ozon Product Parser

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

Size

217.7 KB

Version

1.8.39

Created

Mar 26, 2026

Updated

21 days ago

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