File size: 52,961 Bytes
69d1e96
 
dd401c6
69d1e96
17e78e6
 
2125c39
b356b82
69d1e96
 
dd401c6
0cd2b77
69d1e96
dd401c6
0cd2b77
e98bce2
 
 
0cd2b77
23934ad
 
 
 
 
 
 
 
 
 
b356b82
23934ad
 
 
b356b82
23934ad
b356b82
23934ad
 
 
 
 
 
b356b82
23934ad
 
 
b356b82
 
23934ad
 
 
 
 
 
 
 
 
 
 
 
 
 
b356b82
23934ad
b356b82
23934ad
 
 
 
 
 
b356b82
23934ad
 
b356b82
 
 
23934ad
 
 
 
 
 
18a8ea2
0cd2b77
 
 
 
b356b82
 
 
 
 
 
 
 
 
 
 
23934ad
 
 
 
 
 
 
 
 
b356b82
 
23934ad
 
0cd2b77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e98bce2
0cd2b77
 
 
 
 
 
 
 
 
e98bce2
0cd2b77
 
 
 
 
 
 
e98bce2
0cd2b77
 
 
 
 
e98bce2
18a8ea2
b356b82
e98bce2
 
 
 
 
b356b82
 
23934ad
0cd2b77
69d1e96
17e78e6
 
 
 
0cd2b77
 
b356b82
0cd2b77
 
b356b82
0cd2b77
b356b82
0cd2b77
b356b82
0cd2b77
 
b356b82
0cd2b77
 
b356b82
17e78e6
 
 
 
 
e98bce2
0c29456
e98bce2
 
 
 
 
 
 
 
b356b82
 
e98bce2
b356b82
0c29456
 
 
b356b82
 
23934ad
e98bce2
 
0c29456
 
17e78e6
b356b82
 
0cd2b77
 
 
17e78e6
 
e98bce2
 
b356b82
0cd2b77
dd401c6
 
 
23934ad
0cd2b77
e98bce2
dd401c6
 
e98bce2
 
0cd2b77
e98bce2
0cd2b77
e98bce2
 
23934ad
0cd2b77
e98bce2
 
 
b356b82
 
 
 
 
e98bce2
b356b82
e98bce2
 
 
 
 
23934ad
e98bce2
 
23934ad
e98bce2
 
 
 
 
 
 
 
0cd2b77
b356b82
e98bce2
0cd2b77
 
e98bce2
0cd2b77
 
e98bce2
 
23934ad
 
0cd2b77
dd401c6
 
 
 
 
0cd2b77
 
 
b356b82
0cd2b77
e98bce2
2125c39
e98bce2
 
2125c39
e98bce2
 
23934ad
e98bce2
23934ad
e98bce2
 
23934ad
2125c39
17e78e6
 
 
 
e98bce2
17e78e6
e98bce2
0cd2b77
 
 
e98bce2
0cd2b77
 
 
e98bce2
0cd2b77
e98bce2
23934ad
 
17e78e6
 
b356b82
 
 
 
 
 
 
 
 
bbfa293
 
0cd2b77
e98bce2
bbfa293
0cd2b77
 
bbfa293
b356b82
18a8ea2
b356b82
23934ad
 
bbfa293
18a8ea2
0c29456
bbfa293
e98bce2
2125c39
23934ad
2125c39
23934ad
 
 
 
0cd2b77
 
23934ad
 
 
0c29456
bbfa293
0cd2b77
 
b356b82
 
 
18a8ea2
0cd2b77
 
bbfa293
0cd2b77
 
0c29456
23934ad
18a8ea2
0c29456
bbfa293
 
0c29456
bbfa293
 
b356b82
 
 
 
 
e98bce2
 
bbfa293
b356b82
 
 
 
 
17e78e6
 
 
e98bce2
b356b82
17e78e6
e98bce2
 
 
 
0cd2b77
17e78e6
 
0cd2b77
 
 
b356b82
e98bce2
bbfa293
 
b356b82
 
 
18a8ea2
b356b82
 
18a8ea2
 
b356b82
 
 
 
 
 
 
 
 
bbfa293
b356b82
 
 
 
 
18a8ea2
 
 
 
 
 
b356b82
0cd2b77
b356b82
 
 
 
18a8ea2
b356b82
18a8ea2
 
 
 
 
 
 
 
b356b82
 
 
bbfa293
0cd2b77
b356b82
 
 
 
0cd2b77
b356b82
bbfa293
e98bce2
b356b82
 
 
 
 
 
 
 
 
e98bce2
 
 
b356b82
 
 
 
 
2125c39
b356b82
18a8ea2
b356b82
18a8ea2
 
 
 
b356b82
 
 
 
18a8ea2
 
 
b356b82
18a8ea2
 
b356b82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17e78e6
0cd2b77
e98bce2
17e78e6
b356b82
18a8ea2
b356b82
23934ad
 
b356b82
0cd2b77
 
e98bce2
 
0cd2b77
 
 
23934ad
e98bce2
b356b82
 
 
 
 
 
e98bce2
 
 
17e78e6
0cd2b77
b356b82
 
 
 
 
0cd2b77
e98bce2
b356b82
 
e98bce2
b356b82
 
e98bce2
 
0cd2b77
e98bce2
 
 
 
b356b82
 
e98bce2
 
b356b82
 
e98bce2
 
 
69d1e96
b356b82
18a8ea2
b356b82
23934ad
 
b356b82
0cd2b77
 
 
e98bce2
0cd2b77
b356b82
e98bce2
 
 
0cd2b77
 
 
23934ad
e98bce2
b356b82
 
 
 
 
 
e98bce2
 
 
17e78e6
0cd2b77
b356b82
 
 
 
 
dd401c6
e98bce2
 
0cd2b77
b356b82
 
e98bce2
 
b356b82
e98bce2
 
 
 
0cd2b77
e98bce2
17e78e6
e98bce2
0cd2b77
b356b82
 
 
e98bce2
 
b356b82
 
e98bce2
 
 
 
b356b82
18a8ea2
b356b82
23934ad
 
e98bce2
b356b82
e98bce2
b356b82
 
 
 
e98bce2
 
 
 
 
b356b82
e98bce2
 
23934ad
b356b82
 
 
 
 
 
 
 
 
 
 
18a8ea2
b356b82
 
 
 
e98bce2
b356b82
 
 
 
 
e98bce2
b356b82
e98bce2
 
b356b82
 
 
 
 
 
 
 
 
e98bce2
b356b82
 
 
 
e98bce2
b356b82
23934ad
b356b82
 
 
23934ad
b356b82
23934ad
b356b82
23934ad
 
b356b82
23934ad
b356b82
23934ad
 
b356b82
23934ad
b356b82
23934ad
 
b356b82
23934ad
b356b82
23934ad
 
b356b82
23934ad
b356b82
23934ad
b356b82
 
 
23934ad
 
b356b82
0cd2b77
 
 
 
 
 
17e78e6
b356b82
 
 
0cd2b77
b356b82
 
 
 
 
 
e98bce2
23934ad
b356b82
 
 
 
 
e98bce2
0cd2b77
 
e98bce2
b356b82
 
18a8ea2
 
b356b82
 
e98bce2
17e78e6
b356b82
 
 
 
 
 
 
e98bce2
b356b82
 
23934ad
 
 
 
 
 
 
 
 
 
 
 
 
 
e98bce2
 
 
 
18a8ea2
e98bce2
18a8ea2
23934ad
 
e98bce2
 
 
 
0cd2b77
18a8ea2
23934ad
 
18a8ea2
 
 
 
69d1e96
17e78e6
e98bce2
 
 
17e78e6
e98bce2
 
b356b82
17e78e6
e98bce2
 
23934ad
 
e98bce2
0cd2b77
17e78e6
e98bce2
 
 
 
 
 
 
 
0cd2b77
17e78e6
e98bce2
b356b82
e98bce2
0cd2b77
17e78e6
e98bce2
b356b82
e98bce2
 
 
 
18a8ea2
e98bce2
 
b356b82
e98bce2
18a8ea2
e98bce2
 
 
 
18a8ea2
e98bce2
 
b356b82
 
