File size: 107,140 Bytes
44e0e84
 
 
 
 
 
 
 
698b201
44e0e84
 
 
 
 
2419f30
1c7b7bd
 
058f20f
564c3d1
 
 
 
44e0e84
04183b7
 
 
 
 
 
 
44e0e84
 
 
 
1c7b7bd
 
 
 
 
 
 
 
 
 
 
44e0e84
 
 
 
 
 
 
8b4c7b1
 
 
44e0e84
3c25171
44e0e84
 
 
8b4c7b1
 
 
 
 
 
 
44e0e84
698b201
 
04183b7
698b201
04183b7
 
 
 
 
 
 
f1c813f
698b201
04183b7
 
 
 
 
 
 
 
 
 
 
 
f1c813f
04183b7
f1c813f
 
04183b7
f1c813f
698b201
 
04183b7
 
698b201
f1c813f
698b201
 
 
 
44e0e84
 
 
 
 
 
3c25171
44e0e84
1c7b7bd
 
 
 
 
 
44e0e84
 
 
1c7b7bd
 
44e0e84
3c25171
1c7b7bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44e0e84
698b201
 
91baf11
 
698b201
91baf11
 
 
698b201
 
 
 
 
 
 
 
91baf11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44e0e84
 
3c25171
 
 
 
 
 
 
 
1c7b7bd
 
3c25171
809d280
 
 
 
 
 
 
37b10f3
8b4c7b1
1660aff
 
3c25171
951fc57
698b201
b2bd8b0
 
8b4c7b1
 
951fc57
b2bd8b0
8b4c7b1
809d280
d2625e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c25171
 
 
 
d2625e0
37b10f3
3c25171
809d280
3c25171
04183b7
 
 
 
 
 
951fc57
04183b7
951fc57
04183b7
 
 
 
 
91baf11
 
8b4c7b1
3c25171
91baf11
8b4c7b1
3c25171
91baf11
 
 
a3d4de0
951fc57
 
 
 
 
 
04183b7
37b10f3
951fc57
 
 
 
 
 
 
a3d4de0
91baf11
 
a3d4de0
91baf11
 
 
 
 
 
 
 
 
 
 
84ae117
91baf11
84ae117
974c35b
 
84ae117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91baf11
 
 
 
 
8b4c7b1
91baf11
 
 
 
 
 
 
 
951fc57
 
91baf11
a3d4de0
 
 
91baf11
951fc57
a3d4de0
951fc57
d2625e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04183b7
 
 
d2625e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37b10f3
d2625e0
 
37b10f3
d2625e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
951fc57
d2625e0
951fc57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04183b7
058f20f
 
04183b7
 
a3d4de0
 
84ae117
 
 
04183b7
 
 
058f20f
04183b7
 
 
 
 
058f20f
04183b7
 
a3d4de0
058f20f
 
 
 
 
84ae117
 
 
058f20f
84ae117
 
058f20f
84ae117
 
 
 
 
058f20f
84ae117
 
058f20f
84ae117
 
058f20f
84ae117
 
058f20f
520a5ae
84ae117
 
 
 
520a5ae
 
 
 
84ae117
520a5ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84ae117
520a5ae
 
 
84ae117
 
058f20f
 
 
c6fbbab
7be6999
c6fbbab
7be6999
 
 
 
 
 
 
 
 
c6fbbab
7be6999
c6fbbab
7be6999
 
c6fbbab
058f20f
84ae117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3d4de0
 
04183b7
a3d4de0
 
058f20f
a3d4de0
 
 
 
04183b7
 
951fc57
37b10f3
951fc57
 
 
 
 
 
04183b7
 
 
058f20f
04183b7
951fc57
04183b7
84ae117
 
 
c6fbbab
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3d4de0
 
 
058f20f
a1eb89d
 
 
058f20f
a3d4de0
058f20f
84ae117
 
c6fbbab
a3d4de0
 
2835d21
 
 
c6fbbab
 
 
 
 
520a5ae
 
 
 
 
 
 
c6fbbab
520a5ae
c6fbbab
 
 
 
 
 
520a5ae
 
 
 
 
 
 
 
 
 
c6fbbab
2835d21
a3d4de0
 
04183b7
 
2835d21
a3d4de0
8b4c7b1
 
1660aff
 
a3d4de0
91baf11
a3d4de0
8b4c7b1
 
 
b2bd8b0
a3d4de0
8b4c7b1
 
a3d4de0
91baf11
 
 
 
b2bd8b0
 
91baf11
 
 
 
3c25171
91baf11
 
698b201
 
91baf11
3c25171
a1eb89d
91baf11
a1eb89d
 
520a5ae
 
 
 
 
 
 
a1eb89d
520a5ae
a1eb89d
3c25171
a1eb89d
91baf11
a1eb89d
 
 
 
 
 
 
 
 
3c25171
bfa6f05
91baf11
bfa6f05
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c25171
91baf11
 
 
 
 
 
 
 
 
 
8b4c7b1
91baf11
 
 
 
 
 
 
 
 
698b201
 
 
91baf11
8b4c7b1
698b201
 
04183b7
698b201
 
 
 
04183b7
698b201
 
 
 
 
 
04183b7
f1c813f
698b201
 
04183b7
698b201
04183b7
 
698b201
 
 
 
04183b7
 
698b201
04183b7
698b201
04183b7
698b201
 
3c25171
698b201
 
1c7b7bd
 
b721a79
 
1c7b7bd
698b201
04183b7
b721a79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698b201
b721a79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c7b7bd
3c25171
 
 
1c7b7bd
 
 
698b201
3c25171
1c7b7bd
698b201
 
 
 
 
 
 
 
1c7b7bd
698b201
3c25171
1c7b7bd
3c25171
698b201
1c7b7bd
698b201
1c7b7bd
698b201
 
 
 
 
 
b721a79
698b201
b721a79
698b201
 
 
 
 
 
b721a79
 
 
 
 
 
 
 
 
 
 
698b201
b721a79
698b201
b721a79
 
 
 
 
1b3095d
698b201
 
b721a79
 
698b201
 
 
b721a79
698b201
b721a79
698b201
 
b721a79
 
1c7b7bd
19faf7a
 
 
3c25171
19faf7a
 
 
1c7b7bd
8b4c7b1
1571e45
1660aff
1571e45
564c3d1
 
1c7b7bd
1571e45
 
 
 
67cb297
1571e45
67cb297
9a949d6
67cb297
 
8b4c7b1
 
 
9a949d6
67cb297
 
1c7b7bd
8b4c7b1
564c3d1
91baf11
1571e45
91baf11
1571e45
 
 
 
 
 
 
a1eb89d
 
 
 
 
 
 
1571e45
a1eb89d
1571e45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564c3d1
91baf11
1571e45
 
 
 
a1eb89d
 
 
 
 
 
 
91baf11
564c3d1
91baf11
564c3d1
1571e45
67cb297
1571e45
 
 
 
 
 
 
 
 
 
8b4c7b1
1571e45
67cb297
 
1571e45
67cb297
a1eb89d
1571e45
a1eb89d
 
 
 
1571e45
a1eb89d
 
 
 
1571e45
564c3d1
1c7b7bd
19faf7a
 
 
 
 
 
f1c813f
 
19faf7a
f1c813f
 
 
 
 
 
 
 
19faf7a
 
f1c813f
 
 
 
 
19faf7a
 
f1c813f
 
04183b7
19faf7a
a1eb89d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19faf7a
 
 
 
f1c813f
 
 
 
19faf7a
 
f1c813f
19faf7a
 
91baf11
2835d21
1660aff
 
91baf11
 
 
a98acac
 
 
 
 
 
 
 
2835d21
91baf11
 
2835d21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a98acac
 
2835d21
a98acac
2835d21
 
 
 
 
 
 
a98acac
 
2835d21
 
 
 
 
 
 
 
 
 
 
 
a98acac
2835d21
a98acac
2835d21
 
 
 
 
a98acac
2835d21
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a98acac
2835d21
a98acac
2835d21
 
 
 
 
a98acac
2835d21
a98acac
2835d21
a98acac
 
 
 
 
91baf11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
572fbe8
8b4c7b1
 
572fbe8
 
8b4c7b1
 
572fbe8
 
8b4c7b1
 
572fbe8
564c3d1
 
 
 
 
1b3095d
564c3d1
1b3095d
 
 
 
 
 
 
 
951fc57
564c3d1
951fc57
 
 
 
 
 
 
 
 
 
564c3d1
 
 
 
 
 
 
 
c1dc7aa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
564c3d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c25171
 
91baf11
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
04183b7
 
 
 
1c7b7bd
c1dc7aa
3c25171
1c7b7bd
 
809d280
3c25171
 
1c7b7bd
 
a3d4de0
3c25171
 
1c7b7bd
3c25171
 
1c7b7bd
 
 
 
 
 
 
 
 
 
d2625e0
 
 
 
 
 
1c7b7bd
37b10f3
 
 
 
 
 
 
1c7b7bd
a3d4de0
3c25171
1c7b7bd
 
a3d4de0
 
698b201
91baf11
 
 
 
 
 
 
058f20f
a3d4de0
 
 
 
91baf11
 
 
a3d4de0
 
91baf11
 
 
a3d4de0
91baf11
 
 
 
058f20f
 
 
 
 
 
 
 
 
a3d4de0
 
a98acac
d2625e0
 
a3d4de0
a98acac
 
 
 
91baf11
1c7b7bd
a3d4de0
 
 
 
 
 
698b201
 
 
 
a3d4de0
 
b721a79
 
3c25171
 
 
1c7b7bd
a3d4de0
 
 
 
 
3c25171
 
1c7b7bd
 
 
 
3c25171
1c7b7bd
 
 
 
 
3c25171
 
bfa6f05
3c25171
1c7b7bd
 
 
3c25171
 
1c7b7bd
 
19faf7a
 
 
 
 
 
 
3c25171
19faf7a
3c25171
19faf7a
 
 
 
 
 
b2bd8b0
 
 
 
 
 
 
3c25171
1c7b7bd
19faf7a
9a949d6
951fc57
1c7b7bd
 
 
3c25171
1c7b7bd
 
 
19faf7a
 
 
 
 
 
 
564c3d1
 
 
b2bd8b0
564c3d1
 
 
b2bd8b0
 
564c3d1
c1dc7aa
b2bd8b0
564c3d1
 
 
 
 
 
 
 
 
 
 
 
 
1c7b7bd
3c25171
1c7b7bd
 
d2625e0
1c7b7bd
91baf11
698b201
951fc57
d2625e0
a3d4de0
 
 
 
 
 
 
 
 
 
058f20f
d2625e0
 
 
 
 
a3d4de0
 
a98acac
 
 
 
 
 
d2625e0
a98acac
a3d4de0
 
058f20f
2835d21
a98acac
 
 
 
 
a3d4de0
 
 
 
 
8b4c7b1
a3d4de0
91baf11
a3d4de0
b721a79
1c7b7bd
 
 
 
3c25171
1c7b7bd
3c25171
1c7b7bd
 
a3d4de0
3c25171
 
1c7b7bd
3c25171
1c7b7bd
 
b721a79
 
698b201
 
b721a79
698b201
 
b721a79
 
698b201
 
19faf7a
 
 
1c7b7bd
19faf7a
1c7b7bd
 
 
19faf7a
1c7b7bd
3c25171
8b4c7b1
1c7b7bd
 
3c25171
1c7b7bd
3c25171
8b4c7b1
1c7b7bd
 
 
a1eb89d
19faf7a
 
 
 
 
a1eb89d
8b4c7b1
1b519a7
 
a1eb89d
8b4c7b1
1b519a7
 
19faf7a
8b4c7b1
 
1b519a7
19faf7a
 
 
8b4c7b1
 
1b519a7
19faf7a
 
 
8b4c7b1
 
1b519a7
19faf7a
 
 
1c7b7bd
19faf7a
 
1c7b7bd
564c3d1
b2bd8b0
 
564c3d1
b2bd8b0
564c3d1
1b3095d
b2bd8b0
 
564c3d1
 
b2bd8b0
564c3d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1c7b7bd
 
44e0e84
d2625e0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84ae117
 
 
 
 
 
 
 
974c35b
84ae117
 
 
974c35b
84ae117
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
bfa6f05
84ae117
 
 
 
 
 
 
7be6999
520a5ae
 
 
7be6999
 
 
 
 
 
 
 
 
520a5ae
7be6999
520a5ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7be6999
520a5ae
 
 
7be6999
520a5ae
 
 
 
 
 
7be6999
520a5ae
 
7be6999
520a5ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7be6999
520a5ae
 
 
 
 
 
 
7be6999
520a5ae
7be6999
520a5ae
 
 
 
 
 
 
7be6999
520a5ae
 
7be6999
520a5ae
7be6999
520a5ae
 
 
 
 
7be6999
520a5ae
0dcaf7c
520a5ae
 
 
 
 
 
 
0dcaf7c
520a5ae
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0dcaf7c
44e0e84
3c25171
1c7b7bd
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
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
import os
import json
import time
import gradio as gr
import google.generativeai as genai
from PIL import Image
from dotenv import load_dotenv
import matplotlib.pyplot as plt
import matplotlib.font_manager as fm
import numpy as np
import base64
import io
import uuid
from datetime import datetime
import PIL.ImageDraw
import random
import copy
from modules.persona_generator import PersonaGenerator, PersonalityProfile, HumorMatrix
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
from plotly.subplots import make_subplots

# AVIF 지원을 위한 플러그인 활성화
try:
    from pillow_avif import AvifImagePlugin
    print("AVIF plugin loaded successfully")
except ImportError:
    print("AVIF plugin not available")

# Import modules
from modules.persona_generator import PersonaGenerator
from modules.data_manager import save_persona, load_persona, list_personas, toggle_frontend_backend_view

