File size: 29,137 Bytes
29a53ac
44eee27
26e04ff
f1fa1f8
 
5df7340
 
26e04ff
63efdf3
 
 
 
26e04ff
63efdf3
 
 
 
 
 
 
 
 
 
 
 
 
26e04ff
 
 
 
 
 
 
 
 
eeaff20
26e04ff
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeaff20
26e04ff
f1fa1f8
 
eeaff20
5df7340
26e04ff
 
f1fa1f8
 
26e04ff
6fbaa52
 
 
 
 
 
 
6ef90a1
 
 
 
5df7340
26e04ff
6ef90a1
 
 
 
 
 
6fbaa52
6ef90a1
 
5df7340
6ef90a1
5df7340
6ef90a1
f1fa1f8
6ef90a1
 
 
 
5df7340
f1fa1f8
6ef90a1
 
 
 
 
6fbaa52
6ef90a1
eeaff20
63efdf3
 
 
 
 
 
 
4be4e6c
63efdf3
f1fa1f8
6ef90a1
 
 
 
 
5df7340
 
f1fa1f8
6ef90a1
63efdf3
6ef90a1
 
4be4e6c
f1fa1f8
6ef90a1
 
 
 
 
 
 
6fbaa52
6ef90a1
 
eeaff20
63efdf3
eeaff20
 
6ef90a1
 
 
 
5df7340
6ef90a1
 
f1fa1f8
6ef90a1
 
 
5df7340
6ef90a1
5df7340
6ef90a1
 
 
 
 
6fbaa52
6ef90a1
 
5df7340
11ee858
f1fa1f8
6ef90a1
 
 
 
f1fa1f8
6fbaa52
 
 
 
 
 
 
6ef90a1
5df7340
6ef90a1
5df7340
11ee858
f1fa1f8
6ef90a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eeaff20
 
 
6ef90a1
 
f1fa1f8
6ef90a1
f1fa1f8
6ef90a1
 
eeaff20
 
6ef90a1
5df7340
f1fa1f8
6ef90a1
 
 
 
 
 
5df7340
 
26e04ff
 
63efdf3
6ef90a1
 
f1fa1f8
6ef90a1
 
 
63efdf3
 
 
 
26e04ff
 
 
 
4be4e6c
6ef90a1
63efdf3
 
4be4e6c
63efdf3
 
 
 
 
 
 
 
4be4e6c
eeaff20
 
 
 
f1fa1f8
6ef90a1
 
 
63efdf3
6ef90a1
f1fa1f8
 
63efdf3
f1fa1f8
63efdf3
 
4be4e6c
f1fa1f8
 
63efdf3
 
eeaff20
f1fa1f8
 
63efdf3
eeaff20
 
 
 
 
 
 
f1fa1f8
 
 
 
 
63efdf3
 
 
26e04ff
 
eeaff20
4be4e6c
eeaff20
 
 
 
 
f1fa1f8
 
63efdf3
f1fa1f8
 
 
 
 
 
 
 
 
 
63efdf3
f1fa1f8
 
63efdf3
 
f1fa1f8
26e04ff
63efdf3
 
f1fa1f8
26e04ff
5df7340
63efdf3
f1fa1f8
26e04ff
eeaff20
4be4e6c
 
 
40ef213
f1fa1f8
4be4e6c
f1fa1f8
 
 
11ee858
 
eeaff20
 
 
f1fa1f8
6ef90a1
 
eeaff20
 
f1fa1f8
 
eeaff20
 
f1fa1f8
eeaff20
 
f1fa1f8
 
63efdf3
f1fa1f8
 
6ef90a1
 
5df7340
 
f1fa1f8
6ef90a1
f1fa1f8
6ef90a1
4be4e6c
 
 
 
 
 
 
 
 
 
 
 
 
f1fa1f8
 
 
6ef90a1
eeaff20
f1fa1f8
 
 
 
 
 
6ef90a1
eeaff20
f1fa1f8
4be4e6c
f1fa1f8
6ef90a1
f1fa1f8
 
 
 
 
6ef90a1
f1fa1f8
6ef90a1
eeaff20
 
f1fa1f8
 
 
 
 
6ef90a1
f1fa1f8
6ef90a1
f1fa1f8
eeaff20
 
 
f1fa1f8
 
6ef90a1
63efdf3
26e04ff
 
 
 
 
 
63efdf3
26e04ff
 
eeaff20
 
 
6ef90a1
36249d2
 
e48be7b
 
 
3a3c667
36249d2
eeaff20
f1fa1f8
 
 
 
 
 
63efdf3
f1fa1f8
 
 
 
 
63efdf3
6ef90a1
 
 
 
 
 
eeaff20
6ef90a1
eeaff20
6ef90a1
f1fa1f8
 
 
 
 
 
 
6ef90a1
 
 
 
 
f1fa1f8
63efdf3
eeaff20
 
 
f1fa1f8
6ef90a1
f1fa1f8
 
 
6ef90a1
eeaff20
6ef90a1
eeaff20
 
f1fa1f8
 