18a8ea2
e98bce2
b356b82
e98bce2
b356b82
 
 
18a8ea2
e98bce2
 
 
0cd2b77
 
b356b82
23934ad
b356b82
 
23934ad
0cd2b77
 
b356b82
23934ad
 
b356b82
0cd2b77
23934ad
 
 
 
e98bce2
23934ad
 
 
 
 
69d1e96
18a8ea2
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
import gradio as gr
from googleapiclient.discovery import build
from groq import Groq
import os
import sqlite3
from datetime import datetime, timedelta
import re
import uuid

API_KEY = os.getenv("YOUTUBE_API_KEY")
GROQ_API_KEY = os.getenv("GROQ_API_KEY")

youtube = build("youtube", "v3", developerKey=API_KEY)
groq_client = Groq(api_key=GROQ_API_KEY) if GROQ_API_KEY else None

# Global storage
ai_pick_storage = {"videos": [], "ratings": {}, "timestamp": None}

# ============================================
# 🌐 UI Language Translations
# ============================================
UI_LANG = {
    "en": {
        "title": "🎬 YOUTUBE TREND ANALYZER 📊",
        "search_keyword": "Search Keyword",
        "enter_keyword": "Enter keyword...",
        "search": "🔍 SEARCH",
        "refresh": "🔄 Refresh",
        "country": "Country",
        "language": "Language", 
        "sort_by": "Sort By",
        "period": "Period",
        "max_results": "Max Results",
        "click_autofill": "Click to auto-fill",
        "total": "Total",
        "results": "results",
        "views": "Views",
        "likes": "Likes",
        "subs": "Subs",
        "date": "Date",
        "rank": "Rank",
        "thumb": "Thumb",
        "title_col": "Title",
        "channel": "Channel",
        "comments": "Cmts",
        "ai_pick_col": "AI Pick",
        "no_keyword": "Please enter a search keyword!",
        "no_results": "No results found.",
        "sort_options": {"Most Viewed": "viewCount", "Latest": "date", "Relevance": "relevance", "Top Rated": "rating"},
        "date_options": {"All Time": "", "Today": "today", "This Week": "thisWeek", "This Month": "thisMonth", "This Year": "thisYear"},
    },
    "ko": {
        "title": "🎬 유튜브 트렌드 분석기 📊",
        "search_keyword": "검색어",
        "enter_keyword": "검색어 입력...",
        "search": "🔍 검색",
        "refresh": "🔄 새로고침",
        "country": "국가",
        "language": "언어",
        "sort_by": "정렬",
        "period": "기간",
        "max_results": "최대 결과",
        "click_autofill": "클릭시 자동 입력",
        "total": "총",
        "results": "개 결과",
        "views": "조회수",
        "likes": "좋아요",
        "subs": "구독자",
        "date": "날짜",
        "rank": "순위",
        "thumb": "썸네일",
        "title_col": "제목",
        "channel": "채널",
        "comments": "댓글",
        "ai_pick_col": "AI추천",
        "no_keyword": "검색어를 입력하세요!",
        "no_results": "검색 결과가 없습니다.",
        "sort_options": {"조회수 순": "viewCount", "최신순": "date", "관련성 순": "relevance", "평점 순": "rating"},
        "date_options": {"전체 기간": "", "오늘": "today", "이번 주": "thisWeek", "이번 달": "thisMonth", "올해": "thisYear"},
    }
}

# ============================================
# 🎨 CSS
# ============================================
css = """
@import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');

/* Hide ALL Hugging Face elements */
#space-header, .space-header, header, .huggingface-space-header,
[data-testid="space-header"], .svelte-1ed2p3z, .svelte-kqij2n,
.svelte-1kyws56, .wrap.svelte-1kyws56, button.svelte-1kyws56,
.duplicate-button, .settings-button, [class*="settings"],
[class*="duplicate"], .embed-buttons, .buttons-container,
header button, .gr-button-icon, footer, .footer,
.gradio-container footer, .built-with, [class*="footer"],
.built-with-gradio, a[href*="gradio.app"],
.gradio-container > div:first-child > button,
.gradio-container > header {
    display: none !important;
    visibility: hidden !important;
    height: 0 !important;
    width: 0 !important;
    padding: 0 !important;
    margin: 0 !important;
    overflow: hidden !important;
    opacity: 0 !important;
    pointer-events: none !important;
    position: absolute !important;
    left: -9999px !important;
}

.gradio-container {
    background-color: #FEF9C3 !important;
    background-image: radial-gradient(#1F2937 1px, transparent 1px) !important;
    background-size: 20px 20px !important;
    min-height: 100vh !important;
    font-family: 'Comic Neue', cursive, sans-serif !important;
}

.header-text h1 {
    font-family: 'Bangers', cursive !important;
    color: #1F2937 !important;
    font-size: 2.8rem !important;
    text-align: center !important;
    text-shadow: 4px 4px 0px #FACC15, 6px 6px 0px #1F2937 !important;
}

.gr-panel, .gr-box, .gr-form, .block, .gr-group {
    background: #FFFFFF !important;
    border: 3px solid #1F2937 !important;
    border-radius: 8px !important;
    box-shadow: 6px 6px 0px #1F2937 !important;
}

.gr-button-primary, button.primary {
    background: #3B82F6 !important;
    border: 3px solid #1F2937 !important;
    border-radius: 8px !important;
    color: #FFFFFF !important;
    font-family: 'Bangers', cursive !important;
    font-size: 1.2rem !important;
    box-shadow: 5px 5px 0px #1F2937 !important;
}
.gr-button-primary:hover { background: #2563EB !important; }

.gr-button-secondary, button.secondary {
    background: #EF4444 !important;
    border: 3px solid #1F2937 !important;
    color: #FFFFFF !important;
    font-family: 'Bangers', cursive !important;
    box-shadow: 4px 4px 0px #1F2937 !important;
}

textarea, input[type="text"] {
    background: #FFFFFF !important;
    border: 3px solid #1F2937 !important;
    border-radius: 8px !important;
    font-family: 'Comic Neue', cursive !important;
    font-weight: 700 !important;
}

label { color: #1F2937 !important; font-family: 'Comic Neue', cursive !important; font-weight: 700 !important; }

::-webkit-scrollbar { width: 12px; }
::-webkit-scrollbar-track { background: #FEF9C3; border: 2px solid #1F2937; }
::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; }
::selection { background: #FACC15; color: #1F2937; }

/* LLM Result box */
.llm-result textarea {
    background: #1F2937 !important;
    color: #10B981 !important;
    border: 3px solid #10B981 !important;
    border-radius: 8px !important;
    font-family: 'Courier New', monospace !important;
    font-size: 14px !important;
    line-height: 1.6 !important;
}
"""

# DB 초기화
def init_db():
    conn = sqlite3.connect("youtube_data.db")
    c = conn.cursor()
    c.execute('''CREATE TABLE IF NOT EXISTS videos (
        video_id TEXT PRIMARY KEY, title TEXT, channel_id TEXT, channel_name TEXT,
        thumbnail TEXT, published_at TEXT, first_seen TEXT)''')
    c.execute('''CREATE TABLE IF NOT EXISTS video_stats (
        id INTEGER PRIMARY KEY AUTOINCREMENT, video_id TEXT, views INTEGER,
        likes INTEGER, comments INTEGER, recorded_at TEXT)''')
    c.execute('''CREATE TABLE IF NOT EXISTS channels (
        channel_id TEXT PRIMARY KEY, channel_name TEXT, first_seen TEXT)''')
    c.execute('''CREATE TABLE IF NOT EXISTS channel_stats (
        id INTEGER PRIMARY KEY AUTOINCREMENT, channel_id TEXT, subscribers INTEGER, recorded_at TEXT)''')
    c.execute('''CREATE TABLE IF NOT EXISTS search_history (
        id INTEGER PRIMARY KEY AUTOINCREMENT, keyword TEXT, country TEXT,
        language TEXT, sort_by TEXT, results_count INTEGER, searched_at TEXT)''')
    c.execute('''CREATE TABLE IF NOT EXISTS trending_alerts (
        id INTEGER PRIMARY KEY AUTOINCREMENT, video_id TEXT, alert_type TEXT,
        old_value INTEGER, new_value INTEGER, change_percent REAL, detected_at TEXT)''')
    conn.commit()
    conn.close()