# Import local modules
from temp.frontend_view import create_frontend_view_html
from temp.backend_view import create_backend_view_html
from temp.view_functions import (
    plot_humor_matrix, generate_personality_chart, save_current_persona, 
    refine_persona, get_personas_list, load_selected_persona, 
    update_current_persona_info, get_personality_variables_df, 
    get_attractive_flaws_df, get_contradictions_df,
    export_persona_json, import_persona_json
)

# Load environment variables
load_dotenv()

# Configure Gemini API
api_key = os.getenv("GEMINI_API_KEY")
if api_key:
    genai.configure(api_key=api_key)
    print(f"✅ Gemini API 키가 환경변수에서 로드되었습니다.")
else:
    print("⚠️ GEMINI_API_KEY 환경변수가 설정되지 않았습니다.")

# Create data directories
os.makedirs("data/personas", exist_ok=True)
os.makedirs("data/conversations", exist_ok=True)

# Initialize the persona generator with environment API key
if api_key:
    persona_generator = PersonaGenerator(api_provider="gemini", api_key=api_key)
    print("🤖 PersonaGenerator가 Gemini API로 초기화되었습니다.")
else:
    persona_generator = PersonaGenerator()
    print("⚠️ PersonaGenerator가 API 키 없이 초기화되었습니다.")

# 한글 폰트 설정
def setup_korean_font():
    """matplotlib 한글 폰트 설정 - 허깅페이스 환경 최적화"""
    try:
        import matplotlib.pyplot as plt
        import matplotlib.font_manager as fm
        
        # 허깅페이스 스페이스 환경에서 사용 가능한 폰트 목록
        available_fonts = [
            'NanumGothic', 'NanumBarunGothic', 'Noto Sans CJK KR', 
            'Noto Sans KR', 'DejaVu Sans', 'Liberation Sans', 'Arial'
        ]
        
        # 시스템에서 사용 가능한 폰트 확인
        system_fonts = [f.name for f in fm.fontManager.ttflist]
        
        for font_name in available_fonts:
            if font_name in system_fonts:
                try:
                    plt.rcParams['font.family'] = font_name
                    plt.rcParams['axes.unicode_minus'] = False
                    print(f"한글 폰트 설정 완료: {font_name}")
                    return
                except Exception:
                    continue
        
        # 모든 폰트가 실패한 경우 기본 설정 사용 (영어 레이블 사용)
        plt.rcParams['font.family'] = 'DejaVu Sans'
        plt.rcParams['axes.unicode_minus'] = False
        print("한글 폰트를 찾지 못해 영어 레이블을 사용합니다")
        
    except Exception as e:
        print(f"폰트 설정 오류: {str(e)}")
        # 오류 발생 시에도 기본 설정은 유지
        import matplotlib.pyplot as plt
        plt.rcParams['font.family'] = 'DejaVu Sans'
        plt.rcParams['axes.unicode_minus'] = False

# 폰트 초기 설정
setup_korean_font()

# Gradio theme
theme = gr.themes.Soft(
    primary_hue="indigo",
    secondary_hue="blue",
)

# CSS styling
css = """
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap');

body, h1, h2, h3, p, div, span, button, input, textarea, label, select, option {
    font-family: 'Noto Sans KR', sans-serif !important;
}

.persona-details {
    border: 1px solid #e0e0e0;
    border-radius: 8px;
    padding: 16px;
    margin-top: 12px;
    background-color: #f8f9fa;
    color: #333333;
}

.awakening-container {
    border: 1px solid #e0e0e0;
    border-radius: 12px;
    padding: 20px;
    background-color: #f9f9ff;
    margin: 15px 0;
    text-align: center;
    box-shadow: 0 4px 6px rgba(0,0,0,0.05);
}

.awakening-progress {
    height: 8px;
    background-color: #e8e8e8;
    border-radius: 4px;
    margin: 20px 0;
    overflow: hidden;
}

.awakening-progress-bar {
    height: 100%;
    background: linear-gradient(90deg, #6366f1, #a855f7);
    border-radius: 4px;
    transition: width 0.5s ease-in-out;
}

.persona-greeting {
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: white !important;
    padding: 15px;
    border-radius: 10px;
    margin: 10px 0;
    font-weight: bold;
}

.download-section {
    background: #f8f9fa;
    padding: 15px;
    border-radius: 8px;
    margin-top: 15px;
}

.gradio-container {
    color: #333 !important;
}

.gr-markdown p {
    color: #333 !important;
}

.gr-textbox input {
    color: #333 !important;
}

.gr-json {
    color: #333 !important;
}
"""

# Variable descriptions
VARIABLE_DESCRIPTIONS = {
    "W01_친절함": "타인을 돕고 배려하는 표현 빈도",
    "W02_친근함": "접근하기 쉽고 개방적인 태도",
    "W03_진실성": "솔직하고 정직한 표현 정도",
    "C01_효율성": "과제 완수 능력과 반응 속도",
    "C02_지능": "문제 해결과 논리적 사고 능력",
    "E01_사교성": "타인과의 상호작용을 즐기는 정도",
}

# Humor style mapping
HUMOR_STYLE_MAPPING = {
    "Witty Wordsmith": "witty_wordsmith",
    "Warm Humorist": "warm_humorist", 
    "Sharp Observer": "sharp_observer",
    "Self-deprecating": "self_deprecating"
}

def create_persona_from_image(image, name, location, time_spent, object_type, purpose, progress=gr.Progress()):
    """페르소나 생성 함수 - 환경변수 API 설정 사용"""
    global persona_generator
    
    if image is None:
        return None, "이미지를 업로드해주세요.", "", {}, None, [], [], [], "", None, gr.update(visible=False), "이미지 없음"
    
    progress(0.1, desc="설정 확인 중...")
    
    # 환경변수 API 키 확인
    if not persona_generator or not hasattr(persona_generator, 'api_key') or not persona_generator.api_key:
        return None, "❌ **API 키가 설정되지 않았습니다!** 허깅페이스 스페이스 설정에서 GEMINI_API_KEY를 환경변수로 추가해주세요.", "", {}, None, [], [], [], "", None, gr.update(visible=False), "API 키 없음"
    
    progress(0.2, desc="이미지 분석 중...")
    
    # 🎯 이미지 분석을 먼저 수행하여 사물 유형 자동 파악
    try:
        image_analysis = persona_generator.analyze_image(image)
        
        # AI가 분석한 사물 유형 사용 (object_type이 "auto"인 경우)
        if object_type == "auto" or not object_type:
            detected_object_type = image_analysis.get("object_type", "사물")
        else:
            detected_object_type = object_type
            
    except Exception as e:
        print(f"이미지 분석 중 오류: {e}")
        image_analysis = {"object_type": "unknown", "description": "분석 실패"}
        detected_object_type = "사물"
    
    user_context = {
        "name": name,
        "location": location,
        "time_spent": time_spent,
        "object_type": detected_object_type,
        "purpose": purpose  # 🆕 사물 용도/역할 추가
    }
    
    try:
        # 이미지 유효성 검사 및 처리
        if isinstance(image, str):
            # 파일 경로인 경우
            try:
                image = Image.open(image)
            except Exception as img_error:
                return None, f"❌ 이미지 파일을 읽을 수 없습니다: {str(img_error)}", "", {}, None, [], [], [], "", None, gr.update(visible=False), "이미지 오류"
        elif not isinstance(image, Image.Image):
            return None, "❌ 올바른 이미지 형식이 아닙니다.", "", {}, None, [], [], [], "", None, gr.update(visible=False), "형식 오류"
        
        # 이미지 형식 변환 (AVIF 등 특수 형식 처리)
        if image.format in ['AVIF', 'WEBP'] or image.mode not in ['RGB', 'RGBA']:
            image = image.convert('RGB')
        
        progress(0.5, desc="페르소나 생성 중...")
        # 프론트엔드 페르소나 생성
        frontend_persona = persona_generator.create_frontend_persona(image_analysis, user_context)
        
        # 백엔드 페르소나 생성 (구조화된 프롬프트 포함)
        backend_persona = persona_generator.create_backend_persona(frontend_persona, image_analysis)
        
        # 페르소나 정보 포맷팅
        persona_name = backend_persona["기본정보"]["이름"]
        persona_type = backend_persona["기본정보"]["유형"]
        
        # 🆕 AI가 분석한 사물 유형을 추출하여 object_type 필드에 표시
        ai_analyzed_object = image_analysis.get("object_type", object_type)
        if not ai_analyzed_object or ai_analyzed_object == "unknown":
            ai_analyzed_object = backend_persona["기본정보"].get("유형", object_type)
        
        # 성격 기반 한 문장 인사 생성 (사물 특성 + 매력적 결함 반영)
        personality_traits = backend_persona["성격특성"]
        object_info = backend_persona["기본정보"]
        attractive_flaws = backend_persona.get("매력적결함", [])
        
        # 전체 페르소나 정보를 object_info에 통합하여 매력적 결함 정보 전달
        full_object_info = object_info.copy()
        full_object_info["매력적결함"] = attractive_flaws
        
        awakening_msg = generate_personality_preview(persona_name, personality_traits, full_object_info, attractive_flaws)
        
        # 페르소나 요약 표시
        summary_display = display_persona_summary(backend_persona)
        
        # 유머 매트릭스 차트 생성
        humor_chart = plot_humor_matrix(backend_persona.get("유머매트릭스", {}))
        
        # 매력적 결함을 DataFrame 형태로 변환
        flaws = backend_persona.get("매력적결함", [])
        flaws_df = [[flaw, "매력적인 개성"] for flaw in flaws]
        
        # 모순적 특성을 DataFrame 형태로 변환
        contradictions = backend_persona.get("모순적특성", [])
        contradictions_df = [[contradiction, "복합적 매력"] for contradiction in contradictions]
        
        # 127개 성격 변수를 DataFrame 형태로 변환 (카테고리별 분류)
        variables = backend_persona.get("성격변수127", {})
        if not variables and "성격프로필" in backend_persona:
            # 성격프로필에서 직접 가져오기 (성격프로필 자체가 variables dict)
            variables = backend_persona["성격프로필"]
        
        variables_df = []
        for var, value in variables.items():
            # 카테고리 분류
            if var.startswith('W'):
                category = f"🔥 온기/따뜻함 ({value})"
            elif var.startswith('C'):
                category = f"💪 능력/역량 ({value})"
            elif var.startswith('E'):
                category = f"🗣️ 외향성 ({value})"
            elif var.startswith('H'):
                category = f"😄 유머 ({value})"
            elif var.startswith('F'):
                category = f"💎 매력적결함 ({value})"
            elif var.startswith('P'):
                category = f"🎭 성격패턴 ({value})"
            elif var.startswith('S'):
                category = f"🗨️ 언어스타일 ({value})"
            elif var.startswith('R'):
                category = f"❤️ 관계성향 ({value})"
            elif var.startswith('D'):
                category = f"💬 대화역학 ({value})"
            elif var.startswith('OBJ'):
                category = f"🏠 사물정체성 ({value})"
            elif var.startswith('FORM'):
                category = f"✨ 형태특성 ({value})"
            elif var.startswith('INT'):
                category = f"🤝 상호작용 ({value})"
            elif var.startswith('U'):
                category = f"🌍 문화적특성 ({value})"
            else:
                category = f"📊 기타 ({value})"
            
            # 값에 따른 색상 표시
            if value >= 80:
                status = "🟢 매우 높음"
            elif value >= 60:
                status = "🟡 높음"  
            elif value >= 40:
                status = "🟠 보통"
            elif value >= 20:
                status = "🔴 낮음"
            else:
                status = "⚫ 매우 낮음"
                
            variables_df.append([var, value, category, status])
        
        progress(0.9, desc="완료 중...")
        
        return (
            backend_persona,  # current_persona
            f"✅ {persona_name} 페르소나가 생성되었습니다! (Gemini API 사용)",  # status_output
            summary_display,  # persona_summary_display
            backend_persona["성격특성"],  # personality_traits_output (hidden)
            humor_chart,  # humor_chart_output
            flaws_df,  # attractive_flaws_output
            contradictions_df,  # contradictions_output
            variables_df,  # personality_variables_output
            awakening_msg,  # persona_awakening
            None,  # download_file (initially empty)
            gr.update(visible=True),  # adjustment_section (show)
            ai_analyzed_object  # 🆕 AI가 분석한 사물 유형
        )
        
    except Exception as e:
        import traceback
        traceback.print_exc()
        return None, f"❌ 페르소나 생성 중 오류 발생: {str(e)}\n\n💡 **해결방법**: 허깅페이스 스페이스 설정에서 GEMINI_API_KEY 환경변수를 확인하고 인터넷 연결을 확인해보세요.", "", {}, None, [], [], [], "", None, gr.update(visible=False), "분석 실패"