6ef90a1
f1fa1f8
eeaff20
 
26e04ff
 
4be4e6c
 
44eee27
26e04ff
 
4be4e6c
 
 
 
 
44eee27
 
4be4e6c
 
 
 
 
 
 
 
 
 
 
 
 
 
44eee27
 
f1fa1f8
 
 
 
 
 
 
 
 
11ee858
f1fa1f8
44eee27
26e04ff
4be4e6c
44eee27
eeaff20
 
6ef90a1
f1fa1f8
6ef90a1
f1fa1f8
 
 
 
 
6ef90a1
 
eeaff20
 
 
 
6ef90a1
 
 
eeaff20
26e04ff
eeaff20
6ef90a1
 
 
5df7340
6ef90a1
f1fa1f8
6ef90a1
 
 
 
eeaff20
 
 
 
 
e48be7b
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
const ACCESS_PASSWORDS=["12345","2030"];

const EXPORT_COLUMNS=[
  "التصنيف","نوع المشكلة","وقت حدوث المشكلة","اسم صاحب المشكلة",
  "رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة","اسم الدعم الفني","الحالة"
];

const DISPLAY_COLUMNS=[
  "التصنيف","نوع المشكلة","المنطقة","اسم المسح","اسم المشتغل",
  "رقم الجوال","رقم الهوية ID","رقم الجهاز","تاريخ اليوم بالميلادي","الحالة","اسم الدعم الفني"
];

const DISPLAY_TO_BASE={
  "التصنيف":"التصنيف",
  "نوع المشكلة":"نوع المشكلة",
  "المنطقة":"المنطقة",
  "اسم المسح":"المسح",
  "اسم المشتغل":"اسم صاحب المشكلة",
  "رقم الجوال":"رقم الجوال",
  "رقم الهوية ID":"رقم الهوية",
  "رقم الجهاز":"رقم الجهاز",
  "تاريخ اليوم بالميلادي":"تاريخ اليوم بالميلادي",
  "الحالة":"الحالة",
  "اسم الدعم الفني":"اسم الدعم الفني"
};

const FIELD_ALIASES={
  "نوع المشكلة":["نوع المشكله","نوع المشكلة","المشكلة","نوع-المشكلة","نوع  المشكلة"],
  "وقت حدوث المشكلة":["وقت حدوث المشكله","وقت حدوث المشكلة","وقت المشكلة","وقت حدوث","وقت حدوث المشكله:","وقت حدوث المشكله :"],
  "اسم صاحب المشكلة":["اسم صاحب المشكله","اسم صاحب المشكلة","اسم صاحب البلاغ","الاسم"],
  "رقم الهوية":["رقم الهويه","رقم الهوية","الهوية","هوية"],
  "رقم الجهاز":["رقم الجهاز","الجهاز"],
  "رقم الجوال":["رقم الجوال","الجوال","الهاتف","جوال"],
  "المسح":["المسح","اسم المسح"],
  "المنطقة":["المنطقة","المنطقه","اسم المنطقة","المدينة","المحافظة","منطقة"]
};
const START_LABELS=Array.from(new Set(Object.values(FIELD_ALIASES).flat()));

const CLASS_RULES={
  "استفسار":["استفسار","سؤال","استعلام","معلومة","استفسارات"],
  "إضافة أجهزة":["اضافة جهاز","إضافة أجهزة","اضافة اجهزة","تركيب جهاز","جهاز جديد","تسجيل جهاز","ربط جهاز","اضافة ماسح","إضافة ماسح"],
  "الاستمارة":["الاستمارة","استمارة","النموذج","نموذج","الفورم","تعليق الاستمارة","لا استطيع اكمال الاستمارة","التعبئة"],
  "التقييم":["التقييم","تقييم","feedback","survey","رضا","نجوم"],
  "الخرائط":["الخرائط","خرائط","map","gps","تحديد الموقع","احداثيات","إحداثيات","الموقع الجغرافي"],
  "السوتي":["السوتي","سوتي","soti","soti assist","mobicontrol","soti mobicontrol"],
  "الشبكة":["الشبكة","شبكة","نت","انترنت","إنترنت","wifi","واي فاي","4g","5g","ضعف الشبكة","stc","mobily","زين","weak signal","no signal"],
  "النسخة":["النسخة","نسخة","الإصدار","اصدار","version","build","release","تحديث نسخة","ترقية النسخة"],
  "النظام المكتبي":["النظام المكتبي","نسخة ويندوز","ويندوز","windows","pc app","برنامج المكتب","التطبيق على الكمبيوتر","الديسكتوب"],
  "تسجيل دخول":["تسجيل دخول","تسجيل الدخول","login","signin","رفض تسجيل الدخول","لا يقبل الدخول","اسم المستخدم","كلمة المرور","نسيت كلمة السر","إعادة تعيين"],
  "تفعيل حساب":["تفعيل حساب","تفعيل","activation","activate","رمز التفعيل","كود التفعيل"],
  "تناقل البيانات":["تناقل البيانات","ترحيل البيانات","مزامنة","sync","مزامنه","نقل البيانات","رفع البيانات","sync failed","المزامنة"],
  "صيانة وتحديث الأجهزة":["صيانة","تحديث الأجهزة","تحديث جهاز","ترقية الجهاز","اعطال الجهاز","تصليح","صيانة وتحديث الأجهزة","صيانة الجهاز"]
};
const CLASS_PRIORITY=[
  "صيانة وتحديث الأجهزة","إضافة أجهزة","تسجيل دخول","تفعيل حساب","الاستمارة","التقييم",
  "الخرائط","السوتي","الشبكة","النسخة","النظام المكتبي","تناقل البيانات","استفسار"
];