init_db()

# Country & Language codes
COUNTRIES = {
    "Worldwide": ("", ""), "United States": ("US", "en"), "United Kingdom": ("GB", "en"),
    "Canada": ("CA", "en"), "Australia": ("AU", "en"), "Germany": ("DE", "de"),
    "France": ("FR", "fr"), "Japan": ("JP", "ja"), "South Korea": ("KR", "ko"),
    "Brazil": ("BR", "pt"), "Mexico": ("MX", "es"), "Spain": ("ES", "es"),
    "Italy": ("IT", "it"), "Russia": ("RU", "ru"), "India": ("IN", "hi"),
    "Indonesia": ("ID", "id"), "Thailand": ("TH", "th"), "Vietnam": ("VN", "vi"),
    "Philippines": ("PH", "tl"), "Turkey": ("TR", "tr"), "Saudi Arabia": ("SA", "ar"),
    "Egypt": ("EG", "ar"), "South Africa": ("ZA", "en"), "Nigeria": ("NG", "en"),
    "Argentina": ("AR", "es"), "Colombia": ("CO", "es"), "Poland": ("PL", "pl"),
    "Netherlands": ("NL", "nl"), "Sweden": ("SE", "sv"), "Switzerland": ("CH", "de"),
    "Taiwan": ("TW", "zh"), "Hong Kong": ("HK", "zh"), "China": ("CN", "zh"),
    "Singapore": ("SG", "en"), "Malaysia": ("MY", "ms"), "UAE": ("AE", "ar"),
}

LANGUAGES = {
    "Auto (by Country)": "", "English": "en", "Korean": "ko", "Spanish": "es",
    "Portuguese": "pt", "French": "fr", "German": "de", "Italian": "it",
    "Russian": "ru", "Japanese": "ja", "Chinese": "zh", "Hindi": "hi",
    "Arabic": "ar", "Turkish": "tr", "Indonesian": "id", "Vietnamese": "vi",
    "Thai": "th", "Dutch": "nl", "Polish": "pl", "Swedish": "sv",
}

def format_count(count):
    if count is None: return "0"
    count = int(count)
    if count >= 1000000000: return f"{count/1000000000:.1f}B"
    elif count >= 1000000: return f"{count/1000000:.1f}M"
    elif count >= 1000: return f"{count/1000:.1f}K"
    return str(count)

def call_llm(prompt, max_tokens=2000):
    if not groq_client:
        return "⚠️ LLM API not configured. Set GROQ_API_KEY."
    try:
        completion = groq_client.chat.completions.create(
            model="openai/gpt-oss-120b",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7, max_completion_tokens=max_tokens, top_p=1, stream=True, stop=None
        )
        result = ""
        for chunk in completion:
            if chunk.choices[0].delta.content:
                result += chunk.choices[0].delta.content
        return result
    except Exception as e:
        return f"Error: {e}"

def get_ai_pick_rating(videos_data):
    global ai_pick_storage
    if not videos_data: return {}
    
    if groq_client:
        try:
            sample = videos_data[:50]
            video_info = "\n".join([
                f"#{i+1}. {v['title'][:40]}, Views:{v['views']}, Likes:{v['likes']}, Subs:{v.get('subs',0)}"
                for i, v in enumerate(sample)
            ])
            prompt = f"Rate YouTube videos 0-4. 0=None,1=♥,2=⭐,3=⭐⭐,4=⭐⭐⭐. Consider engagement, viral potential. Format: 1:3,2:2,3:4\n\n{video_info}\n\nResponse (number:rating only):"
            result = call_llm(prompt, 1500)
            if result and "Error" not in result and "⚠️" not in result:
                ratings = {}
                for idx, rating in re.findall(r'(\d+):(\d)', result):
                    ratings[int(idx)-1] = int(rating)
                if len(videos_data) > 50:
                    local = calculate_local_rating(videos_data[50:])
                    for k, v in local.items(): ratings[k + 50] = v
                ai_pick_storage = {"videos": videos_data, "ratings": ratings, "timestamp": datetime.now().isoformat()}
                return ratings
        except: pass
    
    ratings = calculate_local_rating(videos_data)
    ai_pick_storage = {"videos": videos_data, "ratings": ratings, "timestamp": datetime.now().isoformat()}
    return ratings

def calculate_local_rating(videos_data):
    ratings = {}
    if not videos_data: return ratings
    views_list = [v['views'] for v in videos_data if v['views'] > 0]
    if not views_list: return {i: 0 for i in range(len(videos_data))}
    avg_views, max_views = sum(views_list)/len(views_list), max(views_list)
    
    for i, v in enumerate(videos_data):
        views, likes, comments, subs = v['views'], v['likes'], v['comments'], v.get('subs', 0)
        score = 0
        if views > 0:
            score += min(40, (views/max_views)*40)
            score += min(30, ((likes+comments*2)/views)*300)
        if subs > 0 and views > 0: score += min(30, (views/subs)*10)
        elif views > avg_views: score += 15
        
        if score >= 70: ratings[i] = 4
        elif score >= 50: ratings[i] = 3
        elif score >= 30: ratings[i] = 2
        elif score >= 15: ratings[i] = 1
        else: ratings[i] = 0
    return ratings

def get_rating_display(rating):
    return {0: "-", 1: "♥", 2: "⭐", 3: "⭐⭐", 4: "⭐⭐⭐"}.get(rating, "-")

def get_real_trending_keywords(region_code="US", language="en"):
    try:
        response = youtube.videos().list(part="snippet", chart="mostPopular", regionCode=region_code or "US", maxResults=50).execute()
        keywords, seen = [], set()
        for item in response.get("items", []):
            for tag in item["snippet"].get("tags", [])[:3]:
                if tag.lower() not in seen and 2 <= len(tag) <= 20:
                    keywords.append(tag); seen.add(tag.lower())
            channel = item["snippet"]["channelTitle"]
            if channel.lower() not in seen: keywords.append(channel); seen.add(channel.lower())
            if len(keywords) >= 20: break
        return keywords[:20] if keywords else ["AI","gaming","music","vlog","shorts","news"]
    except: return ["AI","ChatGPT","gaming","music","vlog","shorts","news","tech"]

def save_to_db(videos_data, channels_data, keyword, country, language, sort_by):
    conn = sqlite3.connect("youtube_data.db")
    c = conn.cursor()
    now = datetime.now().isoformat()
    c.execute('INSERT INTO search_history VALUES (NULL,?,?,?,?,?,?)', (keyword, country, language, sort_by, len(videos_data), now))
    for video in videos_data:
        c.execute('INSERT OR IGNORE INTO videos VALUES (?,?,?,?,?,?,?)',
                  (video['video_id'], video['title'], video['channel_id'], video['channel_name'], video['thumbnail'], video['published_at'], now))
        c.execute('SELECT views FROM video_stats WHERE video_id=? ORDER BY recorded_at DESC LIMIT 1', (video['video_id'],))
        prev = c.fetchone()
        c.execute('INSERT INTO video_stats VALUES (NULL,?,?,?,?,?)', (video['video_id'], video['views'], video['likes'], video['comments'], now))
        if prev and prev[0] > 0:
            change = ((video['views'] - prev[0]) / prev[0]) * 100
            if change >= 20:
                c.execute('INSERT INTO trending_alerts VALUES (NULL,?,?,?,?,?,?)', (video['video_id'], 'views_surge', prev[0], video['views'], change, now))
    for ch_id, subs in channels_data.items():
        c.execute('INSERT OR IGNORE INTO channels VALUES (?,?,?)', (ch_id, '', now))
        if isinstance(subs, int): c.execute('INSERT INTO channel_stats VALUES (NULL,?,?,?)', (ch_id, subs, now))
    conn.commit(); conn.close()

def get_db_stats():
    try:
        conn = sqlite3.connect("youtube_data.db")
        c = conn.cursor()
        stats = {}
        for t, k in [("videos","videos"),("video_stats","stats"),("channels","channels"),("search_history","searches"),("trending_alerts","alerts")]:
            c.execute(f"SELECT COUNT(*) FROM {t}"); stats[k] = c.fetchone()[0]
        conn.close()
        return stats
    except: return {"videos":0,"stats":0,"channels":0,"searches":0,"alerts":0}