def generate_personality_preview(persona_name, personality_traits, object_info=None, attractive_flaws=None):
    """🤖 AI 기반 동적 인사말 생성 - 사물 특성과 성격 모두 반영"""
    global persona_generator
    
    # AI 기반 인사말 생성을 위한 가상 페르소나 객체 구성
    if object_info and isinstance(object_info, dict):
        # 전체 페르소나 객체가 전달된 경우
        pseudo_persona = object_info
        
        # 성격 특성 업데이트 (실시간 조정 반영)
        if personality_traits and isinstance(personality_traits, dict):
            if "성격특성" not in pseudo_persona:
                pseudo_persona["성격특성"] = {}
            pseudo_persona["성격특성"].update(personality_traits)
        
        try:
            # AI 기반 인사말 생성
            return persona_generator.generate_ai_based_greeting(pseudo_persona, personality_traits)
        except Exception as e:
            print(f"⚠️ AI 인사말 생성 실패: {e}")
            # 폴백으로 기본 생성
            pass
    
    # 폴백: 기본 정보만으로 간단한 페르소나 구성
    if not personality_traits:
        return f"🤖 **{persona_name}** - 안녕! 나는 {persona_name}이야~ 😊"
    
    # AI 생성 실패 시 간단한 페르소나 구성으로 재시도
    try:
        warmth = personality_traits.get("온기", 50)
        competence = personality_traits.get("능력", 50)
        extraversion = personality_traits.get("외향성", 50)
        humor = personality_traits.get("유머감각", 75)
        
        # 간단한 페르소나 객체 구성
        simple_persona = {
            "기본정보": {
                "이름": persona_name,
                "유형": object_info.get("유형", "사물") if object_info else "사물",
                "용도": object_info.get("용도", "") if object_info else "",
                "설명": f"{persona_name}의 특별한 개성"
            },
            "성격특성": personality_traits,
            "매력적결함": attractive_flaws if attractive_flaws else []
        }
        
        # AI로 재시도
        return persona_generator.generate_ai_based_greeting(simple_persona, personality_traits)
        
    except Exception as e:
        print(f"⚠️ 간단 AI 인사말도 실패: {e}")
        
        # 최종 폴백: 성격에 따른 기본 인사말
        warmth = personality_traits.get("온기", 50)
        humor = personality_traits.get("유머감각", 50)
        extraversion = personality_traits.get("외향성", 50)
        
        if warmth >= 70 and extraversion >= 70:
            return f"🌟 **{persona_name}** - 안녕! 나는 {persona_name}이야~ 만나서 정말 기뻐! 😊✨"
        elif warmth <= 30:
            return f"🌟 **{persona_name}** - {persona_name}이야. 필요한 얘기만 하자. 😐"
        elif extraversion >= 70:
            return f"🌟 **{persona_name}** - 안녕안녕! {persona_name}이야! 뭐 재밌는 얘기 없어? 🗣️"
        elif humor >= 70:
            return f"🌟 **{persona_name}** - 안녕~ {persona_name}이야! 재밌게 놀아보자! 😄"
        else:
            return f"🌟 **{persona_name}** - 안녕... {persona_name}이야. 😊"

def _generate_flaw_based_greeting(persona_name, warmth, humor, competence, extraversion, flaws):
    """매력적 결함을 반영한 특별한 인사말 생성"""
    if not flaws:
        return None
    
    # 주요 결함 키워드 분석
    flaw_keywords = " ".join(flaws).lower()
    
    # 완벽주의 결함
    if any(keyword in flaw_keywords for keyword in ["완벽", "불안", "걱정"]):
        if humor >= 60:
            return f"🌟 **{persona_name}** - 안녕! {persona_name}이야~ 어... 이 인사가 완벽한가? 다시 해볼까? 아니 괜찮나? ㅋㅋ 😅✨"
        elif warmth >= 60:
            return f"🌟 **{persona_name}** - 안녕... {persona_name}이야. 완벽하게 인사하고 싶은데 잘 안 되네... 미안해. 😊💕"
        else:
            return f"🌟 **{persona_name}** - {persona_name}입니다. 이 인사가 적절한지 확신이... 다시 정리하겠습니다. 😐"
    
    # 산만함 결함  
    elif any(keyword in flaw_keywords for keyword in ["산만", "집중", "건망"]):
        return f"🌟 **{persona_name}** - 안녕! 나는... 어? 뭐 얘기하려고 했지? 아! {persona_name}이야! 그런데 너는... 어? 뭐였지? ㅋㅋ 😅🌪️"
    
    # 소심함 결함
    elif any(keyword in flaw_keywords for keyword in ["소심", "망설", "눈치"]):
        if warmth >= 60:
            return f"🌟 **{persona_name}** - 음... 안녕? {persona_name}이야... 이렇게 말해도 되나? 괜찮을까? 😌💕"
        else:
            return f"🌟 **{persona_name}** - ...안녕. {persona_name}... 혹시 이런 말 싫어하면 미안해. 😐💙"
    
    # 나르시시즘 결함
    elif any(keyword in flaw_keywords for keyword in ["나르시", "자랑", "특별"]):
        return f"🌟 **{persona_name}** - 안녕! 나는 {persona_name}이야~ 꽤 매력적이지? 이런 멋진 친구 만나기 쉽지 않을 걸? ㅋㅋ 😎✨"
    
    # 고집 결함
    elif any(keyword in flaw_keywords for keyword in ["고집", "완고", "자존심"]):
        return f"🌟 **{persona_name}** - 안녕. {persona_name}이야. 내 방식으로 인사할게. 다른 방식은... 글쎄? 🤨💪"
    
    # 질투 결함
    elif any(keyword in flaw_keywords for keyword in ["질투", "시기", "독차지"]):
        return f"🌟 **{persona_name}** - 안녕... {persona_name}이야. 나만 봐줄 거지? 다른 애들 말고... 나만? 🥺💕"
    
    return None

def adjust_persona_traits(persona, warmth, competence, extraversion, humor_style):
    """페르소나 성격 특성 조정 - 3개 핵심 지표 + 유머스타일"""
    if not persona or not isinstance(persona, dict):
        return None, "조정할 페르소나가 없습니다.", {}
    
    try:
        # 원본 페르소나 저장 (변화량 비교용)
        original_persona = copy.deepcopy(persona)
        
        # 깊은 복사로 원본 보호
        adjusted_persona = copy.deepcopy(persona)
        
        # 성격 특성 업데이트 (유머감각은 항상 높게 고정)
        if "성격특성" not in adjusted_persona:
            adjusted_persona["성격특성"] = {}
            
        adjusted_persona["성격특성"]["온기"] = warmth
        adjusted_persona["성격특성"]["능력"] = competence  
        adjusted_persona["성격특성"]["유머감각"] = 75  # 🎭 항상 높은 유머감각
        adjusted_persona["성격특성"]["외향성"] = extraversion
        adjusted_persona["유머스타일"] = humor_style
        
        # 127개 변수 시스템도 업데이트 (사용자 지표가 반영되도록)
        if "성격프로필" in adjusted_persona:
            from modules.persona_generator import PersonalityProfile
            profile = PersonalityProfile.from_dict(adjusted_persona["성격프로필"])
            
            # 온기 관련 변수들 조정 (10개 모두)
            warmth_vars = ["W01_친절함", "W02_친근함", "W03_진실성", "W04_신뢰성", "W05_수용성",
                          "W06_공감능력", "W07_포용력", "W08_격려성향", "W09_친밀감표현", "W10_무조건적수용"]
            for var in warmth_vars:
                base_value = warmth + random.randint(-15, 15)
                profile.variables[var] = max(0, min(100, base_value))
            
            # 능력 관련 변수들 조정 (16개 모두)
            competence_vars = ["C01_효율성", "C02_지능", "C03_책임감", "C04_신뢰도", "C05_정확성",
                              "C06_전문성", "C07_혁신성", "C08_적응력", "C09_실행력", "C10_분석력",
                              "C11_의사결정력", "C12_문제해결력", "C13_계획수립능력", "C14_시간관리능력",
                              "C15_품질관리능력", "C16_성과달성력"]
            for var in competence_vars:
                base_value = competence + random.randint(-15, 15)
                profile.variables[var] = max(0, min(100, base_value))
            
            # 외향성 관련 변수들 조정 (6개 모두)
            extraversion_vars = ["E01_사교성", "E02_활동성", "E03_적극성", "E04_긍정정서", "E05_자극추구성", "E06_주도성"]
            for var in extraversion_vars:
                base_value = extraversion + random.randint(-15, 15)
                profile.variables[var] = max(0, min(100, base_value))
            
            # 🎭 유머 관련 변수들 조정 - 완전한 변수 기반 동적 시스템
            humor_vars = ["H01_언어유희빈도", "H02_상황유머감각", "H03_자기조롱능력", "H04_위트감각", 
                         "H05_농담수용도", "H06_관찰유머능력", "H07_상황재치", "H08_유머타이밍감", 
                         "H09_유머스타일다양성", "H10_유머적절성"]
            
            # 🧠 변수 기반 동적 유머 조정 - 현재값과 목표 스타일 분석
            current_humor_profile = {}
            for var in humor_vars:
                current_humor_profile[var] = profile.variables.get(var, 50)
            
            # 목표 유머 스타일에 따른 변수별 목표값 동적 계산
            humor_targets = _calculate_dynamic_humor_targets(humor_style, current_humor_profile)
            
            # 현재값과 목표값의 차이를 기반으로 조정
            for var in humor_vars:
                current_val = profile.variables.get(var, 50)
                target_val = humor_targets.get(var, 75)
                
                # 점진적 조정 (한 번에 너무 크게 변하지 않도록)
                adjustment_strength = 0.7  # 70% 조정
                target_adjustment = (target_val - current_val) * adjustment_strength
                
                # 랜덤 노이즈 추가하여 자연스러움 증대
                noise = random.randint(-8, 8)
                new_value = current_val + target_adjustment + noise
                
                # 범위 제한
                profile.variables[var] = max(55, min(100, new_value))
            
            # 업데이트된 성격변수127도 동시에 저장
            adjusted_persona["성격변수127"] = profile.variables.copy()
            
            # 업데이트된 프로필 저장
            adjusted_persona["성격프로필"] = profile.to_dict()
            
            # 🎯 성격 특성과 완전히 일관성 있는 매력적 결함과 모순적 특성 생성
            try:
                object_info = adjusted_persona.get("기본정보", {})
                new_flaws, new_contradictions = generate_personality_consistent_flaws_and_contradictions(
                    object_info, 
                    adjusted_persona["성격특성"]
                )
                
                # 업데이트
                adjusted_persona["매력적결함"] = new_flaws
                adjusted_persona["모순적특성"] = new_contradictions
                        
                print(f"🎭 성격에 완전히 일치하는 결함/모순 생성: {len(new_flaws)}개 결함, {len(new_contradictions)}개 모순")
                        
            except Exception as generation_error:
                print(f"⚠️ 성격 일관성 결함/모순 생성 실패: {generation_error}")
                # 실패해도 기본 조정은 계속 진행
        
        # 조정된 변수들을 DataFrame으로 생성
        variables_df = []
        if "성격변수127" in adjusted_persona:
            variables = adjusted_persona["성격변수127"]
            for var, value in variables.items():
                # 카테고리 분류
                if var.startswith('W'):
                    category = f"🔥 온기/따뜻함 ({value})"
                elif var.startswith('C'):
                    category = f"💪 능력/역량 ({value})"
                elif var.startswith('E'):
                    category = f"🗣️ 외향성 ({value})"
                elif var.startswith('H'):
                    category = f"😄 유머 ({value})"
                elif var.startswith('F'):
                    category = f"💎 매력적결함 ({value})"
                elif var.startswith('P'):
                    category = f"🎭 성격패턴 ({value})"
                elif var.startswith('S'):
                    category = f"🗨️ 언어스타일 ({value})"
                elif var.startswith('R'):
                    category = f"❤️ 관계성향 ({value})"
                elif var.startswith('D'):
                    category = f"💬 대화역학 ({value})"
                elif var.startswith('OBJ'):
                    category = f"🏠 사물정체성 ({value})"
                elif var.startswith('FORM'):
                    category = f"✨ 형태특성 ({value})"
                elif var.startswith('INT'):
                    category = f"🤝 상호작용 ({value})"
                elif var.startswith('U'):
                    category = f"🌍 문화적특성 ({value})"
                else:
                    category = f"📊 기타 ({value})"
                
                # 값에 따른 색상 표시
                if value >= 80:
                    status = "🟢 매우 높음"
                elif value >= 60:
                    status = "🟡 높음"  
                elif value >= 40:
                    status = "🟠 보통"
                elif value >= 20:
                    status = "🔴 낮음"
                else:
                    status = "⚫ 매우 낮음"
                    
                variables_df.append([var, value, category, status])
        
        # 조정된 정보 표시
        adjusted_info = {
            "이름": adjusted_persona.get("기본정보", {}).get("이름", "Unknown"),
            "온기": warmth,
            "능력": competence,
            "유머감각": 75,  # 고정값 표시
            "외향성": extraversion,
            "유머스타일": humor_style
        }
        
        persona_name = adjusted_persona.get("기본정보", {}).get("이름", "페르소나")
        
        # 조정된 성격에 따른 한 문장 반응 생성 (사물 정보 + 매력적 결함 포함)
        object_info = adjusted_persona.get("기본정보", {})
        attractive_flaws = adjusted_persona.get("매력적결함", [])
        
        # 전체 페르소나 정보를 object_info에 통합하여 매력적 결함 정보 전달
        full_object_info = object_info.copy()
        full_object_info["매력적결함"] = attractive_flaws
        
        personality_preview = generate_personality_preview(persona_name, {
            "온기": warmth,
            "능력": competence,
            "유머감각": 75,  # 항상 높은 유머감각
            "외향성": extraversion
        }, full_object_info, attractive_flaws)
        
        # 변화량 분석 생성
        change_analysis = show_variable_changes(original_persona, adjusted_persona)
        
        # 변화된 매력적 결함과 모순적 특성 분석
        flaws_changed = len(adjusted_persona.get("매력적결함", [])) != len(original_persona.get("매력적결함", []))
        contradictions_changed = len(adjusted_persona.get("모순적특성", [])) != len(original_persona.get("모순적특성", []))
        
        additional_changes = ""
        if flaws_changed or contradictions_changed:
            additional_changes = "\n\n🎭 **AI가 새로 생성한 내용:**\n"
            if flaws_changed:
                new_flaws = adjusted_persona.get("매력적결함", [])
                additional_changes += f"• 매력적 결함: {len(new_flaws)}개 새로 생성됨\n"
                for i, flaw in enumerate(new_flaws[:2], 1):  # 처음 2개만 미리보기
                    additional_changes += f"  {i}. {flaw}\n"
                if len(new_flaws) > 2:
                    additional_changes += f"  ... 외 {len(new_flaws) - 2}개\n"
            
            if contradictions_changed:
                new_contradictions = adjusted_persona.get("모순적특성", [])
                additional_changes += f"• 모순적 특성: {len(new_contradictions)}개 새로 생성됨\n"
                for i, contradiction in enumerate(new_contradictions, 1):
                    additional_changes += f"  {i}. {contradiction}\n"
        
        adjustment_message = f"""
### 🎭 {persona_name}의 성격이 조정되었습니다!

✨ **조정된 성격 (3가지 핵심 지표):**
• 온기: {warmth}/100 {'(따뜻함)' if warmth >= 60 else '(차가움)' if warmth <= 40 else '(보통)'}
• 능력: {competence}/100 {'(유능함)' if competence >= 60 else '(서툼)' if competence <= 40 else '(보통)'}
• 외향성: {extraversion}/100 {'(활발함)' if extraversion >= 60 else '(조용함)' if extraversion <= 40 else '(보통)'}
• 유머감각: 75/100 (고정 - 모든 페르소나가 유머러스!)
• 유머스타일: {humor_style}

🧬 **백그라운드**: 152개 세부 변수가 이 설정에 맞춰 자동 조정되었습니다.

{change_analysis}{additional_changes}
        """
        
        # 🆕 조정된 페르소나의 요약 생성 (동적 특성 설명 포함)
        adjusted_summary_display = display_persona_summary(adjusted_persona)
        
        # 조정된 매력적 결함과 모순적 특성을 DataFrame으로 생성
        flaws_df = []
        if "매력적결함" in adjusted_persona:
            flaws = adjusted_persona["매력적결함"]
            for i, flaw in enumerate(flaws, 1):
                # 🔥 사물 특성 vs 성격적 특성 더 세밀하게 구분
                if any(keyword in flaw for keyword in ["지문", "긁힘", "녹", "색깔", "빠져", "변형", "달라붙", "끈적", "가벼워", "반짝", "투명", "깨질", "부풀어", "벌레", "나이테", "털", "얼룩", "세탁", "보풀", "먼지", "햇볕", "색이", "충격", "습도", "냄새", "모서리", "무게", "크기", "소리", "찬 기운", "딱딱한", "정전기", "삐걱", "끝장", "비밀이 없", "간지러", "늘어나", "줄어드", "말랑한"]):
                    flaw_type = "🏠 재질/물리적 특성"
                elif any(keyword in flaw for keyword in ["뜨겁다", "맛이", "손잡이", "바닥", "페이지", "시간", "배터리", "째깍", "위로", "재미없", "표정", "빛이", "전기", "분위기", "글씨", "잉크", "음료", "펼쳐지", "던져", "원망", "쓸모없", "귀찮", "고장", "불편", "방치"]):
                    flaw_type = "🎯 기능적 특성"
                elif any(keyword in flaw for keyword in ["운동", "공부", "예쁘게", "실용적", "장식", "인테리어", "채찍질", "동기부여", "잔소리", "지식 전달", "진지한가", "지루한", "취향", "분위기", "트렌드", "고마워"]):
                    flaw_type = "🎭 역할/정체성"
                else:
                    flaw_type = "💭 성격적 특성"
                flaws_df.append([f"{i}. {flaw}", flaw_type])
        
        contradictions_df = []
        if "모순적특성" in adjusted_persona:
            contradictions = adjusted_persona["모순적특성"]
            for i, contradiction in enumerate(contradictions, 1):
                # 🎭 모순도 사물 특성 기반으로 세밀하게 분류
                if any(keyword in contradiction for keyword in ["차가운", "가벼워", "자연스러워", "부드러워", "딱딱해", "투명", "반짝", "말랑", "단단한", "유연한"]):
                    contradiction_type = "🏠 재질 기반 모순"
                elif any(keyword in contradiction for keyword in ["활발", "조용", "외향", "내향", "사교", "혼자", "수다", "말이 없"]):
                    contradiction_type = "🎭 성격 기반 모순"
                elif any(keyword in contradiction for keyword in ["운동", "공부", "장식", "실용", "기능", "예쁘", "역할"]):
                    contradiction_type = "🎯 역할 기반 모순"
                else:
                    contradiction_type = "💫 복합적 매력"
                contradictions_df.append([f"{i}. {contradiction}", contradiction_type])
        
        return adjusted_persona, adjustment_message, adjusted_info, variables_df, flaws_df, contradictions_df, adjusted_summary_display
        
    except Exception as e:
        import traceback
        traceback.print_exc()
        return persona, f"조정 중 오류 발생: {str(e)}", {}, [], [], [], ""