const TICKET_SEP=/\n\s*(?:\n{2,}|—+|-{3,}|={3,}|🔴+)\s*\n/;
const arabicDigitsMap={"٠":"0","١":"1","٢":"2","٣":"3","٤":"4","٥":"5","٦":"6","٧":"7","٨":"8","٩":"9"};

function normalizeText(s){
  if(typeof s!=="string")return"";
  return s.replace(/\r\n/g,"\n")
          .replace(/[\u200f\u200e\u202a-\u202e\u2066-\u2069\u00a0]/g," ")
          .replace(/[٠-٩]/g,d=>arabicDigitsMap[d])
          .replace(/[ــ]+/g,"")
          .replace(/[ \t]+\n/g,"\n")
          .replace(/\n{3,}/g,"\n\n")
          .trim();
}
function lettersOnly(ar){return(ar||"").replace(/[^A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s]/g,"").replace(/\s{2,}/g," ").trim()}
function alnumAr(s){return(s||"").replace(/[^0-9A-Za-z\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\s\-\._/]/g,"").replace(/\s{2,}/g," ").trim()}
function digitsOnly(s){return(s||"").replace(/\D+/g,"")}

const LABEL_FIXES=[
  [/(^|\n)\s*نوع\s*المشكله/gi,"$1نوع المشكلة"],
  [/(^|\n)\s*وقت\s*حدوث\s*المشكله/gi,"$1وقت حدوث المشكلة"],
  [/(^|\n)\s*اسم\s*صاحب\s*المشكله/gi,"$1اسم صاحب المشكلة"],
  [/(^|\n)\s*رقم\s*الهويه/gi,"$1رقم الهوية"],
  [/(^|\n)\s*المنطقه/gi,"$1المنطقة"],
  [/(^|\n)\s*اسم\s*المنطقة/gi,"$1المنطقة"],
  [/(^|\n)\s*اسم\s*المسح/gi,"$1المسح"],
  [/(^|\n)\s*الهاتف/gi,"$1رقم الجوال"],
  [/(^|\n)\s*جوال/gi,"$1رقم الجوال"]
];
function fixLabels(s){let t=s;LABEL_FIXES.forEach(([re,rep])=>t=t.replace(re,rep));return t}

const H_MONTHS=["محرم","صفر","ربيع الأول","ربيع الاول","ربيع الآخر","ربيع الاخر","جمادى الأولى","جمادى الاولى","جمادى الآخرة","جمادى الاخرة","رجب","شعبان","رمضان","شوال","ذو القعدة","ذو القعده","ذو الحجة","ذو الحجه"];
function monthIndexHijri(name){
  const i=H_MONTHS.findIndex(m=>new RegExp("^"+m+"$","i").test(name.trim()));
  if(i<0)return-1;
  const map={0:1,1:2,2:3,3:3,4:4,5:4,6:5,7:5,8:6,9:6,10:7,11:8,12:9,13:10,14:11,15:11,16:12,17:12};
  return map[i]||-1;
}
function hijriToGregorian(hy,hm,hd){
  const jd=Math.floor((11*hy+3)/30)+354*hy+30*hm-Math.floor((hm-1)/2)+hd+1948440-385;
  let l=jd+68569;
  let n=Math.floor(4*l/146097);l=l-Math.floor((146097*n+3)/4);
  let i=Math.floor(4000*(l+1)/1461001);l=l-Math.floor(1461*i/4)+31;
  let j=Math.floor(80*l/2447);const d=l-Math.floor(2447*j/80);
  l=Math.floor(j/11);const m=j+2-12*l;const y=100*(ن-49)+i+l;
  return[y,m,d];
}
function isTimeOnly(t){
  const a=/(^|\s)\d{1,2}\s*(?:[:٫\.\-]\d{2})\s*(?:ص|صباح(?:اً|ا)?|am|م|مساء|pm)?($|\s)/i.test(t);
  const b=/(^|\s)\d{1,2}\s*(?:ص|صباح(?:اً|ا)?|am|م|مساء|pm)($|\s)/i.test(t);
  const c=/الساعة\s*\d{1,2}(?:[:٫\.\-]\d{2})?/i.test(t);
  return a||b||c;
}
function isRelativeDatePhrase(t){
  return /(أمس|من\s*أمس|من\s*امس|اليوم|غدًا|غدا|بكرة|بعد\s*بكرة|قبل\s*\d+\s*(?:دقيقة|دقايق|ساع(?:ة|ات)|يوم|أيام)|الآن|الحين|قبل\s*شوي)/i.test(t);
}
function detectHijriDate(str){
  const t=normalizeText(str);
  let m=t.match(/(\d{1,2})\s+([^\s]+)\s+(\d{3,4})\s*(هـ|ه|هجري)?/i);
  if(m){const d=+m[1];const hm=monthIndexHijri(m[2]);const y=+m[3];if(hm>=1&&hm<=12)return{hy:y,hm:hm,hd:d}}
  m=t.match(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{3,4})\s*(هـ|ه|هجري)/i);
  if(m)return{hy:+m[3],hm:+m[2],hd:+m[1]};
  return null;
}
function normalizeDateOnly(raw){
  const t=normalizeText(raw);
  if(isTimeOnly(t)||isRelativeDatePhrase(t))return"";
  const hj=detectHijriDate(t);
  if(hj){const[gy,gm,gd]=hijriToGregorian(hj.hy,hj.hm,hj.hd);return`${String(gy).padStart(4,"0")}-${String(gm).padStart(2,"0")}-${String(gd).padStart(2,"0")}`}
  const m=t.match(/(\d{1,4})[\/\-](\d{1,2})[\/\-](\d{1,4})/);
  if(m){
    let a=+m[1],b=+m[2],c=+m[3],y,mo,d;
    if(String(m[1]).length===4){y=a;mo=b;d=c}
    else if(String(m[3]).length===4){y=c;mo=b;d=a}
    else if(a>31){y=a;mo=b;d=c}
    else if(c>31){y=c;mo=b;d=a}
    else{y=c;mo=b;d=a}
    if(y<100)y+=2000;
    if(mo>12&&d<=12){const tmp=mo;mo=d;d=tmp}
    if(mo<1||mo>12||d<1||d>31)return"";
    return`${String(y).padStart(4,"0")}-${String(mo).padStart(2,"0")}-${String(d).padStart(2,"0")}`
  }
  return"";
}

