Ozon Product Parser

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

Size

192.4 KB

Version

1.8.26

Created

Mar 25, 2026

Updated

22 days ago

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