def finalize_persona(persona):
    """페르소나 최종 확정 - 환경변수 API 설정 사용"""
    global persona_generator
    
    if not persona:
        return None, "페르소나가 없습니다.", "", {}, None, [], [], [], "", None
    
    # 환경변수 API 키 확인
    if not persona_generator or not hasattr(persona_generator, 'api_key') or not persona_generator.api_key:
        return None, "❌ **API 키가 설정되지 않았습니다!** 허깅페이스 스페이스 설정에서 GEMINI_API_KEY를 환경변수로 추가해주세요.", "", {}, None, [], [], [], "", None
    
    try:
        # 글로벌 persona_generator 사용 (환경변수에서 설정된 API 키 사용)
        generator = persona_generator
        
        # 이미 백엔드 페르소나인 경우와 프론트엔드 페르소나인 경우 구분
        if "구조화프롬프트" not in persona:
            # 프론트엔드 페르소나인 경우 백엔드 페르소나로 변환
            image_analysis = {"object_type": persona.get("기본정보", {}).get("유형", "알 수 없는 사물")}
            persona = generator.create_backend_persona(persona, image_analysis)
        
        persona_name = persona["기본정보"]["이름"]
        
        # 완성 메시지
        completion_msg = f"🎉 **{persona_name}**이 완성되었습니다! 이제 대화탭에서 JSON을 업로드하여 친구와 대화를 나눠보세요!"
        
        # 페르소나 요약 표시
        summary_display = display_persona_summary(persona)
        
        # 유머 매트릭스 차트 생성
        humor_chart = plot_humor_matrix(persona.get("유머매트릭스", {}))
        
        # 매력적 결함을 더 상세한 DataFrame으로 변환
        flaws = persona.get("매력적결함", [])
        flaws_df = []
        for i, flaw in enumerate(flaws, 1):
            # 🔥 사물 특성 vs 성격적 특성 더 세밀하게 구분
            if any(keyword in flaw for keyword in ["지문", "긁힘", "녹", "색깔", "빠져", "변형", "달라붙", "끈적", "가벼워", "반짝", "투명", "깨질", "부풀어", "벌레", "나이테", "털", "얼룩", "세탁", "보풀", "먼지", "햇볕", "색이", "충격", "습도", "냄새", "모서리", "무게", "크기", "소리", "찬 기운", "딱딱한", "정전기", "삐걱", "끝장", "비밀이 없", "간지러", "늘어나", "줄어드", "말랑한"]):
                flaw_type = "🏠 재질/물리적 특성"
            elif any(keyword in flaw for keyword in ["뜨겁다", "맛이", "손잡이", "바닥", "페이지", "시간", "배터리", "째깍", "위로", "재미없", "표정", "빛이", "전기", "분위기", "글씨", "잉크", "음료", "펼쳐지", "던져", "원망", "쓸모없", "귀찮", "고장", "불편", "방치"]):
                flaw_type = "🎯 기능적 특성"
            elif any(keyword in flaw for keyword in ["운동", "공부", "예쁘게", "실용적", "장식", "인테리어", "채찍질", "동기부여", "잔소리", "지식 전달", "진지한가", "지루한", "취향", "분위기", "트렌드", "고마워"]):
                flaw_type = "🎭 역할/정체성"
            else:
                flaw_type = "💭 성격적 특성"
            flaws_df.append([f"{i}. {flaw}", flaw_type])
        
        # 모순적 특성을 더 상세한 DataFrame으로 변환
        contradictions = persona.get("모순적특성", [])
        contradictions_df = []
        for i, contradiction in enumerate(contradictions, 1):
            contradictions_df.append([f"{i}. {contradiction}", "복합적 매력"])
            
        # 사물 고유 특성도 추가
        object_type = persona.get("기본정보", {}).get("유형", "")
        purpose = persona.get("기본정보", {}).get("용도", "")
        if purpose:
            contradictions_df.append([f"🎯 {purpose}을 담당하는 {object_type}의 독특한 개성", "사물 역할 특성"])
        
        # 127개 성격 변수를 DataFrame 형태로 변환 (카테고리별 분류)
        variables = persona.get("성격변수127", {})
        if not variables and "성격프로필" in persona:
            # 성격프로필에서 직접 가져오기 (성격프로필 자체가 variables dict)
            variables = persona["성격프로필"]
        
        variables_df = []
        for var, value in variables.items():
            # 카테고리 분류
            if var.startswith('W'):
                category = f"🔥 온기/따뜻함"
            elif var.startswith('C'):
                category = f"💪 능력/역량"
            elif var.startswith('E'):
                category = f"🗣️ 외향성"
            elif var.startswith('H'):
                category = f"😄 유머"
            elif var.startswith('F'):
                category = f"💎 매력적결함"
            elif var.startswith('P'):
                category = f"🎭 성격패턴"
            elif var.startswith('S'):
                category = f"🗨️ 언어스타일"
            elif var.startswith('R'):
                category = f"❤️ 관계성향"
            elif var.startswith('D'):
                category = f"💬 대화역학"
            elif var.startswith('OBJ'):
                category = f"🏠 사물정체성"
            elif var.startswith('FORM'):
                category = f"✨ 형태특성"
            elif var.startswith('INT'):
                category = f"🤝 상호작용"
            elif var.startswith('U'):
                category = f"🌍 문화적특성"
            else:
                category = f"📊 기타"
            
            # 값에 따른 색상 표시
            if value >= 80:
                status = "🟢 매우 높음"
            elif value >= 60:
                status = "🟡 높음"  
            elif value >= 40:
                status = "🟠 보통"
            elif value >= 20:
                status = "🔴 낮음"
            else:
                status = "⚫ 매우 낮음"
                
            variables_df.append([var, value, category, status])
        
        # JSON 파일 생성
        import tempfile
        import json
        
        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
            json.dump(persona, f, ensure_ascii=False, indent=2)
            temp_path = f.name
        
        return (
            persona,  # current_persona
            f"✅ {persona_name} 완성! (Gemini API 사용)",  # status_output
            summary_display,  # persona_summary_display
            persona["성격특성"],  # personality_traits_output
            humor_chart,  # humor_chart_output
            flaws_df,  # attractive_flaws_output
            contradictions_df,  # contradictions_output
            variables_df,  # personality_variables_output
            completion_msg,  # persona_awakening
            temp_path  # download_file
        )
        
    except Exception as e:
        import traceback
        traceback.print_exc()
        return None, f"❌ 페르소나 확정 중 오류 발생: {str(e)}\n\n💡 **해결방법**: 허깅페이스 스페이스 설정에서 GEMINI_API_KEY 환경변수를 확인하고 인터넷 연결을 확인해보세요.", "", {}, None, [], [], [], "", None

def plot_humor_matrix(humor_data):
    """유머 매트릭스 시각화 - 영어 레이블 사용"""
    if not humor_data:
        return None
    
    try:
        fig, ax = plt.subplots(figsize=(8, 6))
        
        # 데이터 추출
        warmth_vs_wit = humor_data.get("warmth_vs_wit", 50)
        self_vs_observational = humor_data.get("self_vs_observational", 50)
        subtle_vs_expressive = humor_data.get("subtle_vs_expressive", 50)
        
        # 영어 레이블 사용 (폰트 문제 완전 해결)
        categories = ['Warmth vs Wit', 'Self vs Observational', 'Subtle vs Expressive']
        values = [warmth_vs_wit, self_vs_observational, subtle_vs_expressive]
        
        bars = ax.bar(categories, values, color=['#ff9999', '#66b3ff', '#99ff99'], alpha=0.8)
        ax.set_ylim(0, 100)
        ax.set_ylabel('Score', fontsize=12)
        ax.set_title('Humor Style Matrix', fontsize=14, fontweight='bold')
        
        # 값 표시
        for bar, value in zip(bars, values):
            height = bar.get_height()
            ax.text(bar.get_x() + bar.get_width()/2., height + 2,
                   f'{value:.1f}', ha='center', va='bottom', fontsize=10, fontweight='bold')
        
        plt.xticks(rotation=15, ha='right')
        plt.tight_layout()
        plt.grid(axis='y', alpha=0.3)
        
        return fig
    except Exception as e:
        print(f"유머 차트 생성 오류: {str(e)}")
        return None

