#!/usr/bin/env python3
"""Pemeriksa statis Dart sederhana untuk proyek SIMAP Wali.
Bukan pengganti `flutter analyze`, tapi menangkap kelas galat yang paling
sering muncul: impor tak ada, kelas tak terdefinisi, pemanggilan metode
ApiService yang tidak ada, dan kurung tidak seimbang."""
import re, os, sys, collections

ROOT = 'lib'
berkas = []
for r, d, fs in os.walk(ROOT):
    for f in fs:
        if f.endswith('.dart'):
            berkas.append(os.path.join(r, f))

isi = {p: open(p).read() for p in berkas}
masalah = collections.defaultdict(list)

def buang_komentar_string(s):
    """String dibuang lebih dulu, baru komentar. Kalau dibalik, teks
    seperti https:// di dalam string dikira awal komentar dan menelan
    tanda kutip penutupnya sehingga seluruh berkas salah dibaca."""
    s = re.sub(r"'''.*?'''", "''", s, flags=re.S)
    s = re.sub(r'""".*?"""', '""', s, flags=re.S)
    s = re.sub(r"'(?:\\.|[^'\\\n])*'", "''", s)
    s = re.sub(r'"(?:\\.|[^"\\\n])*"', '""', s)
    s = re.sub(r"//[^\n]*", "", s)
    s = re.sub(r"/\*.*?\*/", "", s, flags=re.S)
    return s

bersih = {p: buang_komentar_string(t) for p, t in isi.items()}

# ---- 1. impor resolve
for p, t in isi.items():
    for m in re.finditer(r"import\s+'([^']+)'", t):
        target = m.group(1)
        if target.startswith('package:') or target.startswith('dart:'):
            continue
        jalur = os.path.normpath(os.path.join(os.path.dirname(p), target))
        if not os.path.isfile(jalur):
            masalah[p].append(f"impor tidak ditemukan: {target}")

# ---- 2. kelas terdefinisi
kelas_def = set()
for p, t in bersih.items():
    for m in re.finditer(r'\bclass\s+(\w+)', t):
        kelas_def.add(m.group(1))
    for m in re.finditer(r'\benum\s+(\w+)', t):
        kelas_def.add(m.group(1))

# kelas dari paket yang kita pakai (tidak bisa diverifikasi, daftar putih)
BAWAAN = set("""
Widget StatelessWidget StatefulWidget State BuildContext Key MaterialApp Scaffold AppBar Text
Icon Icons Column Row Container Padding EdgeInsets Center SizedBox Expanded Flexible Spacer
ListView ListTile Card Divider CircleAvatar NetworkImage AssetImage Image Colors Color
TextStyle FontWeight TextAlign TextOverflow MainAxisAlignment CrossAxisAlignment MainAxisSize
ThemeData ColorScheme AppBarTheme CardThemeData FilledButtonThemeData OutlinedButtonThemeData
InputDecorationTheme DividerThemeData ChipThemeData NavigationBarThemeData Brightness
FilledButton OutlinedButton TextButton IconButton ElevatedButton ButtonStyle
TextField TextEditingController InputDecoration OutlineInputBorder BorderSide BorderRadius
Radius RoundedRectangleBorder Border BoxDecoration BoxShape LinearProgressIndicator
CircularProgressIndicator AlwaysStoppedAnimation ClipRRect Navigator MaterialPageRoute
FutureBuilder Future ConnectionState RefreshIndicator ScrollController SafeArea
SingleChildScrollView Align Alignment Stack Positioned Wrap Chip PopupMenuButton PopupMenuItem
NavigationBar NavigationDestination BottomNavigationBar Theme ExpansionTile SegmentedButton
ButtonSegment ScaffoldMessenger SnackBar AlertDialog Dialog showDialog showDatePicker
showModalBottomSheet DateTime Duration TimeOfDay Locale DateFormat NumberFormat
File Directory Uri Exception Error String int double bool num List Map Set Iterable Object
WidgetStateProperty MediaQuery WidgetsBinding WidgetsFlutterBinding SharedPreferences
ImagePicker ImageSource XFile Response Client InkWell InputDecorator GestureDetector
Opacity Transform Matrix4 AnimatedContainer Curves Tween Offset Size Rect
FontFeature TextInputType TextInputAction TextCapitalization Characters
CheckboxListTile Clipboard ClipboardData Checkbox Switch SwitchListTile RadioListTile
ListTileControlAffinity FloatingActionButton BottomSheet Card Divider Tooltip
SnackBarAction RefreshIndicator Scrollbar
DefaultTabController Tab TabBar TabBarView TabController IndexedStack PopScope
CustomScrollView SliverAppBar SliverList SliverPadding SliverChildListDelegate
SliverChildBuilderDelegate SliverToBoxAdapter SliverGrid FlexibleSpaceBar
ChoiceChip FilterChip ActionChip InputChip CircleAvatar NetworkImage LinearProgressIndicator
RegExp BoxConstraints IntrinsicHeight Function Uint8List ByteData Completer Timer
StreamBuilder Stream ValueNotifier ValueListenableBuilder Form FormState GlobalKey
Table TableRow TableCell Positioned Hero AnimatedOpacity FadeTransition
""".split())

for p, t in bersih.items():
    for m in re.finditer(r'\b([A-Z][A-Za-z0-9_]*)\s*\(', t):
        nama = m.group(1)
        if nama in kelas_def or nama in BAWAAN:
            continue
        masalah[p].append(f"kelas/konstruktor tak dikenal: {nama}")

# ---- 3. metode ApiService
api = bersih.get('lib/services/api_service.dart', '')
# Tipe kembalian bisa bersarang seperti Future<Map<String, dynamic>>,
# jadi cocokkan apa pun sampai spasi terakhir sebelum nama metode.
metode_api = set(re.findall(r'static\s+[\w<>,?\s]+?\s(\w+)\s*\(', api))
metode_api |= set(re.findall(r'static\s+\w+\s+get\s+(\w+)', api))
metode_api |= set(re.findall(r'static\s+const\s+\w*\s*(\w+)\s*=', api))
for p, t in bersih.items():
    if p.endswith('api_service.dart'):
        continue
    for m in re.finditer(r'ApiService\.(\w+)', t):
        if m.group(1) not in metode_api:
            masalah[p].append(f"ApiService.{m.group(1)} tidak ada")

# ---- 4. kurung seimbang
for p, t in bersih.items():
    for buka, tutup, nama in [('{', '}', 'kurawal'), ('(', ')', 'kurung'), ('[', ']', 'siku')]:
        if t.count(buka) != t.count(tutup):
            masalah[p].append(f"{nama} tidak seimbang: {t.count(buka)} buka, {t.count(tutup)} tutup")

# ---- laporan
total = 0
for p in sorted(masalah):
    uniq = sorted(set(masalah[p]))
    if not uniq: continue
    print(f"### {p}")
    for u in uniq:
        print("   ", u); total += 1
    print()
print(f"Berkas Dart: {len(berkas)} | Metode ApiService: {len(metode_api)} | Temuan: {total}")