def update_trending(country):
    region, lang = COUNTRIES.get(country, ("", ""))
    return gr.update(choices=get_real_trending_keywords(region or "US", lang or "en"), value=None)

def use_trending_keyword(kw):
    return kw if kw else ""

# ============================================
# 🔍 Main Search Function
# ============================================
def search_videos(keyword, country, language, sort_by, date_filter, max_results, ui_lang):
    L = UI_LANG.get(ui_lang, UI_LANG["en"])
    if not keyword or not keyword.strip():
        return f"⚠️ {L['no_keyword']}", "📊 DB: -"
    
    max_results = int(max_results)
    all_items, next_page = [], None
    region_code, default_lang = COUNTRIES.get(country, ("", ""))
    lang_code = default_lang if language in ["Auto (by Country)", "자동 (국가 기반)"] else LANGUAGES.get(language, "")
    
    sort_value = L["sort_options"].get(sort_by, "viewCount")
    date_value = L["date_options"].get(date_filter, "")
    
    params = {"q": keyword, "part": "snippet", "type": "video", "order": sort_value}
    if region_code: params["regionCode"] = region_code
    if lang_code: params["relevanceLanguage"] = lang_code
    if date_value:
        deltas = {"today": 1, "thisWeek": 7, "thisMonth": 30, "thisYear": 365}
        params["publishedAfter"] = (datetime.utcnow() - timedelta(days=deltas.get(date_value, 0))).strftime("%Y-%m-%dT%H:%M:%SZ")
    
    while len(all_items) < max_results:
        params["maxResults"] = min(50, max_results - len(all_items))
        if next_page: params["pageToken"] = next_page
        try:
            resp = youtube.search().list(**params).execute()
        except Exception as e:
            return f"API Error: {e}", "📊 DB: Error"
        items = resp.get("items", [])
        if not items: break
        all_items.extend(items)
        next_page = resp.get("nextPageToken")
        if not next_page: break
    
    if not all_items:
        return f"{L['no_results']}", "📊 DB: -"
    
    video_ids = [item["id"]["videoId"] for item in all_items]
    channel_ids = list(set([item["snippet"]["channelId"] for item in all_items]))
    
    video_stats = {}
    for i in range(0, len(video_ids), 50):
        try:
            for v in youtube.videos().list(id=",".join(video_ids[i:i+50]), part="statistics").execute().get("items", []):
                s = v["statistics"]
                video_stats[v["id"]] = {"views": int(s.get("viewCount", 0)), "likes": int(s.get("likeCount", 0)), "comments": int(s.get("commentCount", 0))}
        except: pass
    
    channel_subs, channel_subs_raw = {}, {}
    for i in range(0, len(channel_ids), 50):
        try:
            for ch in youtube.channels().list(id=",".join(channel_ids[i:i+50]), part="statistics").execute().get("items", []):
                sub = ch["statistics"].get("subscriberCount", "0")
                if sub: channel_subs_raw[ch["id"]] = int(sub); channel_subs[ch["id"]] = format_count(int(sub))
        except: pass
    
    videos_data = []
    for item in all_items:
        vid, snip = item["id"]["videoId"], item["snippet"]
        st = video_stats.get(vid, {"views": 0, "likes": 0, "comments": 0})
        videos_data.append({
            "video_id": vid, "title": snip["title"], "channel_id": snip["channelId"],
            "channel_name": snip["channelTitle"], "thumbnail": snip["thumbnails"]["medium"]["url"],
            "published_at": snip["publishedAt"], "views": st.get("views", 0),
            "likes": st.get("likes", 0), "comments": st.get("comments", 0),
            "subs": channel_subs_raw.get(snip["channelId"], 0),
        })
    
    ai_ratings = get_ai_pick_rating(videos_data)
    save_to_db(videos_data, channel_subs_raw, keyword, country, language, sort_by)
    
    uid = str(uuid.uuid4()).replace("-", "")[:8]
    
    html = f'''
    <style>
        #tbl_{uid} {{ width:100%; border-collapse:collapse; font-family:'Comic Neue',cursive; }}
        #tbl_{uid} th {{ 
            background:#EF4444; color:#fff; padding:12px 6px; border:2px solid #1F2937;
            font-family:'Bangers',cursive; cursor:pointer; user-select:none;
        }}
        #tbl_{uid} th:hover {{ background:#DC2626; }}
        #tbl_{uid} th.sort-asc::after {{ content:" ▲"; color:#FACC15; }}
        #tbl_{uid} th.sort-desc::after {{ content:" ▼"; color:#FACC15; }}
        #tbl_{uid} td {{ padding:8px 6px; border-bottom:2px solid #1F2937; background:#FFF; vertical-align:middle; }}
        #tbl_{uid} tr:hover td {{ background:#FEF9C3; }}
        #tbl_{uid} img {{ border-radius:4px; border:2px solid #1F2937; }}
        #tbl_{uid} a {{ color:#3B82F6; text-decoration:none; font-weight:700; }}
        #tbl_{uid} a:hover {{ color:#EF4444; }}
        .ai-pick {{ color:#FACC15; text-shadow:1px 1px 0 #1F2937; font-size:1rem; }}
        .hdr_{uid} {{ background:#3B82F6; color:#fff; padding:15px; border-radius:8px; border:3px solid #1F2937; box-shadow:4px 4px 0 #1F2937; margin-bottom:15px; font-family:'Comic Neue',cursive; }}
        .sortbtn_{uid} {{ background:#FACC15; border:2px solid #1F2937; border-radius:4px; padding:6px 12px; margin:2px; cursor:pointer; font-weight:700; font-family:'Comic Neue',cursive; }}
        .sortbtn_{uid}:hover {{ background:#1F2937; color:#FACC15; }}
    </style>
    
    <div class="hdr_{uid}">
        🎬 {L["total"]} <b>{len(videos_data)}</b> {L["results"]} | 🔍 "{keyword}" | 🌍 {country}
        <br><span style="font-size:0.85rem">🤖 AI Pick: ♥ ⭐ ⭐⭐ ⭐⭐⭐ | 💡 {"헤더 클릭 = 정렬" if ui_lang=="ko" else "Click header to sort"}</span>
        <div style="margin-top:10px">
            <button class="sortbtn_{uid}" onclick="doSort_{uid}(4,'n')">{L["subs"]}</button>
            <button class="sortbtn_{uid}" onclick="doSort_{uid}(5,'n')">{L["views"]}</button>
            <button class="sortbtn_{uid}" onclick="doSort_{uid}(6,'n')">{L["likes"]}</button>
            <button class="sortbtn_{uid}" onclick="doSort_{uid}(7,'n')">{L["comments"]}</button>
            <button class="sortbtn_{uid}" onclick="doSort_{uid}(8,'n')">AI</button>
            <button class="sortbtn_{uid}" onclick="doSort_{uid}(9,'s')">{L["date"]}</button>
        </div>
    </div>
    
    <div style="max-height:700px; overflow-y:auto; border:3px solid #1F2937; border-radius:8px;">
    <table id="tbl_{uid}">
        <thead><tr>
            <th style="width:45px">{L["rank"]}</th>
            <th style="width:120px">{L["thumb"]}</th>
            <th>{L["title_col"]}</th>
            <th style="width:100px">{L["channel"]}</th>
            <th onclick="doSort_{uid}(4,'n')" style="width:70px;cursor:pointer">{L["subs"]}</th>
            <th onclick="doSort_{uid}(5,'n')" style="width:75px;cursor:pointer">{L["views"]}</th>
            <th onclick="doSort_{uid}(6,'n')" style="width:60px;cursor:pointer">{L["likes"]}</th>
            <th onclick="doSort_{uid}(7,'n')" style="width:55px;cursor:pointer">{L["comments"]}</th>
            <th onclick="doSort_{uid}(8,'n')" style="width:65px;cursor:pointer">{L["ai_pick_col"]}</th>
            <th onclick="doSort_{uid}(9,'s')" style="width:90px;cursor:pointer">{L["date"]}</th>
        </tr></thead>
        <tbody>
    '''
    
    for i, v in enumerate(videos_data):
        title_short = v["title"][:42] + "..." if len(v["title"]) > 42 else v["title"]
        ch_short = v["channel_name"][:12] + "..." if len(v["channel_name"]) > 12 else v["channel_name"]
        url = f"https://youtube.com/watch?v={v['video_id']}"
        ch_url = f"https://youtube.com/channel/{v['channel_id']}"
        rating = ai_ratings.get(i, 0)
        rank_color = "#FFD700" if i==0 else ("#C0C0C0" if i==1 else ("#CD7F32" if i==2 else "#EF4444"))
        
        html += f'''<tr>
            <td style="text-align:center;font-family:'Bangers',cursive;color:{rank_color};font-size:1.1rem" data-v="{i+1}">{i+1}</td>
            <td><a href="{url}" target="_blank"><img src="{v['thumbnail']}" width="110" height="62"></a></td>
            <td data-v="{v['title'][:60].replace('"','&quot;')}"><a href="{url}" target="_blank" title="{v['title']}">{title_short}</a></td>
            <td data-v="{v['channel_name'][:30].replace('"','&quot;')}"><a href="{ch_url}" target="_blank">{ch_short}</a></td>
            <td data-v="{v['subs']}" style="text-align:right">{format_count(v['subs'])}</td>
            <td data-v="{v['views']}" style="text-align:right;color:#3B82F6;font-weight:700">{format_count(v['views'])}</td>
            <td data-v="{v['likes']}" style="text-align:right">{format_count(v['likes'])}</td>
            <td data-v="{v['comments']}" style="text-align:right">{format_count(v['comments'])}</td>
            <td data-v="{rating}" style="text-align:center" class="ai-pick">{get_rating_display(rating)}</td>
            <td data-v="{v['published_at'][:10]}">{v['published_at'][:10]}</td>
        </tr>'''
    
    html += f'''
        </tbody>
    </table>
    </div>
    
    <script>
    (function() {{
        var sortState_{uid} = {{}};
        
        window.doSort_{uid} = function(colIdx, type) {{
            var tbl = document.getElementById('tbl_{uid}');
            if (!tbl) return;
            
            var tbody = tbl.querySelector('tbody');
            var rows = Array.from(tbody.querySelectorAll('tr'));
            var headers = tbl.querySelectorAll('th');
            
            var asc = sortState_{uid}[colIdx] !== 'asc';
            sortState_{uid} = {{}};
            sortState_{uid}[colIdx] = asc ? 'asc' : 'desc';
            
            headers.forEach(function(h) {{ h.classList.remove('sort-asc', 'sort-desc'); }});
            if (headers[colIdx]) headers[colIdx].classList.add(asc ? 'sort-asc' : 'sort-desc');
            
            rows.sort(function(a, b) {{
                var aCell = a.cells[colIdx];
                var bCell = b.cells[colIdx];
                if (!aCell || !bCell) return 0;
                
                var aVal = aCell.getAttribute('data-v') || aCell.textContent.trim();
                var bVal = bCell.getAttribute('data-v') || bCell.textContent.trim();
                
                if (type === 'n') {{
                    aVal = parseFloat(aVal) || 0;
                    bVal = parseFloat(bVal) || 0;
                    return asc ? aVal - bVal : bVal - aVal;
                }} else {{
                    return asc ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal);
                }}
            }});
            
            rows.forEach(function(row) {{ tbody.appendChild(row); }});
        }};
    }})();
    </script>
    '''
    
    stats = get_db_stats()
    return html, f"📊 DB: Videos {stats['videos']} | Records {stats['stats']} | Channels {stats['channels']} | Searches {stats['searches']}"