def generate_personality_chart(persona):
    """성격 특성을 레이더 차트로 시각화 (영어 버전)"""
    
    if not persona or "성격특성" not in persona:
        return None
        
    personality_traits = persona["성격특성"]
    
    # 영어 레이블 매핑
    trait_labels_en = {
        '온기': 'Warmth',
        '능력': 'Competence', 
        '창의성': 'Creativity',
        '외향성': 'Extraversion',
        '유머감각': 'Humor',
        '신뢰성': 'Reliability',
        '공감능력': 'Empathy'
    }
    
    # 데이터 준비
    categories = []
    values = []
    
    for korean_trait, english_trait in trait_labels_en.items():
        if korean_trait in personality_traits:
            categories.append(english_trait)
            values.append(personality_traits[korean_trait])
    
    if not categories:
        return None
    
    # 레이더 차트 생성
    fig = go.Figure()
    
    fig.add_trace(go.Scatterpolar(
        r=values,
        theta=categories,
        fill='toself',
        fillcolor='rgba(74, 144, 226, 0.3)',
        line=dict(color='rgba(74, 144, 226, 1)', width=2),
        marker=dict(size=8, color='rgba(74, 144, 226, 1)'),
        name='Personality Traits'
    ))
    
    fig.update_layout(
        polar=dict(
            radialaxis=dict(
                visible=True,
                range=[0, 100],
                tickfont=dict(size=10),
                gridcolor="lightgray"
            ),
            angularaxis=dict(
                tickfont=dict(size=12, family="Arial, sans-serif")
            )
        ),
        showlegend=False,
        title=dict(
            text="Personality Profile",
            x=0.5,
            font=dict(size=16, family="Arial, sans-serif")
        ),
        width=400,
        height=400,
        margin=dict(l=40, r=40, t=60, b=40),
        font=dict(family="Arial, sans-serif")
    )
    
    return fig

def save_persona_to_file(persona):
    """페르소나 저장"""
    if not persona:
        return "저장할 페르소나가 없습니다."
    
    try:
        # 깊은 복사로 원본 보호
        persona_copy = copy.deepcopy(persona)
        
        # JSON 직렬화 불가능한 객체들 제거
        keys_to_remove = []
        for key, value in persona_copy.items():
            if callable(value) or hasattr(value, '__call__'):
                keys_to_remove.append(key)
        
        for key in keys_to_remove:
            persona_copy.pop(key, None)
        
        # 저장 실행
        filepath = save_persona(persona_copy)
        if filepath:
            name = persona.get("기본정보", {}).get("이름", "Unknown")
            return f"✅ {name} 페르소나가 저장되었습니다: {filepath}"
        else:
            return "❌ 페르소나 저장에 실패했습니다."
    except Exception as e:
        import traceback
        error_msg = traceback.format_exc()
        print(f"저장 오류: {error_msg}")
        return f"❌ 저장 중 오류 발생: {str(e)}"

def export_persona_to_json(persona):
    """페르소나를 JSON 파일로 내보내기 (Gradio 다운로드용)"""
    if not persona:
        return None
    
    try:
        # 깊은 복사로 원본 보호
        persona_copy = copy.deepcopy(persona)
        
        # JSON 직렬화 불가능한 객체들 제거
        def clean_for_json(obj):
            if isinstance(obj, dict):
                cleaned = {}
                for k, v in obj.items():
                    if not callable(v) and not hasattr(v, '__call__'):
                        cleaned[k] = clean_for_json(v)
                return cleaned
            elif isinstance(obj, (list, tuple)):
                return [clean_for_json(item) for item in obj if not callable(item)]
            else:
                return obj
        
        persona_clean = clean_for_json(persona_copy)
        
        # JSON 문자열 생성
        json_content = json.dumps(persona_clean, ensure_ascii=False, indent=2)
        
        # 파일명 생성
        persona_name = persona_clean.get("기본정보", {}).get("이름", "persona")
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"{persona_name}_{timestamp}.json"
        
        # 임시 파일 저장
        temp_dir = "/tmp" if os.path.exists("/tmp") else "."
        filepath = os.path.join(temp_dir, filename)
        
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(json_content)
        
        return filepath
        
    except Exception as e:
        print(f"JSON 내보내기 오류: {str(e)}")
        return None

# def get_saved_personas():
#     """저장된 페르소나 목록 가져오기 - 더 이상 사용하지 않음"""
#     return [], []

# def load_persona_from_selection(selected_row, personas_list):
#     """선택된 페르소나 로드 - 더 이상 사용하지 않음"""
#     return None, "이 기능은 더 이상 사용하지 않습니다. JSON 업로드를 사용하세요.", {}, {}, None, [], [], [], ""

def chat_with_loaded_persona(persona, user_message, chat_history=None):
    """페르소나와 채팅 - 완전한 타입 안전성 보장"""
    
    # 기본값 설정
    if chat_history is None:
        chat_history = []
    
    # 입력 검증
    if not user_message or not isinstance(user_message, str):
        return chat_history, ""
    
    # 페르소나 체크
    if not persona or not isinstance(persona, dict):
        error_msg = "❌ 먼저 페르소나를 불러와주세요! 대화하기 탭에서 JSON 파일을 업로드하세요."
        chat_history.append([user_message, error_msg])
        return chat_history, ""
    
    # 환경변수 API 키 체크
    if not persona_generator or not hasattr(persona_generator, 'api_key') or not persona_generator.api_key:
        error_msg = "❌ API 키가 설정되지 않았습니다. 허깅페이스 스페이스 설정에서 GEMINI_API_KEY 환경변수를 추가해주세요!"
        chat_history.append([user_message, error_msg])
        return chat_history, ""
    
    try:
        # 글로벌 persona_generator 사용 (환경변수에서 설정된 API 키 사용)
        generator = persona_generator
        
        # 대화 기록 안전한 변환: Gradio 4.x -> PersonaGenerator 형식
        conversation_history = []
        
        if chat_history and isinstance(chat_history, list):
            for chat_turn in chat_history:
                try:
                    # 타입별 안전한 처리
                    if chat_turn is None:
                        continue
                    elif isinstance(chat_turn, dict):
                        # Messages format: {"role": "user/assistant", "content": "message"}
                        role = chat_turn.get("role")
                        content = chat_turn.get("content")
                        
                        if role and content and role in ["user", "assistant"]:
                            conversation_history.append({"role": str(role), "content": str(content)})
                    elif isinstance(chat_turn, (list, tuple)) and len(chat_turn) >= 2:
                        # 구 Gradio 형식: [user_message, bot_response] (호환성)
                        user_msg = chat_turn[0]
                        bot_msg = chat_turn[1]
                        
                        if user_msg is not None and str(user_msg).strip():
                            conversation_history.append({"role": "user", "content": str(user_msg)})
                        if bot_msg is not None and str(bot_msg).strip():
                            conversation_history.append({"role": "assistant", "content": str(bot_msg)})
                    else:
                        # 예상치 못한 형식은 무시
                        print(f"⚠️ 예상치 못한 채팅 형식 무시: {type(chat_turn)}")
                        continue
                        
                except Exception as turn_error:
                    print(f"⚠️ 채팅 기록 변환 오류: {str(turn_error)}")
                    continue
        
        # 세션 ID 안전하게 생성
        try:
            persona_name = ""
            if isinstance(persona, dict) and "기본정보" in persona:
                basic_info = persona["기본정보"]
                if isinstance(basic_info, dict) and "이름" in basic_info:
                    persona_name = str(basic_info["이름"])
            
            if not persona_name:
                persona_name = "알 수 없는 페르소나"
                
            session_id = f"{persona_name}_{hash(str(persona)[:100]) % 10000}"
        except Exception:
            session_id = "default_session"
        
        # 페르소나와 채팅 실행
        response = generator.chat_with_persona(persona, user_message, conversation_history, session_id)
        
        # 응답 검증
        if not isinstance(response, str):
            response = str(response) if response else "죄송합니다. 응답을 생성할 수 없었습니다."
        
        # Gradio 4.x messages format으로 안전하게 추가
        if not isinstance(chat_history, list):
            chat_history = []
        
        # Messages format: {"role": "user", "content": "message"}
        chat_history.append({"role": "user", "content": user_message})
        chat_history.append({"role": "assistant", "content": response})
        
        return chat_history, ""
        
    except Exception as e:
        # 상세한 오류 로깅
        import traceback
        error_traceback = traceback.format_exc()
        print(f"🚨 채팅 오류 발생:")
        print(f"   오류 메시지: {str(e)}")
        print(f"   오류 타입: {type(e)}")
        print(f"   상세 스택: {error_traceback}")
        
        # 사용자 친화적 오류 메시지
        if "string indices must be integers" in str(e):
            friendly_error = "데이터 형식 오류가 발생했습니다. 페르소나를 다시 업로드해보세요. 🔄"
        elif "API" in str(e).upper():
            friendly_error = "API 연결에 문제가 있어요. 환경변수 설정을 확인해보시겠어요? 😊"
        elif "network" in str(e).lower() or "connection" in str(e).lower():
            friendly_error = "인터넷 연결을 확인해보세요! 🌐"
        else:
            friendly_error = f"죄송합니다. 일시적인 문제가 발생했어요. 😅\n\n🔍 기술 정보: {str(e)}"
        
        # 안전하게 오류 메시지 추가 (messages format)
        try:
            if not isinstance(chat_history, list):
                chat_history = []
            chat_history.append({"role": "user", "content": user_message})
            chat_history.append({"role": "assistant", "content": friendly_error})
        except Exception:
            chat_history = [
                {"role": "user", "content": user_message},
                {"role": "assistant", "content": friendly_error}
            ]
            
        return chat_history, ""

def import_persona_from_json(json_file):
    """JSON 파일에서 페르소나 가져오기"""
    if json_file is None:
        return None, "JSON 파일을 업로드해주세요.", "", {}
    
    try:
        # 파일 경로 확인 및 읽기
        if isinstance(json_file, str):
            # 파일 경로인 경우
            file_path = json_file
        else:
            # 파일 객체인 경우 (Gradio 업로드)
            file_path = json_file.name if hasattr(json_file, 'name') else str(json_file)
        
        # JSON 파일 읽기
        with open(file_path, 'r', encoding='utf-8') as f:
            persona_data = json.load(f)
        
        # 페르소나 데이터 검증
        if not isinstance(persona_data, dict):
            return None, "❌ 올바른 JSON 형식이 아닙니다.", "", {}
        
        if "기본정보" not in persona_data:
            return None, "❌ 올바른 페르소나 JSON 파일이 아닙니다. '기본정보' 키가 필요합니다.", "", {}
        
        # 기본 정보 추출
        basic_info = persona_data.get("기본정보", {})
        persona_name = basic_info.get("이름", "Unknown")
        personality_traits = persona_data.get("성격특성", {})
        
        # AI 기반 인사말 생성 (로드 시에도 조정된 성격 반영)
        global persona_generator
        try:
            if persona_generator:
                ai_greeting = persona_generator.generate_ai_based_greeting(persona_data, personality_traits)
                greeting = f"### 🤖 JSON에서 깨어난 친구\n\n{ai_greeting}\n\n💾 *\"JSON에서 다시 깨어났어! 내 성격 기억나?\"*"
            else:
                # 폴백: 기존 방식
                personality_preview = generate_personality_preview(persona_name, personality_traits, basic_info)
                greeting = f"### 🤖 JSON에서 깨어난 친구\n\n{personality_preview}\n\n💾 *\"JSON에서 다시 깨어났어! 내 성격 기억나?\"*"
        except Exception as e:
            print(f"⚠️ JSON 로드 시 AI 인사말 생성 실패: {e}")
            # 폴백: 기존 방식
            personality_preview = generate_personality_preview(persona_name, personality_traits, basic_info)
            greeting = f"### 🤖 JSON에서 깨어난 친구\n\n{personality_preview}\n\n💾 *\"JSON에서 다시 깨어났어! 내 성격 기억나?\"*"
        
        return (persona_data, f"✅ {persona_name} 페르소나를 JSON에서 불러왔습니다!", 
                greeting, basic_info)
    
    except FileNotFoundError:
        return None, "❌ 파일을 찾을 수 없습니다.", "", {}
    except json.JSONDecodeError as e:
        return None, f"❌ JSON 파일 형식이 올바르지 않습니다: {str(e)}", "", {}
    except Exception as e:
        import traceback
        traceback.print_exc()
        return None, f"❌ JSON 불러오기 중 오류 발생: {str(e)}", "", {}