function findStartsByLabels(text,labels){
  const lblRe=labels.map(l=>l.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join("|");
  const re=new RegExp(`(^|\\n)\\s*(?:[-–—\\*•]+|\\d+[\\)\\.]\\s*)?\\s*(?:${lblRe})(?:\\s*[::]|\\s+)`,"gi");
  const idxs=[];let m;while((m=re.exec(text))){idxs.push(m.index+(m[1]?m[1].length:0))}return idxs;
}
function findAfterLabel(text,labels){
  const hay="\n"+normalizeText(text)+"\n";
  for(const rawLbl of labels){
    const lbl=rawLbl.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
    let m=hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s*[::]\\s*([^\\n]+)`,"i"));if(m)return m[1].trim();
    m=hay.match(new RegExp(`(?:^|\\n)\\s*${lbl}\\s+([^\\n]+)`,"i"));if(m)return m[1].trim();
  }
  return"";
}
function findBlockAfterLabel(text,labels,allLabels=START_LABELS){
  const hay="\n"+normalizeText(text)+"\n";
  const esc=s=>s.replace(/[.*+?^${}()|[\]\\]/g,'\\$&');
  const lblAlt=labels.map(esc).join("|");
  const allAlt=allLabels.map(esc).join("|");
  const re=new RegExp(`(?:^|\\n)\\s*(?:${lblAlt})\\s*(?::|:|\\s)\\s*([\\s\\S]*?)(?=\\n\\s*(?:${allAlt})\\s*(?::|:|\\s)|$)`,"i");
  const m=hay.match(re);
  return m?m[1].trim():"";
}

function splitTickets(raw){
  const text=normalizeText(fixLabels(raw));
  if(!text)return[];
  if(TICKET_SEP.test(text))return text.split(TICKET_SEP).map(p=>p.trim()).filter(Boolean);
  const niu=findStartsByLabels(text,["نوع المشكلة","نوع المشكله"]).sort((a,b)=>a-b);
  if(niu.length>=2){
    const parts=[];
    for(let i=0;i<niu.length;i++){
      const s=niu[i];
      const e=i+1<niu.length?niu[i+1]:text.length;
      const slice=text.slice(s,e).trim();
      if(slice)parts.push(slice);
    }
    if(parts.length)return parts;
  }
  return[text];
}

function extractFields(ticketText){
  const text=normalizeText(fixLabels(ticketText));
  const out={"نوع المشكلة":"","وقت حدوث المشكلة":"","اسم صاحب المشكلة":"","رقم الهوية":"","رقم الجهاز":"","رقم الجوال":"","المسح":"","المنطقة":""};
  let v=findBlockAfterLabel(text,FIELD_ALIASES["نوع المشكلة"],START_LABELS);
  if(!v)v=findAfterLabel(text,FIELD_ALIASES["نوع المشكلة"]);
  if(v)out["نوع المشكلة"]=normalizeText(v);
  v=findAfterLabel(text,FIELD_ALIASES["وقت حدوث المشكلة"]);
  if(v)out["وقت حدوث المشكلة"]=normalizeDateOnly(v);
  v=findAfterLabel(text,FIELD_ALIASES["اسم صاحب المشكلة"]);
  if(v)out["اسم صاحب المشكلة"]=v;
  v=findAfterLabel(text,FIELD_ALIASES["رقم الهوية"]);
  if(v)out["رقم الهوية"]=digitsOnly(v);
  if(!out["رقم الهوية"]){const m=text.match(/(?:^|\D)((?:1|2)\d{9})(?:\D|$)/);if(m)out["رقم الهوية"]=m[1]}
  v=findAfterLabel(text,FIELD_ALIASES["رقم الجهاز"]);
  if(v)out["رقم الجهاز"]=digitsOnly(v);
  if(!out["رقم الجهاز"]){const m=text.match(/(?:^|\D)(\d{5,20})(?:\D|$)/);if(m)out["رقم الجهاز"]=m[1]}
  v=findAfterLabel(text,FIELD_ALIASES["رقم الجوال"]);
  if(v)out["رقم الجوال"]=digitsOnly(v);
  if(!out["رقم الجوال"]){const m=text.match(/(?:^|\D)(05\d{7,})(?:\D|$)/);if(m)out["رقم الجوال"]=m[1]}
  v=findAfterLabel(text,FIELD_ALIASES["المسح"]);
  if(v)out["المسح"]=alnumAr(v);
  v=findAfterLabel(text,FIELD_ALIASES["المنطقة"]);
  if(v)out["المنطقة"]=lettersOnly(v);
  return out;
}

function classifyTicket(text,fields){
  const hay=normalizeText(`${text}\n${fields?.["نوع المشكلة"]||""}`).toLowerCase();
  for(const label of CLASS_PRIORITY){
    const kws=CLASS_RULES[label]||[];
    for(const kw of kws){
      const needle=normalizeText(kw).toLowerCase();
      if(needle&&hay.includes(needle))return label;
    }
  }
  return"استفسار";
}
function catClass(label){
  if(/تسجيل دخول/.test(label))return"login";
  if(/الشبكة/.test(label))return"network";
  if(/الاستمارة/.test(label))return"form";
  if(/النسخة|تحديث/.test(label))return"update";
  if(/أجهزة/.test(label))return"device";
  return"default";
}

function isValidNationalId(d){const x=(d||"").replace(/\D/g,"");return/^[12]\d{9}$/.test(x)}
function isPhoneNumber(d){const x=(d||"").replace(/\D/g,"");return/^05\d{8}$/.test(x)}

function parseTicketsWithExtras(raw,agentName,defaultRegion){
  const regionChosen=(defaultRegion||"").toString();
  return splitTickets(raw||"").map(t=>{
    const f=extractFields(t);
    const cls=classifyTicket(t,f);
    const region=regionChosen?regionChosen:(f["المنطقة"]||"");
    let survey=f["المسح"]||region;
    let id=(f["رقم الهوية"]||"").replace(/\D/g,"");
    let dev=(f["رقم الجهاز"]||"").replace(/\D/g,"");
    let phone=(f["رقم الجوال"]||"").replace(/\D/g,"");
    if(!isValidNationalId(id))id="";
    if(!isPhoneNumber(phone))phone="";
    if(!dev&&isValidNationalId(id))dev=id;
    if(!id&&isValidNationalId(dev))id=dev;
    const dateOnly=f["وقت حدوث المشكلة"]||"";
    return{
      "التصنيف":cls,
      "نوع المشكلة":f["نوع المشكلة"]||"",
      "وقت حدوث المشكلة":dateOnly,
      "اسم صاحب المشكلة":f["اسم صاحب المشكلة"]||"",
      "رقم الهوية":id,
      "رقم الجهاز":dev,
      "رقم الجوال":phone,
      "المسح":survey||region,
      "المنطقة":region,
      "اسم الدعم الفني":agentName||"",
      "الحالة":"تم الحل",
      "تاريخ اليوم بالميلادي":dateOnly
    };
  });
}

function buildTable(rows){
  const theadRow=document.getElementById("theadRow");
  const tbody=document.getElementById("tbody");
  theadRow.innerHTML="";
  DISPLAY_COLUMNS.forEach(col=>{const th=document.createElement("th");th.textContent=col;theadRow.appendChild(th)});
  tbody.innerHTML="";
  rows.forEach(r=>{
    const tr=document.createElement("tr");
    DISPLAY_COLUMNS.forEach(col=>{
      const td=document.createElement("td");
      const base=DISPLAY_TO_BASE[col];
      td.dataset.base=base;
      let val=(base?(r[base]||""):"");
      if(col==="التصنيف"){
        const span=document.createElement("span");
        span.className=`cat ${catClass(val||"")}`;
        span.textContent=val||"";
        td.appendChild(span);
      }else{
        td.contentEditable="true";
        td.textContent=val;
      }
      tr.appendChild(td);
    });
    tbody.appendChild(tr);
  });
}

function readTable(){
  const tbody=document.getElementById("tbody");
  const rows=[];
  [...tbody.querySelectorAll("tr")].forEach(tr=>{
    const obj={};
    [...tr.children].forEach(td=>{
      const base=td.dataset.base;
      if(!base)return;
      if(base==="التصنيف"){obj[base]=(td.querySelector("span")?.textContent||td.textContent||"").trim()}
      else{obj[base]=(td.textContent||"").trim()}
    });
    obj["وقت حدوث المشكلة"]=obj["تاريخ اليوم بالميلادي"]||"";
    rows.push(obj);
  });
  return rows;
}

function updateBadge(n){
  const b=document.getElementById("countBadge");
  if(!b)return;
  b.textContent=n;
  b.hidden=(n===0);
}
function setButtonsEnabled(hasRows){
  document.getElementById("btn-export").disabled=!hasRows;
  document.getElementById("btn-copy").disabled=!hasRows;
}

function validateCells(){
  const tbody=document.getElementById("tbody");
  const required=new Set(["نوع المشكلة","اسم صاحب المشكلة","رقم الهوية","رقم الجهاز","رقم الجوال","المسح","المنطقة"]);
  let missing=0;
  [...tbody.rows].forEach(tr=>{
    [...tr.children].forEach(td=>{
      const base=td.dataset.base||"";
      const val=(td.textContent||"").trim();
      let invalid=false,reason="";
      if(required.has(base)&&!val){invalid=true;reason="required";missing++}
      if(base==="رقم الهوية"){
        const digits=val.replace(/\D/g,"");
        if(val&&!/^[12]\d{9}$/.test(digits)){invalid=true;if(!reason)reason="id"}
      }
      if(base==="رقم الجوال"){
        const digits=val.replace(/\D/g,"");
        if(val&&!/^05\d{8}$/.test(digits)){invalid=true;if(!reason)reason="phone"}
      }
      if(base==="تاريخ اليوم بالميلادي"){
        if(val&&!/^\d{4}-\d{2}-\d{2}$/.test(val)){invalid=true;reason="date"}
      }
      //td.classList.toggle("invalid",invalid);
      if(invalid){
        const msg=reason==="required"?"الحقل مطلوب":reason==="id"?"رقم الهوية يجب أن يبدأ بـ 1 أو 2 وطوله 10 خانات":reason==="phone"?"رقم الجوال يجب أن يبدأ بـ 05 وطوله 10 خانات":"أدخل تاريخ بصيغة YYYY-MM-DD";
        td.setAttribute("title",msg);
        td.dataset.reason=reason;
      }else{
        td.removeAttribute("title");
        delete td.dataset.reason;
      }
    });
  });
  const warn=document.getElementById("warn");
  if(missing>0){warn.hidden=false;warn.textContent=`هناك ${missing} حقول مطلوبة فارغة أو غير صحيحة. يرجى إكمالها.`}
  else{warn.hidden=true;warn.textContent=""}
}

document.addEventListener("input",e=>{
  if(e.target&&e.target.closest&&e.target.closest("#tbody")){
    validateCells();
    saveState();
  }
});

function toast(msg){
  const t=document.getElementById("toast");
  if(!t)return;
  t.textContent=msg;
  t.hidden=false;
  t.classList.remove("show");void t.offsetWidth;t.classList.add("show");
  setTimeout(()=>{t.hidden=true},2000);
}

async function exportExcel(){
  const TEMPLATE_HEADERS=["التصنيف","نوع المشكلة","المنطقة","اسم المسح","اسم المشغل","رقم الجوال","رقم الهوية ID","رقم الجهاز","وقت حدوث المشكلة","الحالة","اسم الدعم الفني"];
  const rows=readTable();
  if(!rows.length){toast("لا يوجد بيانات لتصديرها.");return}
  const mapRow=r=>({
    "التصنيف":r["التصنيف"]||"",
    "نوع المشكلة":r["نوع المشكلة"]||"",
    "المنطقة":r["المنطقة"]||"",
    "اسم المسح":r["المسح"]||"",
    "اسم المشغل":r["اسم صاحب المشكلة"]||"",
    "رقم الجوال":(r["رقم الجوال"]||"").toString(),
    "رقم الهوية ID":(r["رقم الهوية"]||"").toString(),
    "رقم الجهاز":(r["رقم الجهاز"]||"").toString(),
    "وقت حدوث المشكلة":r["وقت حدوث المشكلة"]||"",
    "الحالة":r["الحالة"]||"تم الحل",
    "اسم الدعم الفني":r["اسم الدعم الفني"]||""
  });
  const wb=new ExcelJS.Workbook();
  const ws=wb.addWorksheet("التذاكر",{views:[{rightToLeft:true}]});
  const colWidths=[16,18,16,18,20,18,18,18,20,14,18];
  TEMPLATE_HEADERS.forEach((h,i)=>ws.getColumn(i+1).width=colWidths[i]||18);
  ws.addRow(TEMPLATE_HEADERS);
  const headerRow=ws.getRow(1);
  headerRow.height=24;
  headerRow.eachCell(cell=>{
    cell.font={bold:true,color:{argb:"FFFFFFFF"}};
    cell.alignment={horizontal:"center",vertical:"middle"};
    cell.fill={type:"pattern",pattern:"solid",fgColor:{argb:"FF4137A8"}};
    cell.border={top:{style:"thin",color:{argb:"FFCDD2E1"}},bottom:{style:"thin",color:{argb:"FFCDD2E1"}},left:{style:"thin",color:{argb:"FFE5E7EB"}},right:{style:"thin",color:{argb:"FFE5E7EB"}}};
  });
  const toTextCols=new Set(["رقم الجوال","رقم الهوية ID","رقم الجهاز"]);
  rows.forEach((r,idx)=>{
    const m=mapRow(r);
    const vals=TEMPLATE_HEADERS.map(h=>(m[h]??""));
    const row=ws.addRow(vals);
    row.alignment={horizontal:"center",vertical:"middle"};
    const even=(idx%2)===1;
    row.eachCell((cell,colNumber)=>{
      cell.border={top:{style:"thin",color:{argb:"FFE5E7EB"}},bottom:{style:"thin",color:{argb:"FFE5E7EB"}},left:{style:"thin",color:{argb:"FFE5E7EB"}},right:{style:"thin",color:{argb:"FFE5E7EB"}}};
      if(even)cell.fill={type:"pattern",pattern:"solid",fgColor:{argb:"FFF5F8FF"}};
      const header=TEMPLATE_HEADERS[colNumber-1];
      if(toTextCols.has(header))cell.value=String(cell.value??"");
    });
  });
  const ts=new Date().toISOString().replace(/\D/g,"").slice(0,14);
  const filename=`Ticket_${ts}.xlsx`;
  const buffer=await wb.xlsx.writeBuffer();
  const blob=new Blob([buffer],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
  const file=new File([blob],filename,{type:blob.type});
  if(navigator.canShare&&navigator.canShare({files:[file]})){try{await navigator.share({files:[file],title:"ملف التذاكر"});toast("تمت المشاركة/الحفظ.");return}catch(e){}}
  const url=URL.createObjectURL(blob);
  const a=document.createElement("a");a.href=url;a.download=filename;document.body.appendChild(a);a.click();a.remove();
  setTimeout(()=>URL.revokeObjectURL(url),1000);
  toast("تم تنزيل الملف بتنسيق القالب.");
}

async function copyToClipboardTSV(){
  const rows=readTable();
  if(!rows.length){toast("لا يوجد بيانات لنسخها.");return}
  const textCols=new Set(["رقم الهوية ID","رقم الجهاز","رقم الجوال"]);
  const body=rows.map(r=>DISPLAY_COLUMNS.map(c=>{
    const base=DISPLAY_TO_BASE[c];
    let v=(base?(r[base]??""):"").toString().replace(/\t/g," ");
    if(textCols.has(c)&&v&&/^[0-9]+$/.test(v))v="'"+v;
    return v;
  }).join("\t")).join("\r\n");
  const tsv="\uFEFF"+body;
  try{await navigator.clipboard.writeText(tsv);toast("تم النسخ — بدون عناوين.")}catch(e){
    const ta=document.createElement("textarea");ta.value=tsv;document.body.appendChild(ta);ta.select();document.execCommand("copy");document.body.removeChild(ta);toast("تم النسخ — بدون عناوين.")
  }
}

const SAMPLE=`نوع المشكلة: لا استطيع اكمال الاستمارة بسبب تعليق عند الحفظ
وقت حدوث المشكلة: 1446/09/10 هـ
اسم صاحب المشكلة: نوف الناصر
رقم الهوية: 1234567890
رقم الجهاز: 01234
رقم الجوال: 0558174717
المسح: الخبر
المنطقة: الشرقية`;

function saveState(){
  try{
    const raw=document.getElementById("raw")?.value||"";
    const agent=document.getElementById("agentName")?.value||"";
    const region=document.getElementById("regionDefault")?.value||"";
    const rows=readTable();
    localStorage.setItem("ticketParserState_latest",JSON.stringify({raw,agent,region,rows,theme:document.body.classList.contains('dark')?'dark':'light'}));
  }catch{}
}

function loadState(){
  try{
    const s=localStorage.getItem("ticketParserState_latest");
    if(!s)return false;
    let{raw,agent,region,rows,theme}=JSON.parse(s);
    const rawEl=document.getElementById("raw");if(typeof raw==="string"&&rawEl)rawEl.value=raw;
    const agentEl=document.getElementById("agentName");if(typeof agent==="string"&&agentEl)agentEl.value=agent;
    const regionEl=document.getElementById("regionDefault");if(typeof region==="string"&&regionEl)regionEl.value=region;
    if(theme==='dark')document.body.classList.add('dark');
    updateThemeLabel();
    if(Array.isArray(rows)&&rows.length){buildTable(rows);validateCells();updateBadge(rows.length);setButtonsEnabled(true)}
    return true;
  }catch{return false}
}

function clearAll(){
  const rawEl=document.getElementById("raw");
  const tbody=document.getElementById("tbody");
  const agentEl=document.getElementById("agentName");
  const regionEl=document.getElementById("regionDefault");
  if(rawEl)rawEl.value="";
  if(tbody)tbody.innerHTML="";
  if(agentEl)agentEl.value="";
  if(regionEl)regionEl.value="";
  updateBadge(0);setButtonsEnabled(false);
  document.getElementById("warn").hidden=true;
  try{localStorage.removeItem("ticketParserState_latest")}catch{}
  toast("تم مسح كل البيانات والتخزين.");
}

function mergeDuplicatesRows(rows){
  if(!rows.length)return rows;
  const map=new Map();
  rows.forEach(r=>{
    const key=[r["رقم الهوية"]||"",r["رقم الجهاز"]||"",r["رقم الجوال"]||"",r["وقت حدوث المشكلة"]||"",(r["نوع المشكلة"]||"").slice(0,40)].join("|");
    if(!map.has(key))map.set(key,r);
  });
  return[...map.values()];
}

function updateThemeLabel(){
  const btn=document.getElementById("btn-theme");
  if(!btn)return;
  btn.textContent=document.body.classList.contains("dark")?"☀️ وضع نهار":"🌙 وضع ليلي";
}

function isUnlocked(){try{return localStorage.getItem("ticketParser_unlocked")==="1"}catch{return false}}
function markUnlocked(){try{localStorage.setItem("ticketParser_unlocked","1")}catch{}}
function getLockBtn(){return document.getElementById("lockBtn")||document.getElementById("unlockBtn")||document.querySelector('[data-action="unlock"]')}
function getLockInput(){return document.getElementById("lockPass")||document.querySelector('input[type="password"][name="lock"]')}
function showGate(){
  if(isUnlocked()){hideGate();return}
  const ov=document.getElementById("lockOverlay");
  if(ov){ov.style.display="flex";setTimeout(()=>{const p=getLockInput();if(p)p.focus()},0)}
}
function hideGate(){
  const ov=document.getElementById("lockOverlay");
  if(ov)ov.style.display="none";
}
function tryUnlock(){
  const inp=getLockInput();
  const raw=inp?(inp.value||""):"";
  const normalized=raw.replace(/[٠-٩]/g,d=>arabicDigitsMap[d]).trim();
  if(ACCESS_PASSWORDS.includes(normalized)){markUnlocked();hideGate()}
  else{
    const m=document.getElementById("lockMsg");
    if(m){m.hidden=false;setTimeout(()=>m.hidden=true,1500)}
  }
}
function wireLock(){
  const btn=getLockBtn();
  const inp=getLockInput();
  if(btn)btn.addEventListener("click",tryUnlock);
  if(inp)inp.addEventListener("keydown",e=>{if(e.key==="Enter")tryUnlock()});
}

function init(){
  const parseBtn=document.getElementById("btn-parse");
  const exportBtn=document.getElementById("btn-export");
  const copyBtn=document.getElementById("btn-copy");
  const clearBtn=document.getElementById("btn-clear");
  const themeBtn=document.getElementById("btn-theme");
  const rawEl=document.getElementById("raw");
  const agentEl=document.getElementById("agentName");
  const regionEl=document.getElementById("regionDefault");

  rawEl.placeholder=SAMPLE;

  if(!isUnlocked())showGate();else hideGate();
  wireLock();

  loadState();

  parseBtn.addEventListener("click",()=>{
    const raw=(rawEl.value||"").trim();
    if(!raw){toast("فضلاً الصق/ي تذاكر أولاً.");return}
    const cleaned=normalizeText(fixLabels(raw));
    const agent=agentEl.value||"";
    const defRegion=regionEl.value||"";
    let rows=parseTicketsWithExtras(cleaned,agent,defRegion);
    rows=mergeDuplicatesRows(rows);
    buildTable(rows);validateCells();
    updateBadge(rows.length);setButtonsEnabled(rows.length>0);
    saveState();
    toast(`تم استخراج ${rows.length} تذكرة.`);
  });

  exportBtn.addEventListener("click",exportExcel);
  copyBtn.addEventListener("click",copyToClipboardTSV);
  clearBtn.addEventListener("click",clearAll);

  themeBtn.addEventListener("click",()=>{document.body.classList.toggle("dark");updateThemeLabel();saveState()});

  rawEl.addEventListener("input",saveState);
  agentEl.addEventListener("input",saveState);
  regionEl.addEventListener("change",saveState);

  document.addEventListener("keydown",e=>{
    const ctrl=e.ctrlKey||e.metaKey;
    if(ctrl&&e.key==="Enter"){e.preventDefault();parseBtn.click()}
    else if(ctrl&&e.key.toLowerCase()==="e"){e.preventDefault();exportBtn.click()}
    else if(ctrl&&e.shiftKey&&e.key.toLowerCase()==="c"){e.preventDefault();copyBtn.click()}
    else if(e.key==="Escape"){e.preventDefault();clearAll()}
  });

  setButtonsEnabled(!!document.getElementById("tbody")?.children.length);
  updateThemeLabel();
}
init();