# ============================================
# 🔥 Trending Alerts
# ============================================
def show_trending_alerts(ui_lang):
    is_ko = ui_lang == "ko"
    
    conn = sqlite3.connect("youtube_data.db")
    c = conn.cursor()
    c.execute('''SELECT ta.video_id, v.title, v.channel_name, v.thumbnail, ta.old_value, ta.new_value, ta.change_percent, ta.detected_at
        FROM trending_alerts ta JOIN videos v ON ta.video_id = v.video_id ORDER BY ta.detected_at DESC LIMIT 30''')
    alerts = c.fetchall()
    conn.close()
    
    info_box = f'''
    <div style="background:linear-gradient(135deg,#EF4444,#DC2626);color:#fff;padding:20px;border:3px solid #1F2937;border-radius:8px;margin-bottom:20px;font-family:'Comic Neue',cursive">
        <h2 style="font-family:'Bangers',cursive;margin:0 0 15px 0;font-size:1.8rem">🔥 {"급상승 - 조회수 20%+ 급증 감지" if is_ko else "TRENDING - 20%+ Sudden View Surge"}</h2>
        <table style="width:100%;color:#fff;font-size:14px;line-height:1.8">
            <tr><td style="width:100px;font-weight:bold">📌 {"정의" if is_ko else "What"}</td><td>{"이전 대비 조회수가 20% 이상 급증한 영상" if is_ko else "Videos with 20%+ view increase vs. previous check"}</td></tr>
            <tr><td style="font-weight:bold">🎯 {"목적" if is_ko else "Purpose"}</td><td>{"지금 바로 바이럴 중인 영상 포착" if is_ko else "Catch videos going viral RIGHT NOW"}</td></tr>
            <tr><td style="font-weight:bold">⏱️ {"작동" if is_ko else "Trigger"}</td><td>{"동일 영상 재검색시 조회수 변화 감지" if is_ko else "Detected when same video is searched again"}</td></tr>
            <tr><td style="font-weight:bold">💡 {"활용" if is_ko else "Best for"}</td><td>{"뉴스, 이슈, 핫토픽 발굴" if is_ko else "News, breaking stories, hot topics"}</td></tr>
        </table>
    </div>'''
    
    if not alerts:
        try:
            resp = youtube.videos().list(part="snippet,statistics", chart="mostPopular", regionCode="KR" if is_ko else "US", maxResults=20).execute()
            html = info_box + f'''<div style="background:#FEF9C3;padding:15px;border:3px solid #1F2937;border-radius:8px;margin-bottom:20px;font-family:'Comic Neue',cursive">
                <p style="margin:0">📢 {"아직 급상승 알림이 없습니다. 검색을 여러 번 실행하면 조회수 변화를 감지합니다!" if is_ko else "No surge alerts yet. Run searches multiple times to detect view changes!"}</p>
            </div><h3 style="font-family:'Bangers',cursive;color:#1F2937">{"현재 인기 영상" if is_ko else "Current Popular Videos"}</h3><div style="display:flex;flex-wrap:wrap;gap:15px">'''
            
            for i, item in enumerate(resp.get("items", [])[:20], 1):
                snip, stats = item["snippet"], item["statistics"]
                title = snip["title"][:32] + "..." if len(snip["title"]) > 32 else snip["title"]
                html += f'''<div style="width:190px;background:#FFF;border:3px solid #1F2937;border-radius:8px;padding:12px;box-shadow:4px 4px 0 #1F2937;font-family:'Comic Neue',cursive">
                    <a href="https://youtube.com/watch?v={item['id']}" target="_blank"><img src="{snip['thumbnails']['medium']['url']}" style="width:100%;border-radius:5px;border:2px solid #1F2937"></a>
                    <p style="margin:10px 0 5px;font-size:12px;font-weight:700;color:#1F2937">{i}. {title}</p>
                    <p style="margin:0;font-size:16px;color:#EF4444;font-weight:700;font-family:'Bangers',cursive">👀 {format_count(int(stats.get('viewCount',0)))}</p>
                </div>'''
            return html + '</div>'
        except Exception as e:
            return info_box + f"<p>Error: {e}</p>"
    
    html = info_box + '<div style="display:flex;flex-wrap:wrap;gap:15px">'
    for vid, title, channel, thumb, old_v, new_v, pct, detected in alerts:
        title = title[:28] + "..." if len(title) > 28 else title
        html += f'''<div style="width:190px;background:#FFF;border:3px solid #EF4444;border-radius:8px;padding:12px;box-shadow:4px 4px 0 #1F2937;font-family:'Comic Neue',cursive">
            <a href="https://youtube.com/watch?v={vid}" target="_blank"><img src="{thumb}" style="width:100%;border-radius:5px;border:2px solid #1F2937"></a>
            <p style="margin:10px 0 5px;font-size:12px;font-weight:700">{title}</p>
            <p style="margin:0;font-size:11px;color:#666">{channel[:18]}</p>
            <p style="margin:5px 0 0;font-size:22px;color:#EF4444;font-weight:700;font-family:'Bangers',cursive">🔥 +{pct:.1f}%</p>
            <p style="margin:2px 0 0;font-size:11px;color:#888">{format_count(old_v)}{format_count(new_v)}</p>
        </div>'''
    return html + '</div>'