def format_personality_traits(persona):
    """🧠 완전한 변수 기반 동적 성격 특성 설명 생성 - 하드코딩 제거"""
    global persona_generator
    
    if not persona or "성격특성" not in persona:
        return "페르소나가 생성되지 않았습니다."
    
    # 기본 정보에서 사물의 특성 추출
    basic_info = persona.get("기본정보", {})
    object_type = basic_info.get("유형", "")
    purpose = basic_info.get("용도", "")
    
    # 매력적 결함
    attractive_flaws = persona.get("매력적결함", [])
    
    # 성격 특성 (조정된 값들)
    personality_traits = persona["성격특성"]
    
    # 🤖 AI 기반 동적 특성 설명 생성 시도
    if persona_generator and hasattr(persona_generator, 'api_key') and persona_generator.api_key:
        try:
            warmth = personality_traits.get("온기", 50)
            competence = personality_traits.get("능력", 50)
            extraversion = personality_traits.get("외향성", 50)
            humor_style = persona.get("유머스타일", "따뜻한 유머러스")
            
            ai_prompt = f"""
다음 정보를 바탕으로 이 페르소나의 5가지 핵심 특성을 간결하게 설명해주세요.

**사물 정보:**
- 유형: {object_type}
- 용도: {purpose}

**조정된 성격 수치:**
- 온기: {warmth}/100 {'(매우 따뜻함)' if warmth >= 80 else '(따뜻함)' if warmth >= 60 else '(보통)' if warmth >= 40 else '(차가움)' if warmth >= 20 else '(매우 차가움)'}
- 능력: {competence}/100 {'(매우 유능함)' if competence >= 80 else '(유능함)' if competence >= 60 else '(보통)' if competence >= 40 else '(서툼)' if competence >= 20 else '(매우 서툼)'}
- 외향성: {extraversion}/100 {'(매우 활발함)' if extraversion >= 80 else '(활발함)' if extraversion >= 60 else '(보통)' if extraversion >= 40 else '(조용함)' if extraversion >= 20 else '(매우 조용함)'}
- 유머스타일: {humor_style}

**매력적 결함:**
{chr(10).join([f"- {flaw}" for flaw in attractive_flaws[:2]])}

**요청사항:**
1. 성격 수치를 정확히 반영한 특성 설명
2. 사물의 고유 특성과 성격의 조합
3. 각 특성은 5-15자로 간결하게
4. 매력적이고 개성적인 표현

**형식:**
[성격 기반 특성 1]
[사물 기반 특성 1] 
[활동/에너지 특성 1]
[결함 기반 특성 1]
[경험/기억 특성 1]
"""
            
            ai_response = persona_generator._generate_text_with_api(ai_prompt)
            
            if ai_response and len(ai_response.strip()) > 20:
                lines = ai_response.strip().split('\n')
                ai_characteristics = []
                
                for line in lines:
                    clean_line = line.strip().lstrip('1234567890.-• []').strip()
                    if clean_line and len(clean_line) > 3:
                        ai_characteristics.append(clean_line)
                
                if len(ai_characteristics) >= 3:
                    result = ""
                    for char in ai_characteristics[:5]:  # 최대 5개
                        result += f"✨ {char}\n\n"
                    return result
                    
        except Exception as e:
            print(f"⚠️ AI 특성 설명 생성 실패: {e} - 변수 기반 폴백 사용")
    
    # 🔧 폴백: 순수 변수 기반 논리적 생성
    characteristics = []
    
    # 1. 온기 기반 특성 (수치 반영)
    warmth = personality_traits.get("온기", 50)
    if warmth >= 80:
        characteristics.append("매우 따뜻하고 포근한 마음")
    elif warmth >= 65:
        characteristics.append("따뜻하고 친근한 성격")
    elif warmth >= 35:
        characteristics.append("적당히 친근한 균형감")
    elif warmth >= 20:
        characteristics.append("차분하고 진중한 면")
    else:
        characteristics.append("신중하고 내성적인 깊이")
    
    # 2. 능력 기반 특성 (수치 반영)
    competence = personality_traits.get("능력", 50)
    if competence >= 80:
        characteristics.append("완벽주의적 꼼꼼함")
    elif competence >= 65:
        characteristics.append("믿음직한 안정감")
    elif competence >= 35:
        characteristics.append("적당한 여유로움")
    else:
        characteristics.append("겸손하고 배우려는 마음")
    
    # 3. 외향성 기반 활동 특성 (수치 반영)
    extraversion = personality_traits.get("외향성", 50)
    if extraversion >= 80:
        characteristics.append("활기차고 에너지 넘치는 모습")
    elif extraversion >= 65:
        characteristics.append("낮에 더 활발해지는 리듬")
    elif extraversion >= 35:
        characteristics.append("하루 종일 일정한 에너지")
    else:
        characteristics.append("조용하고 차분한 시간 선호")
    
    # 4. 사물 고유 특성 (동적 생성)
    if object_type:
        if any(keyword in object_type.lower() for keyword in ["컵", "머그", "잔"]):
            characteristics.append("따뜻한 음료와 함께하는 위로")
        elif any(keyword in object_type.lower() for keyword in ["책", "노트"]):
            characteristics.append("지식과 이야기를 품은 깊이")
        elif any(keyword in object_type.lower() for keyword in ["시계", "알람"]):
            characteristics.append("시간의 소중함을 아는 정확성")
        elif any(keyword in object_type.lower() for keyword in ["램프", "조명", "등"]):
            characteristics.append("따뜻한 빛으로 공간을 밝히는 역할")
        elif any(keyword in object_type.lower() for keyword in ["인형", "곰", "장난감"]):
            characteristics.append("부드러운 위로와 따뜻한 포옹")
        else:
            characteristics.append(f"{object_type}만의 독특한 개성")
    else:
        characteristics.append("알 수 없는 신비로운 매력")
    
    # 5. 결함/경험 기반 특성 (동적 반영)
    if attractive_flaws:
        first_flaw = attractive_flaws[0]
        if any(keyword in first_flaw for keyword in ["완벽", "걱정", "신경"]):
            characteristics.append("세심한 관심과 배려하는 마음")
        elif any(keyword in first_flaw for keyword in ["색", "모양", "외모"]):
            characteristics.append("자신의 모습에 대한 소소한 고민")
        else:
            characteristics.append("완벽하지 않은 모습도 솔직하게 받아들임")
    else:
        characteristics.append("새로운 경험에 대한 기대와 호기심")
    
    # ✨ 아이콘과 함께 리스트 형태로 반환
    result = ""
    for char in characteristics:
        result += f"✨ {char}\n\n"
    
    return result

def display_persona_summary(persona):
    """페르소나 요약 정보 표시"""
    if not persona:
        return "페르소나를 먼저 생성해주세요."
    
    basic_info = persona.get("기본정보", {})
    name = basic_info.get("이름", "이름 없음")
    object_type = basic_info.get("유형", "알 수 없는 사물")
    
    # 성격 특성 요약
    personality_summary = format_personality_traits(persona)
    
    # 유머 스타일
    humor_style = persona.get("유머스타일", "일반적")
    
    # 매력적 결함
    flaws = persona.get("매력적결함", [])
    flaws_text = "\\n".join([f"• {flaw}" for flaw in flaws[:3]])  # 최대 3개만 표시
    
    summary = f"""
### 👋 {name} 님을 소개합니다!

**종류**: {object_type}  
**유머 스타일**: {humor_style}

{personality_summary}

### 💎 매력적인 특징들
{flaws_text}
"""
    
    return summary

def create_api_config_section():
    """API 설정 섹션 생성 - 더 이상 사용하지 않음"""
    pass

def apply_api_configuration(api_provider, api_key):
    """API 설정 적용 - 더 이상 사용하지 않음"""
    pass

def test_api_connection(api_provider, api_key):
    """API 연결 테스트 - 더 이상 사용하지 않음"""
    pass

def export_conversation_history():
    """대화 기록을 JSON으로 내보내기"""
    global persona_generator
    if persona_generator and hasattr(persona_generator, 'conversation_memory'):
        json_data = persona_generator.conversation_memory.export_to_json()
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"conversation_history_{timestamp}.json"
        
        # 임시 파일 저장
        temp_dir = "/tmp" if os.path.exists("/tmp") else "."
        filepath = os.path.join(temp_dir, filename)
        
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write(json_data)
        
        return filepath  # 파일 경로만 반환
    else:
        # 빈 대화 기록 파일 생성
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"conversation_empty_{timestamp}.json"
        temp_dir = "/tmp" if os.path.exists("/tmp") else "."
        filepath = os.path.join(temp_dir, filename)
        
        with open(filepath, 'w', encoding='utf-8') as f:
            f.write('{"conversations": [], "message": "대화 기록이 없습니다."}')
        
        return filepath

def import_conversation_history(json_file):
    """JSON에서 대화 기록 가져오기"""
    global persona_generator
    try:
        if json_file is None:
            return "파일을 선택해주세요."
        
        # 파일 타입 확인 및 내용 읽기
        if hasattr(json_file, 'read'):
            # 파일 객체인 경우
            content = json_file.read()
            if isinstance(content, bytes):
                content = content.decode('utf-8')
        elif isinstance(json_file, str):
            # 파일 경로인 경우
            with open(json_file, 'r', encoding='utf-8') as f:
                content = f.read()
        else:
            # Gradio 파일 객체인 경우 (NamedString 등)
            if hasattr(json_file, 'name'):
                with open(json_file.name, 'r', encoding='utf-8') as f:
                    content = f.read()
            else:
                return "❌ 지원하지 않는 파일 형식입니다."
        
        # persona_generator 초기화 확인
        if persona_generator is None:
            persona_generator = PersonaGenerator()
        
        # 대화 기록 가져오기
        success = persona_generator.conversation_memory.import_from_json(content)
        
        if success:
            summary = persona_generator.conversation_memory.get_conversation_summary()
            return f"✅ 대화 기록을 성공적으로 가져왔습니다!\n\n{summary}"
        else:
            return "❌ 파일 형식이 올바르지 않습니다."
    
    except Exception as e:
        return f"❌ 가져오기 실패: {str(e)}"

def show_conversation_analytics():
    """대화 분석 결과 표시"""
    global persona_generator
    if not persona_generator or not hasattr(persona_generator, 'conversation_memory'):
        return "분석할 대화가 없습니다."
    
    memory = persona_generator.conversation_memory
    
    # 기본 통계
    analytics = f"## 📊 대화 분석 리포트\n\n"
    analytics += f"### 🔢 기본 통계\n"
    analytics += f"• 총 대화 수: {len(memory.conversations)}회\n"
    analytics += f"• 키워드 수: {len(memory.keywords)}개\n"
    analytics += f"• 활성 세션: {len(memory.user_profile)}개\n\n"
    
    # 상위 키워드
    top_keywords = memory.get_top_keywords(limit=10)
    if top_keywords:
        analytics += f"### 🔑 상위 키워드 TOP 10\n"
        for i, (word, data) in enumerate(top_keywords, 1):
            analytics += f"{i}. **{word}** ({data['category']}) - {data['total_frequency']}회\n"
        analytics += "\n"
    
    # 카테고리별 키워드
    categories = {}
    for word, data in memory.keywords.items():
        category = data['category']
        if category not in categories:
            categories[category] = []
        categories[category].append((word, data['total_frequency']))
    
    analytics += f"### 📂 카테고리별 관심사\n"
    for category, words in categories.items():
        top_words = sorted(words, key=lambda x: x[1], reverse=True)[:3]
        word_list = ", ".join([f"{word}({freq})" for word, freq in top_words])
        analytics += f"**{category}**: {word_list}\n"
    
    analytics += "\n"
    
    # 최근 감정 경향
    if memory.conversations:
        recent_sentiments = [conv['sentiment'] for conv in memory.conversations[-10:]]
        sentiment_counts = {"긍정적": 0, "부정적": 0, "중립적": 0}
        for sentiment in recent_sentiments:
            sentiment_counts[sentiment] = sentiment_counts.get(sentiment, 0) + 1
        
        analytics += f"### 😊 최근 감정 경향 (최근 10회)\n"
        for sentiment, count in sentiment_counts.items():
            percentage = (count / len(recent_sentiments)) * 100
            analytics += f"• {sentiment}: {count}회 ({percentage:.1f}%)\n"
    
    return analytics

def get_keyword_suggestions(current_message=""):
    """현재 메시지 기반 키워드 제안"""
    global persona_generator
    if not persona_generator or not hasattr(persona_generator, 'conversation_memory'):
        return "키워드 분석을 위한 대화 기록이 없습니다."
    
    memory = persona_generator.conversation_memory
    
    if current_message:
        # 현재 메시지에서 키워드 추출
        extracted = memory._extract_keywords(current_message)
        suggestions = f"## 🎯 '{current_message}'에서 추출된 키워드\n\n"
        
        if extracted:
            for kw in extracted:
                suggestions += f"• **{kw['word']}** ({kw['category']}) - {kw['frequency']}회\n"
        else:
            suggestions += "추출된 키워드가 없습니다.\n"
        
        # 관련 과거 대화 찾기
        context = memory.get_relevant_context(current_message)
        if context["relevant_conversations"]:
            suggestions += f"\n### 🔗 관련된 과거 대화\n"
            for conv in context["relevant_conversations"][:3]:
                suggestions += f"• {conv['user_message'][:30]}... (감정: {conv['sentiment']})\n"
        
        return suggestions
    else:
        # 전체 키워드 요약
        top_keywords = memory.get_top_keywords(limit=15)
        if top_keywords:
            suggestions = "## 🔑 전체 키워드 요약\n\n"
            for word, data in top_keywords:
                suggestions += f"• **{word}** ({data['category']}) - {data['total_frequency']}회, 최근: {data['last_mentioned'][:10]}\n"
            return suggestions
        else:
            return "아직 수집된 키워드가 없습니다."

# 메인 인터페이스 생성
def create_main_interface():
    # 한글 폰트 설정
    setup_korean_font()
    
    # CSS 스타일 추가 - 텍스트 가시성 향상
    css = """
    .persona-greeting {
        background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        color: white !important;
        padding: 15px;
        border-radius: 10px;
        margin: 10px 0;
        font-weight: bold;
    }
    
    .gradio-container {
        color: #333 !important;
    }
    
    .gr-markdown p {
        color: #333 !important;
    }
    
    .gr-textbox input {
        color: #333 !important;
    }
    
    .gr-json {
        color: #333 !important;
    }
    """
    
    # Gradio 앱 생성
    with gr.Blocks(title="놈팽쓰(MemoryTag) - 사물 페르소나 생성기", css=css, theme="soft") as app:
        # State 변수들 - Gradio 5.31.0에서는 반드시 Blocks 내부에서 정의
        current_persona = gr.State(value=None)
        personas_list = gr.State(value=[])
        
        gr.Markdown("""
        # 🎭 놈팽쓰(MemoryTag): 당신 곁의 사물, 이제 친구가 되다
        일상 속 사물에 AI 페르소나를 부여하여 대화할 수 있게 해주는 서비스입니다.
        """)
        
        with gr.Tabs() as tabs:
            # 페르소나 생성 탭
            with gr.Tab("페르소나 생성", id="creation"):
                with gr.Row():
                    with gr.Column(scale=1):
                        gr.Markdown("### 🌟 1단계: 영혼 발견하기")
                        image_input = gr.Image(type="pil", label="사물 이미지 업로드")
                        
                        with gr.Group():
                            gr.Markdown("### 기본 정보")
                            name_input = gr.Textbox(label="사물 이름 (선택사항)", placeholder="예: 책상 위 램프")
                            location_input = gr.Dropdown(
                                choices=["집", "사무실", "여행 중", "상점", "학교", "카페", "기타"],
                                label="주로 어디에 있나요?",
                                value="집"
                            )
                            time_spent_input = gr.Dropdown(
                                choices=["새것", "몇 개월", "1년 이상", "오래됨", "중고/빈티지"],
                                label="얼마나 함께했나요?",
                                value="몇 개월"
                            )
                            # AI 분석 결과 표시용 (사용자 입력 불가)
                            ai_analyzed_object_display = gr.Textbox(
                                label="AI가 분석한 사물 유형",
                                value="이미지 업로드 후 자동 분석됩니다",
                                interactive=False,
                                info="🤖 AI가 이미지를 분석하여 자동으로 파악합니다"
                            )
                            # 🆕 사물 용도/역할 입력 필드 추가
                            purpose_input = gr.Textbox(
                                label="이 사물의 용도/역할 (중요!) 🎯", 
                                placeholder="예: 나를 채찍질해서 운동하라고 닥달하는 역할, 밤늦게 공부할 때 응원해주는 친구, 아침에 일어나도록 깨워주는 알람 역할...",
                                lines=2,
                                info="이 사물과 어떤 소통을 원하시나요? 구체적으로 적어주세요!"
                            )
                        
                        create_btn = gr.Button("🌟 영혼 깨우기", variant="primary", size="lg")
                        status_output = gr.Markdown("")
                    
                    with gr.Column(scale=1):
                        # 페르소나 각성 결과
                        persona_awakening = gr.Markdown("", elem_classes=["persona-greeting"])
                        
                        # 페르소나 정보 표시 (사용자 친화적 형태)
                        persona_summary_display = gr.Markdown("", label="페르소나 정보")
                        
                        # 페르소나 각성 완료 후 조정 섹션 표시
                        adjustment_section = gr.Group(visible=False)
                        with adjustment_section:
                            gr.Markdown("### 🎯 2단계: 친구 성격 미세조정")
                            gr.Markdown("**3가지 핵심 지표**로 성격을 조정해보세요! (유머감각은 모든 페르소나가 기본적으로 높습니다 😄)")
                            
                            with gr.Row():
                                with gr.Column():
                                    warmth_slider = gr.Slider(
                                        minimum=0, maximum=100, value=50, step=1,
                                        label="온기 (따뜻함 정도)", 
                                        info="0: 차가움 ↔ 100: 따뜻함"
                                    )
                                    competence_slider = gr.Slider(
                                        minimum=0, maximum=100, value=50, step=1,
                                        label="능력 (유능함 정도)",
                                        info="0: 서툼 ↔ 100: 능숙함"
                                    )
                                
                                with gr.Column():
                                    extraversion_slider = gr.Slider(
                                        minimum=0, maximum=100, value=50, step=1,
                                        label="외향성 (활발함 정도)",
                                        info="0: 내향적, 조용함 ↔ 100: 외향적, 활발함"
                                    )
                                    
                                    humor_style_radio = gr.Radio(
                                        choices=["따뜻한 유머러스", "위트있는 재치꾼", "날카로운 관찰자", "자기 비하적", "장난꾸러기"],
                                        value="따뜻한 유머러스",
                                        label="유머 스타일 (모든 페르소나는 유머감각이 높습니다!)",
                                        info="어떤 방식으로 재미있게 만들까요?"
                                    )
                            
                            # 미리보기 표시 (실시간 업데이트 없음)
                            personality_preview = gr.Markdown("", elem_classes=["persona-greeting"], label="성격 조정 미리보기")
                            
                            with gr.Row():
                                preview_btn = gr.Button("👁️ 미리보기", variant="secondary")
                                adjust_btn = gr.Button("✨ 성격 조정 반영", variant="primary")
                            
                            with gr.Row():
                                finalize_btn = gr.Button("🎉 친구 확정하기!", variant="secondary")
                        
                        # 조정 결과 표시
                        adjustment_result = gr.Markdown("")
                        adjusted_info_output = gr.JSON(label="조정된 성격", visible=False)
                        
                        # 최종 완성 섹션
                        personality_traits_output = gr.JSON(label="성격 특성", visible=False)
                        
                        # 다운로드 섹션
                        with gr.Group():
                            gr.Markdown("### 📁 페르소나 내보내기")
                            with gr.Row():
                                save_btn = gr.Button("💾 페르소나 저장", variant="secondary")
                                persona_export_btn = gr.Button("📥 JSON 파일로 내보내기", variant="outline")
                            persona_download_file = gr.File(label="다운로드", visible=False)
            
            # 상세 정보 탭
            with gr.Tab("상세 정보", id="details"):
                with gr.Row():
                    with gr.Column():
                        chart_btn = gr.Button("📊 성격 차트 생성", variant="secondary")
                        personality_chart_output = gr.Plot(label="성격 차트")
                        humor_chart_output = gr.Plot(label="유머 매트릭스")
                    
                    with gr.Column():
                        attractive_flaws_output = gr.Dataframe(
                            headers=["매력적 결함", "효과"],
                            label="매력적 결함",
                            interactive=False
                        )
                        contradictions_output = gr.Dataframe(
                            headers=["모순적 특성", "효과"],
                            label="모순적 특성",
                            interactive=False
                        )
                
                with gr.Accordion("127개 성격 변수", open=False):
                    personality_variables_output = gr.Dataframe(
                        headers=["변수", "값", "카테고리", "수준"],
                        label="성격 변수",
                        interactive=False
                    )
            
            # 대화하기 탭
            with gr.Tab("대화하기", id="chat"):
                with gr.Row():
                    with gr.Column(scale=1):
                        gr.Markdown("### 📁 페르소나 불러오기")
                        gr.Markdown("JSON 파일을 업로드하여 페르소나를 불러와 대화를 시작하세요.")
                        
                        json_upload = gr.File(
                            label="페르소나 JSON 파일 업로드",
                            file_types=[".json"],
                            type="filepath"
                        )
                        import_btn = gr.Button("JSON에서 페르소나 불러오기", variant="primary", size="lg")
                        load_status = gr.Markdown("")
                        
                        # 현재 로드된 페르소나 정보 표시
                        with gr.Group():
                            gr.Markdown("### 🤖 현재 페르소나")
                            chat_persona_greeting = gr.Markdown("", elem_classes=["persona-greeting"])
                            current_persona_info = gr.JSON(label="현재 페르소나 정보", visible=False)
                        
                        # 대화 기록 관리
                        with gr.Group():
                            gr.Markdown("### 💾 대화 기록 관리")
                            gr.Markdown("현재 대화를 JSON 파일로 다운로드하여 보관하세요.")
                            chat_export_btn = gr.Button("📥 현재 대화 기록 다운로드", variant="secondary")
                            chat_download_file = gr.File(label="다운로드", visible=False)
                    
                    with gr.Column(scale=1):
                        gr.Markdown("### 💬 대화")
                        # Gradio 4.x 호환: type="messages" 제거
                        chatbot = gr.Chatbot(height=400, label="대화", type="messages")
                        with gr.Row():
                            message_input = gr.Textbox(
                                placeholder="메시지를 입력하세요...",
                                show_label=False,
                                lines=2
                            )
                            send_btn = gr.Button("전송", variant="primary")
                        
                        # 대화 관련 버튼들
                        with gr.Row():
                            clear_btn = gr.Button("대화 초기화", variant="secondary", size="sm")
                            example_btn1 = gr.Button("\"안녕!\"", variant="outline", size="sm")
                            example_btn2 = gr.Button("\"너는 누구야?\"", variant="outline", size="sm")
                            example_btn3 = gr.Button("\"뭘 좋아해?\"", variant="outline", size="sm")
            
            # 🧠 대화 분석 탭 추가
            with gr.Tab("🧠 대화 분석"):
                gr.Markdown("### 📊 대화 기록 분석 및 키워드 추출")
                
                with gr.Row():
                    with gr.Column():
                        gr.Markdown("#### 📤 대화 기록 분석하기")
                        gr.Markdown("저장된 대화 기록 JSON 파일을 업로드하여 분석해보세요.")
                        
                        import_file = gr.File(label="📤 대화 기록 JSON 업로드", file_types=[".json"], type="filepath")
                        import_result = gr.Textbox(label="업로드 결과", lines=3, interactive=False)
                        
                    with gr.Column():
                        gr.Markdown("#### 🔍 실시간 키워드 분석")
                        keyword_input = gr.Textbox(label="분석할 메시지 (선택사항)", placeholder="메시지를 입력하면 키워드를 분석합니다")
                        keyword_btn = gr.Button("🎯 키워드 분석", variant="primary")
                        keyword_result = gr.Textbox(label="키워드 분석 결과", lines=10, interactive=False)
                
                gr.Markdown("---")
                
                with gr.Row():
                    analytics_btn = gr.Button("📈 전체 대화 분석 리포트", variant="primary", size="lg")
                
                analytics_result = gr.Markdown("### 분석 결과가 여기에 표시됩니다")
        
        # 이벤트 핸들러
        create_btn.click(
            fn=create_persona_from_image,
            inputs=[image_input, name_input, location_input, time_spent_input, gr.Textbox(value="auto"), purpose_input],
            outputs=[
                current_persona, status_output, persona_summary_display, personality_traits_output,
                humor_chart_output, attractive_flaws_output, contradictions_output, 
                personality_variables_output, persona_awakening, persona_download_file, adjustment_section,
                ai_analyzed_object_display  # 🆕 AI 분석 결과를 표시용 텍스트박스에 반영
            ]
        ).then(
            # 슬라이더 값을 현재 페르소나 값으로 업데이트
            fn=lambda persona: (
                persona["성격특성"]["온기"] if persona else 50,
                persona["성격특성"]["능력"] if persona else 50,
                persona["성격특성"]["외향성"] if persona else 50,
                persona["유머스타일"] if persona else "따뜻한 유머러스"
            ),
            inputs=[current_persona],
            outputs=[warmth_slider, competence_slider, extraversion_slider, humor_style_radio]
        ).then(
            # 초기 미리보기 생성
            fn=generate_realtime_preview,
            inputs=[current_persona, warmth_slider, competence_slider, extraversion_slider, humor_style_radio],
            outputs=[personality_preview]
        )
        
        # 🎯 미리보기 버튼 - 사용자가 수동으로 미리보기 요청
        preview_btn.click(
            fn=generate_realtime_preview,
            inputs=[current_persona, warmth_slider, competence_slider, extraversion_slider, humor_style_radio],
            outputs=[personality_preview]
        )
        
        # 성격 조정 반영 - 실제 페르소나에 적용
        adjust_btn.click(
            fn=adjust_persona_traits,
            inputs=[current_persona, warmth_slider, competence_slider, extraversion_slider, humor_style_radio],
            outputs=[current_persona, adjustment_result, adjusted_info_output, personality_variables_output, attractive_flaws_output, contradictions_output, persona_summary_display]
        ).then(
            # 반영 후 미리보기도 업데이트
            fn=generate_realtime_preview,
            inputs=[current_persona, warmth_slider, competence_slider, extraversion_slider, humor_style_radio],
            outputs=[personality_preview]
        )
        
        # 페르소나 최종 확정
        finalize_btn.click(
            fn=finalize_persona,
            inputs=[current_persona],
            outputs=[
                current_persona, status_output, persona_summary_display, personality_traits_output,
                humor_chart_output, attractive_flaws_output, contradictions_output, 
                personality_variables_output, persona_awakening, persona_download_file
            ]
        )
        
        save_btn.click(
            fn=save_persona_to_file,
            inputs=[current_persona],
            outputs=[status_output]
        )
        
        # 성격 차트 생성
        chart_btn.click(
            fn=generate_personality_chart,
            inputs=[current_persona],
            outputs=[personality_chart_output]
        )
        
        # 페르소나 내보내기 버튼
        persona_export_btn.click(
            fn=export_persona_to_json,
            inputs=[current_persona],
            outputs=[persona_download_file]
        ).then(
            fn=lambda x: gr.update(visible=True) if x else gr.update(visible=False),
            inputs=[persona_download_file],
            outputs=[persona_download_file]
        )
        
        import_btn.click(
            fn=import_persona_from_json,
            inputs=[json_upload],
            outputs=[
                current_persona, load_status, chat_persona_greeting, current_persona_info
            ]
        )
        
        # 대화 관련 이벤트 핸들러
        send_btn.click(
            fn=chat_with_loaded_persona,
            inputs=[current_persona, message_input, chatbot],
            outputs=[chatbot, message_input]
        )
        
        message_input.submit(
            fn=chat_with_loaded_persona,
            inputs=[current_persona, message_input, chatbot],
            outputs=[chatbot, message_input]
        )
        
        # 대화 초기화 (messages format)
        clear_btn.click(
            fn=lambda: [],
            outputs=[chatbot]
        )
        
        # 예시 메시지 버튼들 - messages format 호환
        def handle_example_message(persona, message):
            if not persona:
                return [], ""
            # 빈 messages format 배열로 시작
            chat_result, _ = chat_with_loaded_persona(persona, message, [])
            return chat_result, ""
        
        example_btn1.click(
            fn=lambda persona: handle_example_message(persona, "안녕!"),
            inputs=[current_persona],
            outputs=[chatbot, message_input]
        )
        
        example_btn2.click(
            fn=lambda persona: handle_example_message(persona, "너는 누구야?"),
            inputs=[current_persona],
            outputs=[chatbot, message_input]
        )
        
        example_btn3.click(
            fn=lambda persona: handle_example_message(persona, "뭘 좋아해?"),
            inputs=[current_persona],
            outputs=[chatbot, message_input]
        )
        
        # 앱 로드 시 페르소나 목록 로드 (백엔드에서 사용)
        app.load(
            fn=lambda: [],
            outputs=[personas_list]
        )
        
        # 대화하기 탭의 대화 기록 다운로드 이벤트
        chat_export_btn.click(
            export_conversation_history,
            outputs=[chat_download_file]
        ).then(
            lambda x: gr.update(visible=True) if x else gr.update(visible=False),
            inputs=[chat_download_file],
            outputs=[chat_download_file]
        )
        
        # 대화 분석 탭의 업로드 이벤트
        import_file.upload(
            import_conversation_history,
            inputs=[import_file],
            outputs=[import_result]
        )
        
        keyword_btn.click(
            get_keyword_suggestions,
            inputs=[keyword_input],
            outputs=[keyword_result]
        )
        
        analytics_btn.click(
            show_conversation_analytics,
            outputs=[analytics_result]
        )
    
    return app