# ============================================
# 📈 Top Growing
# ============================================
def show_top_growing(ui_lang):
    is_ko = ui_lang == "ko"
    
    conn = sqlite3.connect("youtube_data.db")
    c = conn.cursor()
    cutoff = (datetime.now() - timedelta(hours=48)).isoformat()
    c.execute('''SELECT v.video_id, v.title, v.channel_name, v.thumbnail,
               MIN(vs.views) as min_v, MAX(vs.views) as max_v,
               ((MAX(vs.views) - MIN(vs.views)) * 100.0 / NULLIF(MIN(vs.views),0)) as growth
        FROM videos v JOIN video_stats vs ON v.video_id = vs.video_id
        WHERE vs.recorded_at > ? GROUP BY v.video_id HAVING min_v > 0 AND max_v > min_v
        ORDER BY growth DESC LIMIT 20''', (cutoff,))
    results = c.fetchall()
    conn.close()
    
    info_box = f'''
    <div style="background:linear-gradient(135deg,#3B82F6,#2563EB);color:#fff;padding:20px;border:3px solid #1F2937;border-radius:8px;margin-bottom:20px;font-family:'Comic Neue',cursive">
        <h2 style="font-family:'Bangers',cursive;margin:0 0 15px 0;font-size:1.8rem">📈 {"급성장 TOP - 48시간 성장률 순위" if is_ko else "TOP GROWING - 48h Growth Rate Ranking"}</h2>
        <table style="width:100%;color:#fff;font-size:14px;line-height:1.8">
            <tr><td style="width:100px;font-weight:bold">📌 {"정의" if is_ko else "What"}</td><td>{"48시간 동안 가장 높은 성장률을 기록한 영상" if is_ko else "Videos with highest growth RATE over 48 hours"}</td></tr>
            <tr><td style="font-weight:bold">🎯 {"목적" if is_ko else "Purpose"}</td><td>{"꾸준히 성장하는 콘텐츠 발굴" if is_ko else "Find consistently rising content"}</td></tr>
            <tr><td style="font-weight:bold">📊 {"계산" if is_ko else "Formula"}</td><td>(Max - Min) / Min × 100%</td></tr>
            <tr><td style="font-weight:bold">💡 {"활용" if is_ko else "Best for"}</td><td>{"에버그린 콘텐츠, 안정적 트렌드" if is_ko else "Evergreen content, stable trends"}</td></tr>
        </table>
    </div>'''
    
    if not results:
        try:
            resp = youtube.videos().list(part="snippet,statistics", chart="mostPopular", regionCode="KR" if is_ko else "US", maxResults=20).execute()
            html = info_box + f'''<div style="background:#FEF9C3;padding:15px;border:3px solid #1F2937;border-radius:8px;margin-bottom:20px;font-family:'Comic Neue',cursive">
                <p style="margin:0">📢 {"데이터 축적 중입니다. 검색을 여러 번 실행하면 성장률이 계산됩니다!" if is_ko else "Accumulating data. Run searches over time to calculate growth rates!"}</p>
            </div><h3 style="font-family:'Bangers',cursive;color:#1F2937">{"현재 인기 영상" if is_ko else "Current Popular Videos"}</h3><div style="display:flex;flex-wrap:wrap;gap:15px">'''
            
            for i, item in enumerate(resp.get("items", [])[:20], 1):
                snip, stats = item["snippet"], item["statistics"]
                views, likes = int(stats.get("viewCount", 0)), int(stats.get("likeCount", 0))
                engagement = (likes / views * 100) if views > 0 else 0
                title = snip["title"][:28] + "..." if len(snip["title"]) > 28 else snip["title"]
                html += f'''<div style="width:190px;background:#FFF;border:3px solid #1F2937;border-radius:8px;padding:12px;box-shadow:4px 4px 0 #1F2937;font-family:'Comic Neue',cursive">
                    <a href="https://youtube.com/watch?v={item['id']}" target="_blank"><img src="{snip['thumbnails']['medium']['url']}" style="width:100%;border-radius:5px;border:2px solid #1F2937"></a>
                    <p style="margin:10px 0 5px;font-size:12px;font-weight:700">{i}. {title}</p>
                    <p style="margin:0;font-size:11px;color:#666">{snip['channelTitle'][:16]}</p>
                    <p style="margin:5px 0 0;font-size:15px;color:#3B82F6;font-weight:700">👀 {format_count(views)}</p>
                    <p style="margin:2px 0 0;font-size:12px;color:#EF4444">❤️ {engagement:.2f}%</p>
                </div>'''
            return html + '</div>'
        except Exception as e:
            return info_box + f"<p>Error: {e}</p>"
    
    html = info_box + '<div style="display:flex;flex-wrap:wrap;gap:15px">'
    for i, (vid, title, channel, thumb, min_v, max_v, growth) in enumerate(results, 1):
        title = title[:28] + "..." if len(title) > 28 else title
        growth_val = growth if growth else 0
        html += f'''<div style="width:190px;background:#FFF;border:3px solid #3B82F6;border-radius:8px;padding:12px;box-shadow:4px 4px 0 #1F2937;font-family:'Comic Neue',cursive">
            <a href="https://youtube.com/watch?v={vid}" target="_blank"><img src="{thumb}" style="width:100%;border-radius:5px;border:2px solid #1F2937"></a>
            <p style="margin:10px 0 5px;font-size:12px;font-weight:700">{i}. {title}</p>
            <p style="margin:0;font-size:11px;color:#666">{channel[:16]}</p>
            <p style="margin:5px 0 0;font-size:22px;color:#3B82F6;font-weight:700;font-family:'Bangers',cursive">📈 +{growth_val:.1f}%</p>
            <p style="margin:2px 0 0;font-size:11px;color:#888">{format_count(int(min_v))}{format_count(int(max_v))}</p>
        </div>'''
    return html + '</div>'