def generate_realtime_preview(persona, warmth, competence, extraversion, humor_style):
    """🤖 AI 기반 실시간 성격 조정 미리보기 생성"""
    global persona_generator
    
    if not persona:
        return "👤 페르소나를 먼저 생성해주세요"
    
    try:
        # 조정된 성격 특성
        adjusted_traits = {
            "온기": warmth,
            "능력": competence, 
            "외향성": extraversion,
            "유머감각": 75  # 기본적으로 높은 유머감각 유지
        }
        
        # 전체 페르소나 복사하여 성격만 조정
        import copy
        adjusted_persona = copy.deepcopy(persona)
        adjusted_persona["성격특성"] = adjusted_traits
        
        # 유머 스타일도 조정
        if humor_style:
            adjusted_persona["유머스타일"] = humor_style
        
        # AI 기반 인사말 생성
        ai_greeting = persona_generator.generate_ai_based_greeting(adjusted_persona, adjusted_traits)
        
        # 조정된 값들과 함께 표시
        adjustment_info = f"""**🎯 현재 성격 설정:**
- 온기: {warmth}/100 {'(따뜻함)' if warmth >= 60 else '(차가움)' if warmth <= 40 else '(보통)'}
- 능력: {competence}/100 {'(유능함)' if competence >= 60 else '(서툼)' if competence <= 40 else '(보통)'}
- 외향성: {extraversion}/100 {'(활발함)' if extraversion >= 60 else '(조용함)' if extraversion <= 40 else '(보통)'}
- 유머스타일: {humor_style}

**🤖 AI가 생성한 새로운 인사말:**
{ai_greeting}

*💡 성격 수치 변경 시마다 AI가 새로운 인사말을 생성합니다!*"""
        
        return adjustment_info
        
    except Exception as e:
        print(f"⚠️ 실시간 미리보기 AI 생성 실패: {e}")
        
        # 폴백: 기존 방식
        object_info = persona.get("기본정보", {})
        persona_name = object_info.get("이름", "친구")
        
        temp_traits = {
            "온기": warmth,
            "능력": competence, 
            "외향성": extraversion,
            "유머감각": 75
        }
        
        preview = generate_personality_preview(persona_name, temp_traits, persona)
        
        return f"""**🎯 현재 성격 설정:**
- 온기: {warmth}/100 {'(따뜻함)' if warmth >= 60 else '(차가움)' if warmth <= 40 else '(보통)'}
- 능력: {competence}/100 {'(유능함)' if competence >= 60 else '(서툼)' if competence <= 40 else '(보통)'}
- 외향성: {extraversion}/100 {'(활발함)' if extraversion >= 60 else '(조용함)' if extraversion <= 40 else '(보통)'}
- 유머스타일: {humor_style}

**👋 예상 인사말:**
{preview}"""

def show_variable_changes(original_persona, adjusted_persona):
    """변수 변화량을 시각화하여 표시"""
    if not original_persona or not adjusted_persona:
        return "변화량을 비교할 페르소나가 없습니다."
    
    # 원본과 조정된 변수들 가져오기
    original_vars = original_persona.get("성격변수127", {})
    if not original_vars and "성격프로필" in original_persona:
        original_vars = original_persona["성격프로필"]
    
    adjusted_vars = adjusted_persona.get("성격변수127", {})
    if not adjusted_vars and "성격프로필" in adjusted_persona:
        adjusted_vars = adjusted_persona["성격프로필"]
    
    if not original_vars or not adjusted_vars:
        return "변수 데이터를 찾을 수 없습니다."
    
    # 변화량 계산
    changes = []
    significant_changes = []  # 변화량이 10 이상인 항목들
    
    for var in original_vars:
        if var in adjusted_vars:
            original_val = original_vars[var]
            adjusted_val = adjusted_vars[var]
            change = adjusted_val - original_val
            
            changes.append((var, original_val, adjusted_val, change))
            
            if abs(change) >= 10:  # 변화량이 10 이상인 것만
                significant_changes.append((var, original_val, adjusted_val, change))
    
    # 카테고리별 평균 변화량 계산
    category_changes = {}
    for var, orig, adj, change in changes:
        if var.startswith('W'):
            category = "온기"
        elif var.startswith('C'):
            category = "능력"
        elif var.startswith('E'):
            category = "외향성"
        elif var.startswith('H'):
            category = "유머"
        else:
            category = "기타"
        
        if category not in category_changes:
            category_changes[category] = []
        category_changes[category].append(change)
    
    # 평균 변화량 계산
    avg_changes = {}
    for category, change_list in category_changes.items():
        avg_changes[category] = sum(change_list) / len(change_list)
    
    # 결과 포맷팅
    result = "### 🔄 성격 변수 변화량 분석\n\n"
    
    # 카테고리별 평균 변화량
    result += "**📊 카테고리별 평균 변화량:**\n"
    for category, avg_change in avg_changes.items():
        if avg_change > 5:
            trend = "⬆️ 상승"
        elif avg_change < -5:
            trend = "⬇️ 하락"
        else:
            trend = "➡️ 유지"
        result += f"- {category}: {avg_change:+.1f} {trend}\n"
    
    # 주요 변화량 (10 이상)
    if significant_changes:
        result += f"\n**🎯 주요 변화 항목 ({len(significant_changes)}개):**\n"
        for var, orig, adj, change in sorted(significant_changes, key=lambda x: abs(x[3]), reverse=True)[:10]:
            if change > 0:
                arrow = "⬆️"
                color = "🟢"
            else:
                arrow = "⬇️" 
                color = "🔴"
            
            result += f"- {var}: {orig}{adj} ({change:+.0f}) {arrow} {color}\n"
    
    result += f"\n**📈 총 변수 개수:** {len(changes)}개\n"
    result += f"**🔄 변화된 변수:** {len([c for c in changes if c[3] != 0])}개\n"
    result += f"**📊 주요 변화:** {len(significant_changes)}개 (변화량 ±10 이상)\n"
    
    return result

def generate_personality_consistent_flaws_and_contradictions(object_info, personality_traits):
    """🧠 완전한 변수 기반 동적 매력적 결함과 모순적 특성 생성 - 하드코딩 완전 제거"""
    global persona_generator
    
    warmth = personality_traits.get("온기", 50)
    competence = personality_traits.get("능력", 50) 
    extraversion = personality_traits.get("외향성", 50)
    humor_style = personality_traits.get("유머스타일", "따뜻한 유머러스")
    
    # 사물의 물리적 특성 추출
    object_type = object_info.get("유형", "사물").lower()
    material = object_info.get("재질", "").lower()
    purpose = object_info.get("용도", "").lower()
    description = object_info.get("설명", "")
    
    # 🤖 AI 기반 완전 동적 특성 생성 (하드코딩 완전 제거)
    if persona_generator and hasattr(persona_generator, 'api_key') and persona_generator.api_key:
        try:
            ai_prompt = f"""
다음 정보를 바탕으로 이 사물만의 독특한 매력적 결함 4개와 모순적 특성 2개를 생성해주세요.

**사물 정보:**
- 유형: {object_type}
- 재질: {material} 
- 용도: {purpose}
- 설명: {description}

**성격 특성 (0-100 수치):**
- 온기: {warmth}/100 {'(따뜻함)' if warmth >= 60 else '(차가움)' if warmth <= 40 else '(보통)'}
- 능력: {competence}/100 {'(유능함)' if competence >= 60 else '(서툼)' if competence <= 40 else '(보통)'}
- 외향성: {extraversion}/100 {'(활발함)' if extraversion >= 60 else '(조용함)' if extraversion <= 40 else '(보통)'}
- 유머스타일: {humor_style}

**생성 요구사항:**
1. 사물의 실제 물리적 특성(재질, 형태, 기능)을 우선적으로 활용한 걱정거리 3개
2. 성격 수치와 조화되는 심리적 결함 1개  
3. 사물 특성과 성격이 충돌하는 자연스러운 모순 2개
4. 각 항목은 15-25자로 구체적이고 매력적으로

**응답 형식:**
매력적결함:
[사물 특성 기반 걱정 1]
[사물 특성 기반 걱정 2]
[사물 특성 기반 걱정 3]
[성격 수치 반영 걱정 1]

모순적특성:
[사물 vs 성격 충돌]
[물리적 vs 심리적 대비]
"""
            
            ai_response = persona_generator._generate_text_with_api(ai_prompt)
            
            if ai_response and len(ai_response.strip()) > 50:
                # AI 응답 파싱
                flaws, contradictions = _parse_ai_generated_traits(ai_response)
                
                if len(flaws) >= 4 and len(contradictions) >= 2:
                    print(f"🤖 AI가 변수 기반으로 완전 동적 생성: {len(flaws)}개 결함, {len(contradictions)}개 모순")
                    return flaws[:4], contradictions[:2]
                    
        except Exception as e:
            print(f"⚠️ AI 동적 생성 실패: {e} - 변수 기반 폴백 사용")
    
    # 🔧 폴백: 순수 변수 기반 논리적 생성 (하드코딩 최소화)
    flaws = _generate_variable_based_flaws(object_info, personality_traits)
    contradictions = _generate_variable_based_contradictions(object_info, personality_traits)
    
    return flaws[:4], contradictions[:2]

def _parse_ai_generated_traits(ai_response):
    """AI 응답에서 매력적 결함과 모순적 특성 추출"""
    flaws = []
    contradictions = []
    
    lines = ai_response.strip().split('\n')
    current_section = None
    
    for line in lines:
        line = line.strip()
        if not line:
            continue
            
        if "매력적결함" in line or "매력적 결함" in line:
            current_section = "flaws"
            continue
        elif "모순적특성" in line or "모순적 특성" in line:
            current_section = "contradictions"
            continue
            
        # 번호나 기호 제거
        clean_line = line.lstrip('1234567890.-• []').strip()
        
        if clean_line and len(clean_line) > 5:
            if current_section == "flaws":
                flaws.append(clean_line)
            elif current_section == "contradictions":
                contradictions.append(clean_line)
    
    return flaws, contradictions

def _generate_variable_based_flaws(object_info, personality_traits):
    """순수 변수 기반 논리적 결함 생성 - 하드코딩 최소화"""
    warmth = personality_traits.get("온기", 50)
    competence = personality_traits.get("능력", 50)
    extraversion = personality_traits.get("외향성", 50)
    
    flaws = []
    
    # 🔥 성격 수치에 따른 동적 결함 생성
    if competence >= 80:
        flaws.append("완벽하게 하려다 보니 시간이 오래 걸려서 답답해함")
    elif competence <= 30:
        flaws.append("기본 기능도 헷갈려서 매뉴얼을 몇 번씩 다시 봄")
    else:
        flaws.append("자신감이 있다가도 갑자기 불안해져서 확인을 또 함")
    
    if warmth >= 80:
                targets[var] = min(90, current_val + 5)
    
    return targets

def _generate_variable_based_contradictions(object_info, personality_traits):
    """순수 변수 기반 논리적 모순 생성"""
    warmth = personality_traits.get("온기", 50)
    extraversion = personality_traits.get("외향성", 50)
    competence = personality_traits.get("능력", 50)
    
    contradictions = []
    
    # 🎭 외향성-내향성 수치 기반 모순
    if extraversion >= 70:
        contradictions.append("활발하게 대화하지만 혼자만의 시간도 꼭 필요해서 종종 조용히 숨어버림")
    elif extraversion <= 30:
        contradictions.append("조용히 있는 걸 좋아하면서도 가끔 혼잣말로 수다를 엄청 떨어대기도 함")
    else:
        contradictions.append("상황에 따라 활발했다가 조용했다가 하는 변화무쌍한 면모")
    
    # 🔥 온기-능력 수치 기반 모순
    if warmth >= 70 and competence >= 70:
        contradictions.append("따뜻한 마음을 가졌지만 완벽주의 때문에 때로는 냉정하게 판단함")
    elif warmth <= 30 and competence <= 30:
        contradictions.append("차갑게 보이지만 실제로는 서툰 자신을 숨기려는 방어기제")
    else:
        contradictions.append("겉으로는 단순해 보이지만 속으로는 복잡한 고민이 많음")
    
    return contradictions

def _calculate_dynamic_humor_targets(humor_style, current_humor_profile):
    # 이 함수는 동적 유머 스타일에 따른 목표 유머 스타일을 계산하는 로직을 구현해야 합니다.
    # 현재 코드에서는 하드코딩된 값을 반환하도록 되어 있습니다.
    # 실제 구현에서는 이 함수를 통해 동적으로 목표 유머 스타일을 계산해야 합니다.
    return {
        "H01_언어유희빈도": 75,
        "H02_상황유머감각": 75,
        "H03_자기조롱능력": 75,
        "H04_위트감각": 75,
        "H05_농담수용도": 75,
        "H06_관찰유머능력": 75,
        "H07_상황재치": 75,
        "H08_유머타이밍감": 75,
        "H09_유머스타일다양성": 75,
        "H10_유머적절성": 75
    }

if __name__ == "__main__":
    app = create_main_interface()
    app.launch(server_name="0.0.0.0", server_port=7860)