# ============================================
# ⭐ AI Pick
# ============================================
def show_ai_picks(ui_lang):
    is_ko = ui_lang == "ko"
    global ai_pick_storage
    
    if not ai_pick_storage["videos"]:
        return f'''<div style="background:#FACC15;padding:30px;border:3px solid #1F2937;border-radius:8px;text-align:center;font-family:'Comic Neue',cursive">
            <h2 style="font-family:'Bangers',cursive;color:#1F2937">⭐ {"AI 추천 - 데이터 없음" if is_ko else "AI PICK - No Data Yet"}</h2>
            <p style="color:#1F2937;font-size:16px">{"먼저 검색 탭에서 검색을 실행하세요!" if is_ko else "Run a search first in the Search tab!"}</p>
        </div>'''
    
    videos, ratings = ai_pick_storage["videos"], ai_pick_storage["ratings"]
    top_picks = [(i, v, ratings.get(i, 0)) for i, v in enumerate(videos) if ratings.get(i, 0) >= 3]
    top_picks.sort(key=lambda x: (-x[2], -x[1]['views']))
    
    analysis_html = ""
    if groq_client and top_picks:
        info = "\n".join([f"- {v['title'][:50]} (Views:{format_count(v['views'])})" for _, v, _ in top_picks[:5]])
        lang_prompt = "한국어로 답변해주세요." if is_ko else ""
        result = call_llm(f"Analyze top YouTube videos briefly (3-4 sentences):\n{info}\n\n1) Common theme 2) Why popular 3) Content opportunity. {lang_prompt}", 500)
        if result and "Error" not in result and "⚠️" not in result:
            analysis_html = f'''<div style="background:#1F2937;padding:20px;border:3px solid #10B981;border-radius:8px;margin:20px 0">
                <h4 style="color:#FACC15;margin:0 0 10px;font-family:'Bangers',cursive;font-size:1.3rem">🤖 {"AI 분석 결과" if is_ko else "AI ANALYSIS"}</h4>
                <p style="color:#10B981;margin:0;font-size:14px;line-height:1.8;font-family:'Courier New',monospace">{result}</p>
            </div>'''
    
    html = f'''
    <div style="background:linear-gradient(135deg,#FACC15,#F59E0B);padding:20px;border:3px solid #1F2937;border-radius:8px;margin-bottom:15px">
        <h2 style="font-family:'Bangers',cursive;color:#1F2937;margin:0;text-shadow:2px 2px 0 #FFF;font-size:2rem">⭐ {"AI 추천 - TOP 영상" if is_ko else "AI PICK - TOP RECOMMENDATIONS"}</h2>
        <p style="color:#1F2937;margin:10px 0 0;font-family:'Comic Neue',cursive;font-weight:700;font-size:15px">
            {"⭐⭐ 이상 등급 영상" if is_ko else "⭐⭐+ rated videos"}: <b>{len(top_picks)}</b>{"개" if is_ko else " videos"}
        </p>
    </div>
    {analysis_html}
    '''
    
    if not top_picks:
        html += f'''<div style="background:#FFF;padding:30px;border:3px solid #1F2937;border-radius:8px;text-align:center;font-family:'Comic Neue',cursive">
            <p style="color:#1F2937;font-size:16px">{"⭐⭐ 이상 등급 영상이 없습니다. 다른 키워드로 검색해보세요!" if is_ko else "No ⭐⭐+ rated videos found. Try different keywords!"}</p>
        </div>'''
        return html
    
    html += '<div style="display:flex;flex-wrap:wrap;gap:20px">'
    for idx, (_, v, rating) in enumerate(top_picks[:30], 1):
        border = "#FFD700" if rating == 4 else "#C0C0C0"
        title = v["title"][:35] + "..." if len(v["title"]) > 35 else v["title"]
        html += f'''<div style="width:210px;background:#FFF;border:4px solid {border};border-radius:12px;padding:15px;box-shadow:6px 6px 0 #1F2937;font-family:'Comic Neue',cursive">
            <div style="position:relative">
                <a href="https://youtube.com/watch?v={v['video_id']}" target="_blank"><img src="{v['thumbnail']}" style="width:100%;border-radius:8px;border:2px solid #1F2937"></a>
                <span style="position:absolute;top:8px;right:8px;background:#1F2937;color:#FACC15;padding:4px 10px;border-radius:6px;font-family:'Bangers',cursive;font-size:18px">{get_rating_display(rating)}</span>
            </div>
            <p style="margin:12px 0 5px;font-size:13px;font-weight:700;color:#1F2937">{idx}. {title}</p>
            <p style="margin:0;font-size:11px;color:#666"><a href="https://youtube.com/channel/{v['channel_id']}" target="_blank" style="color:#3B82F6">{v["channel_name"][:18]}</a></p>
            <div style="display:flex;justify-content:space-between;margin-top:12px;font-size:12px">
                <span style="color:#3B82F6;font-weight:700">👀 {format_count(v['views'])}</span>
                <span style="color:#EF4444;font-weight:700">❤️ {format_count(v['likes'])}</span>
                <span style="color:#10B981;font-weight:700">💬 {format_count(v['comments'])}</span>
            </div>
        </div>'''
    
    return html + '</div>'

# ============================================
# 🤖 AI Tools Functions
# ============================================
def analyze_keyword_suggest(keyword, ui_lang):
    if not keyword: return "⚠️ 키워드를 입력하세요!" if ui_lang == "ko" else "⚠️ Please enter a keyword!"
    lang = "한국어로 답변해주세요." if ui_lang == "ko" else ""
    return call_llm(f'YouTube SEO expert. For "{keyword}", suggest 15 related keywords.\nFor each: keyword, search volume (High/Med/Low), competition (High/Med/Low), content type.\n{lang}', 1500)

def analyze_trend_prediction(keyword, ui_lang):
    if not keyword: return "⚠️ 키워드를 입력하세요!" if ui_lang == "ko" else "⚠️ Please enter a keyword!"
    lang = "한국어로 답변해주세요." if ui_lang == "ko" else ""
    return call_llm(f'Trend analyst for "{keyword}":\n1) Current status\n2) Peak season\n3) 6-month forecast\n4) Risk factors\n5) Opportunity windows\n6) Emerging topics\n{lang}', 1500)

def analyze_content_ideas(keyword, ui_lang):
    if not keyword: return "⚠️ 주제를 입력하세요!" if ui_lang == "ko" else "⚠️ Please enter a topic!"
    lang = "한국어로 답변해주세요." if ui_lang == "ko" else ""
    return call_llm(f'YouTube strategist for "{keyword}". Generate 10 video ideas:\nEach with: Title, Hook (first 5 sec), Format, Length, Thumbnail concept, Viral score 1-10.\n{lang}', 2000)

def analyze_channel(channel_name, ui_lang):
    if not channel_name: return "⚠️ 채널명을 입력하세요!" if ui_lang == "ko" else "⚠️ Please enter channel name!"
    lang = "한국어로 답변해주세요." if ui_lang == "ko" else ""
    return call_llm(f'YouTube consultant for "{channel_name}":\n1) Niche assessment\n2) Content strategy\n3) Growth tactics\n4) Monetization\n5) Competitive advantages\n{lang}', 2000)

def analyze_competitor(my_channel, competitor, ui_lang):
    if not my_channel or not competitor: return "⚠️ 둘 다 입력하세요!" if ui_lang == "ko" else "⚠️ Please enter both!"
    lang = "한국어로 답변해주세요." if ui_lang == "ko" else ""
    return call_llm(f'Compare "{my_channel}" vs "{competitor}":\n1) Positioning\n2) Content gap\n3) Benchmarks\n4) Advantages\n5) Action plan\n6) 5 video ideas to beat them\n{lang}', 2000)

# ============================================
# 🕐 History
# ============================================
def show_search_history(ui_lang):
    is_ko = ui_lang == "ko"
    
    conn = sqlite3.connect("youtube_data.db")
    c = conn.cursor()
    c.execute('SELECT keyword,country,language,sort_by,results_count,searched_at FROM search_history ORDER BY searched_at DESC LIMIT 50')
    history = c.fetchall()
    conn.close()
    
    if not history:
        return f'''<div style="background:#FFF;padding:30px;border:3px solid #1F2937;border-radius:8px;text-align:center;font-family:'Comic Neue',cursive">
            <p style="color:#1F2937;font-size:16px">{"검색 기록이 없습니다." if is_ko else "No search history yet."}</p>
        </div>'''
    
    html = f'''
    <div style="background:#1F2937;color:#FACC15;padding:15px;border:3px solid #1F2937;border-radius:8px;margin-bottom:15px">
        <h3 style="font-family:'Bangers',cursive;margin:0;font-size:1.5rem">🕐 {"검색 기록" if is_ko else "SEARCH HISTORY"}</h3>
    </div>
    <div style="overflow-x:auto">
    <table style="width:100%;border-collapse:collapse;font-family:'Comic Neue',cursive">
        <thead><tr style="background:#FACC15;color:#1F2937">
            <th style="padding:12px;border:2px solid #1F2937">{"검색어" if is_ko else "Keyword"}</th>
            <th style="padding:12px;border:2px solid #1F2937">{"국가" if is_ko else "Country"}</th>
            <th style="padding:12px;border:2px solid #1F2937">{"언어" if is_ko else "Lang"}</th>
            <th style="padding:12px;border:2px solid #1F2937">{"정렬" if is_ko else "Sort"}</th>
            <th style="padding:12px;border:2px solid #1F2937">{"결과" if is_ko else "Results"}</th>
            <th style="padding:12px;border:2px solid #1F2937">{"시간" if is_ko else "Time"}</th>
        </tr></thead><tbody>'''
    
    for kw, country, lang, sort_by, cnt, searched in history:
        html += f'''<tr style="background:#FFF;border-bottom:2px solid #1F2937">
            <td style="padding:12px;font-weight:700;color:#1F2937">{kw}</td>
            <td style="padding:12px;color:#1F2937">{country}</td>
            <td style="padding:12px;color:#1F2937">{lang[:10] if lang else "-"}</td>
            <td style="padding:12px;color:#1F2937">{sort_by[:10] if sort_by else "-"}</td>
            <td style="padding:12px;color:#3B82F6;font-weight:700">{cnt}</td>
            <td style="padding:12px;font-size:12px;color:#666">{searched[:16].replace("T"," ")}</td>
        </tr>'''
    
    return html + '</tbody></table></div>'

# ============================================
# 🌐 Language Switch
# ============================================
def on_lang_change(lang_choice):
    return "ko" if lang_choice == "한국어" else "en"

def switch_ui_language(ui_lang):
    L = UI_LANG.get(ui_lang, UI_LANG["en"])
    sort_opts = list(L["sort_options"].keys())
    date_opts = list(L["date_options"].keys())
    return (
        gr.update(label=L["search_keyword"], placeholder=L["enter_keyword"]),
        gr.update(value=L["search"]),
        gr.update(value=L["refresh"]),
        gr.update(label=L["country"]),
        gr.update(label=L["language"]),
        gr.update(choices=sort_opts, value=sort_opts[0], label=L["sort_by"]),
        gr.update(choices=date_opts, value=date_opts[0], label=L["period"]),
        gr.update(label=L["max_results"]),
        gr.update(label=L["click_autofill"]),
    )

# Initial trending
initial_trending = get_real_trending_keywords("US", "en")

# ============================================
# 🎨 Gradio UI (Gradio 6.0 Compatible)
# ============================================
with gr.Blocks(title="YouTube Trend Analyzer") as demo:
    ui_lang_state = gr.State("en")
    
    gr.HTML('''<div style="text-align:center;margin:20px 0">
        <a href="https://www.humangen.ai" target="_blank">
            <img src="https://img.shields.io/static/v1?label=🏠 HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge">
        </a></div>''')
    
    gr.Markdown("# 🎬 YOUTUBE TREND ANALYZER 📊", elem_classes=["header-text"])
    
    with gr.Row():
        with gr.Column(scale=4):
            db_stats = gr.Markdown("📊 Loading...")
        with gr.Column(scale=1):
            ui_lang_dropdown = gr.Dropdown(choices=["English", "한국어"], value="English", label="🌐 UI Language", interactive=True)
    
    with gr.Tabs():
        with gr.Tab("🔍 Search"):
            gr.Markdown("### 🔥 Trending Keywords")
            trending = gr.Radio(choices=initial_trending, label="Click to auto-fill", interactive=True)
            with gr.Row():
                keyword = gr.Textbox(label="Search Keyword", placeholder="Enter keyword...", scale=3)
                btn = gr.Button("🔍 SEARCH", variant="primary", scale=1)
                refresh_btn = gr.Button("🔄 Refresh", variant="secondary", scale=1)
            with gr.Row():
                country = gr.Dropdown(list(COUNTRIES.keys()), value="United States", label="Country")
                language = gr.Dropdown(list(LANGUAGES.keys()), value="Auto (by Country)", label="Language")
                sort_by = gr.Dropdown(list(UI_LANG["en"]["sort_options"].keys()), value="Most Viewed", label="Sort By")
                date_filter = gr.Dropdown(list(UI_LANG["en"]["date_options"].keys()), value="All Time", label="Period")
                max_results = gr.Slider(10, 1000, value=100, step=10, label="Max Results")
            output = gr.HTML()
        
        with gr.Tab("⭐ AI Pick"):
            gr.Markdown("### 🤖 AI-Curated Top Recommendations (⭐⭐ and above)")
            pick_btn = gr.Button("🔄 Refresh AI Picks", variant="primary")
            pick_out = gr.HTML()
        
        with gr.Tab("🔥 Trending"):
            gr.Markdown("### 🔥 Sudden Surge Detection (20%+ view increase)")
            alerts_btn = gr.Button("🔄 Refresh", variant="primary")
            alerts_out = gr.HTML()
        
        with gr.Tab("📈 Top Growing"):
            gr.Markdown("### 📈 48-Hour Growth Champions")
            growing_btn = gr.Button("🔄 Refresh", variant="primary")
            growing_out = gr.HTML()
        
        with gr.Tab("🤖 AI Tools"):
            gr.Markdown("### 🧠 LLM-Powered Analysis (GPT-OSS-120B)")
            with gr.Tabs():
                with gr.Tab("🏷️ Keyword Suggest"):
                    kw_input = gr.Textbox(label="Enter base keyword", placeholder="e.g., Python tutorial")
                    kw_btn = gr.Button("🔍 Generate Keywords", variant="primary")
                    kw_output = gr.Textbox(label="Suggested Keywords", lines=20, elem_classes=["llm-result"])
                
                with gr.Tab("🔮 Trend Prediction"):
                    tp_input = gr.Textbox(label="Enter topic", placeholder="e.g., AI tools")
                    tp_btn = gr.Button("🔮 Predict Trend", variant="primary")
                    tp_output = gr.Textbox(label="Trend Analysis", lines=20, elem_classes=["llm-result"])
                
                with gr.Tab("💡 Content Ideas"):
                    ci_input = gr.Textbox(label="Enter topic", placeholder="e.g., Home workout")
                    ci_btn = gr.Button("💡 Generate Ideas", variant="primary")
                    ci_output = gr.Textbox(label="Content Ideas", lines=25, elem_classes=["llm-result"])
                
                with gr.Tab("📊 Channel Analysis"):
                    ca_input = gr.Textbox(label="Enter channel/niche", placeholder="e.g., Tech reviews")
                    ca_btn = gr.Button("📊 Analyze", variant="primary")
                    ca_output = gr.Textbox(label="Analysis", lines=25, elem_classes=["llm-result"])
                
                with gr.Tab("⚔️ Competitor"):
                    with gr.Row():
                        comp_my = gr.Textbox(label="Your Channel", placeholder="My channel")
                        comp_rival = gr.Textbox(label="Competitor", placeholder="Competitor")
                    comp_btn = gr.Button("⚔️ Compare", variant="primary")
                    comp_output = gr.Textbox(label="Analysis", lines=25, elem_classes=["llm-result"])
        
        with gr.Tab("🕐 History"):
            history_btn = gr.Button("🔄 Refresh", variant="primary")
            history_out = gr.HTML()
    
    # Events
    ui_lang_dropdown.change(on_lang_change, ui_lang_dropdown, ui_lang_state)
    ui_lang_dropdown.change(lambda x: switch_ui_language("ko" if x == "한국어" else "en"), ui_lang_dropdown, 
                           [keyword, btn, refresh_btn, country, language, sort_by, date_filter, max_results, trending])
    
    trending.change(use_trending_keyword, trending, keyword)
    country.change(update_trending, country, trending)
    
    btn.click(search_videos, [keyword, country, language, sort_by, date_filter, max_results, ui_lang_state], [output, db_stats])
    keyword.submit(search_videos, [keyword, country, language, sort_by, date_filter, max_results, ui_lang_state], [output, db_stats])
    refresh_btn.click(search_videos, [keyword, country, language, sort_by, date_filter, max_results, ui_lang_state], [output, db_stats])
    
    pick_btn.click(show_ai_picks, ui_lang_state, pick_out)
    alerts_btn.click(show_trending_alerts, ui_lang_state, alerts_out)
    growing_btn.click(show_top_growing, ui_lang_state, growing_out)
    history_btn.click(show_search_history, ui_lang_state, history_out)
    
    kw_btn.click(analyze_keyword_suggest, [kw_input, ui_lang_state], kw_output)
    tp_btn.click(analyze_trend_prediction, [tp_input, ui_lang_state], tp_output)
    ci_btn.click(analyze_content_ideas, [ci_input, ui_lang_state], ci_output)
    ca_btn.click(analyze_channel, [ca_input, ui_lang_state], ca_output)
    comp_btn.click(analyze_competitor, [comp_my, comp_rival, ui_lang_state], comp_output)

# Launch with CSS (Gradio 6.0 style)
demo.launch(css=css)