import 'dart:async';
import 'dart:convert';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:record/record.dart';
import 'package:shared_preferences/shared_preferences.dart';

import 'audio_source.dart';

void main() {
  runApp(const ConfiVoiceMobileApp());
}

class AppColors {
  static const background = Color(0xFF05080F);
  static const surface = Color(0xFF0B1220);
  static const surfaceRaised = Color(0xFF101B30);
  static const navy = Color(0xFF102A56);
  static const blue = Color(0xFF3B82F6);
  static const blueSoft = Color(0xFF93C5FD);
  static const border = Color(0xFF203354);
  static const text = Color(0xFFF1F5F9);
  static const textMuted = Color(0xFF9FB0C9);
  static const warningBackground = Color(0xFF2B1E10);
  static const warningBorder = Color(0xFF8A5A20);
  static const warningText = Color(0xFFFED7AA);
  static const recordingBackground = Color(0xFF29151D);
  static const recordingBorder = Color(0xFF7F3148);
  static const recording = Color(0xFFF87171);
}

void showCompactSnackBar(
  BuildContext context, {
  required String message,
  IconData? icon,
}) {
  final messenger = ScaffoldMessenger.of(context);
  messenger.clearSnackBars();
  messenger.showSnackBar(
    SnackBar(
      duration: const Duration(milliseconds: 1500),
      behavior: SnackBarBehavior.floating,
      margin: const EdgeInsets.fromLTRB(18, 0, 18, 18),
      elevation: 0,
      backgroundColor: AppColors.navy,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(14)),
      content: Row(
        mainAxisSize: MainAxisSize.min,
        children: [
          if (icon != null) ...[
            Icon(icon, color: AppColors.blueSoft, size: 20),
            const SizedBox(width: 10),
          ],
          Expanded(
            child: Text(
              message,
              style: const TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.w700,
              ),
            ),
          ),
        ],
      ),
    ),
  );
}

class ConfiVoiceMobileApp extends StatelessWidget {
  const ConfiVoiceMobileApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'ConfiVoice',
      theme: ThemeData(
        brightness: Brightness.dark,
        colorScheme: const ColorScheme.dark(
          primary: AppColors.blue,
          onPrimary: Colors.white,
          secondary: AppColors.blueSoft,
          surface: AppColors.surface,
          onSurface: AppColors.text,
          outline: AppColors.border,
          error: AppColors.recording,
        ),
        scaffoldBackgroundColor: AppColors.background,
        appBarTheme: const AppBarTheme(
          centerTitle: false,
          elevation: 0,
          scrolledUnderElevation: 0,
          backgroundColor: AppColors.background,
          foregroundColor: AppColors.text,
        ),
        cardTheme: CardTheme(
          elevation: 0,
          color: AppColors.surface,
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(8),
            side: const BorderSide(color: AppColors.border),
          ),
        ),
        inputDecorationTheme: const InputDecorationTheme(
          filled: true,
          fillColor: AppColors.surfaceRaised,
          border: OutlineInputBorder(
            borderSide: BorderSide(color: AppColors.border),
          ),
          enabledBorder: OutlineInputBorder(
            borderSide: BorderSide(color: AppColors.border),
          ),
          focusedBorder: OutlineInputBorder(
            borderSide: BorderSide(color: AppColors.blue, width: 1.5),
          ),
          labelStyle: TextStyle(color: AppColors.textMuted),
          prefixIconColor: AppColors.blueSoft,
        ),
        filledButtonTheme: FilledButtonThemeData(
          style: FilledButton.styleFrom(
            backgroundColor: AppColors.blue,
            foregroundColor: Colors.white,
            disabledBackgroundColor: AppColors.navy,
            disabledForegroundColor: AppColors.textMuted,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
            ),
            padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
          ),
        ),
        outlinedButtonTheme: OutlinedButtonThemeData(
          style: OutlinedButton.styleFrom(
            foregroundColor: AppColors.blueSoft,
            side: const BorderSide(color: AppColors.border),
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(8),
            ),
            padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 16),
          ),
        ),
        progressIndicatorTheme: const ProgressIndicatorThemeData(
          color: AppColors.blue,
          linearTrackColor: AppColors.navy,
        ),
        snackBarTheme: const SnackBarThemeData(
          backgroundColor: AppColors.surfaceRaised,
          contentTextStyle: TextStyle(color: AppColors.text),
        ),
        useMaterial3: true,
      ),
      home: const AuthGate(),
    );
  }
}

class UserSession {
  const UserSession({
    required this.id,
    required this.fullName,
    required this.username,
  });

  factory UserSession.fromJson(Map<String, dynamic> json) {
    return UserSession(
      id:
          json['id'] is int
              ? json['id'] as int
              : int.parse(json['id'].toString()),
      fullName: json['full_name']?.toString() ?? '',
      username: json['username']?.toString() ?? '',
    );
  }

  final int id;
  final String fullName;
  final String username;

  Map<String, dynamic> toJson() {
    return {'id': id, 'full_name': fullName, 'username': username};
  }
}

class AuthGate extends StatefulWidget {
  const AuthGate({super.key});

  @override
  State<AuthGate> createState() => _AuthGateState();
}

class _AuthGateState extends State<AuthGate> {
  static const _sessionPreferenceKey = 'confivoice_user_session';

  UserSession? _session;
  bool _isLoading = true;

  @override
  void initState() {
    super.initState();
    _loadSession();
  }

  Future<void> _loadSession() async {
    try {
      final preferences = await SharedPreferences.getInstance();
      final sessionJson = preferences.getString(_sessionPreferenceKey);
      if (sessionJson != null && sessionJson.isNotEmpty) {
        _session = UserSession.fromJson(
          (jsonDecode(sessionJson) as Map).cast<String, dynamic>(),
        );
      }
    } catch (_) {
      _session = null;
    }

    if (mounted) {
      setState(() {
        _isLoading = false;
      });
    }
  }

  Future<void> _setSession(UserSession session) async {
    final preferences = await SharedPreferences.getInstance();
    await preferences.setString(
      _sessionPreferenceKey,
      jsonEncode(session.toJson()),
    );
    if (mounted) {
      setState(() {
        _session = session;
      });
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) {
          showCompactSnackBar(
            context,
            message: 'Berhasil masuk, ${session.fullName}',
            icon: Icons.check_circle_outline,
          );
        }
      });
    }
  }

  Future<void> _logout() async {
    final preferences = await SharedPreferences.getInstance();
    await preferences.remove(_sessionPreferenceKey);
    if (mounted) {
      setState(() {
        _session = null;
      });
      WidgetsBinding.instance.addPostFrameCallback((_) {
        if (mounted) {
          showCompactSnackBar(context, message: 'Berhasil logout');
        }
      });
    }
  }

  @override
  Widget build(BuildContext context) {
    if (_isLoading) {
      return const Scaffold(body: Center(child: CircularProgressIndicator()));
    }

    final session = _session;
    if (session == null) {
      return AuthPage(onAuthenticated: _setSession);
    }

    return ConfiVoiceShell(session: session, onLogout: _logout);
  }
}

class AuthPage extends StatefulWidget {
  const AuthPage({super.key, required this.onAuthenticated});

  final ValueChanged<UserSession> onAuthenticated;

  @override
  State<AuthPage> createState() => _AuthPageState();
}

class _AuthPageState extends State<AuthPage> {
  static const _apiEndpointPreferenceKey = 'confivoice_api_endpoint';

  final TextEditingController _apiController = TextEditingController(
    text: 'http://MacBook-Pro-2.local:8000/predict',
  );
  final TextEditingController _fullNameController = TextEditingController();
  final TextEditingController _usernameController = TextEditingController();
  final TextEditingController _passwordController = TextEditingController();
  final TextEditingController _confirmPasswordController =
      TextEditingController();

  bool _isRegister = false;
  bool _isLoading = false;
  bool _obscurePassword = true;
  bool _obscureConfirmPassword = true;
  String? _errorMessage;

  @override
  void initState() {
    super.initState();
    _loadApiEndpoint();
  }

  @override
  void dispose() {
    _apiController.dispose();
    _fullNameController.dispose();
    _usernameController.dispose();
    _passwordController.dispose();
    _confirmPasswordController.dispose();
    super.dispose();
  }

  Future<void> _loadApiEndpoint() async {
    try {
      final preferences = await SharedPreferences.getInstance();
      final endpoint =
          preferences.getString(_apiEndpointPreferenceKey)?.trim() ?? '';
      if (endpoint.isNotEmpty && mounted) {
        setState(() {
          _apiController.text = endpoint;
        });
      }
    } catch (_) {}
  }

  Future<void> _saveApiEndpoint() async {
    final preferences = await SharedPreferences.getInstance();
    await preferences.setString(
      _apiEndpointPreferenceKey,
      _apiController.text.trim(),
    );
  }

  Uri? _authEndpoint(String path) {
    final predictEndpoint = Uri.tryParse(_apiController.text.trim());
    if (predictEndpoint == null ||
        !predictEndpoint.hasScheme ||
        predictEndpoint.host.isEmpty) {
      return null;
    }
    return predictEndpoint.replace(path: path, queryParameters: {});
  }

  Future<void> _submit() async {
    final endpoint = _authEndpoint(_isRegister ? '/register' : '/login');
    if (endpoint == null) {
      setState(() {
        _errorMessage = 'Endpoint API tidak valid.';
      });
      return;
    }

    final username = _usernameController.text.trim();
    final password = _passwordController.text;
    final confirmPassword = _confirmPasswordController.text;
    final fullName = _fullNameController.text.trim();
    if (username.isEmpty ||
        password.isEmpty ||
        (_isRegister && (fullName.isEmpty || confirmPassword.isEmpty))) {
      setState(() {
        _errorMessage = 'Lengkapi data akun terlebih dahulu.';
      });
      return;
    }

    if (_isRegister && password != confirmPassword) {
      setState(() {
        _errorMessage = 'Konfirmasi password belum sama.';
      });
      return;
    }

    setState(() {
      _isLoading = true;
      _errorMessage = null;
    });

    try {
      final body = <String, dynamic>{
        'username': username,
        'password': password,
        if (_isRegister) 'full_name': fullName,
      };
      final response = await http
          .post(
            endpoint,
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode(body),
          )
          .timeout(const Duration(seconds: 30));

      if (response.statusCode < 200 || response.statusCode >= 300) {
        final decoded = jsonDecode(response.body);
        final detail =
            decoded is Map ? decoded['detail']?.toString() : response.body;
        throw Exception(detail ?? response.body);
      }

      await _saveApiEndpoint();
      if (_isRegister) {
        if (!mounted) {
          return;
        }
        final registeredUsername = username;
        setState(() {
          _isRegister = false;
          _fullNameController.clear();
          _usernameController.text = registeredUsername;
          _passwordController.clear();
          _confirmPasswordController.clear();
          _errorMessage = null;
        });
        showCompactSnackBar(
          context,
          message: 'Registrasi berhasil. Silakan login.',
          icon: Icons.check_circle_outline,
        );
        return;
      }

      final decoded = jsonDecode(response.body) as Map<String, dynamic>;
      final user = UserSession.fromJson(
        (decoded['user'] as Map).cast<String, dynamic>(),
      );
      widget.onAuthenticated(user);
    } catch (error) {
      setState(() {
        _errorMessage = 'Gagal masuk: $error';
      });
    } finally {
      if (mounted) {
        setState(() {
          _isLoading = false;
        });
      }
    }
  }

  InputDecoration _authFieldDecoration({
    required String label,
    required IconData icon,
    Widget? suffixIcon,
  }) {
    return InputDecoration(
      filled: true,
      fillColor: Colors.white,
      labelText: label,
      labelStyle: const TextStyle(color: Color(0xFF536179)),
      prefixIcon: Icon(icon, color: AppColors.navy, size: 20),
      suffixIcon: suffixIcon,
      contentPadding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
      enabledBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: const BorderSide(color: Color(0xFFD8DFEA)),
      ),
      focusedBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: const BorderSide(color: AppColors.blue, width: 1.5),
      ),
      errorBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: const BorderSide(color: AppColors.recording),
      ),
      focusedErrorBorder: OutlineInputBorder(
        borderRadius: BorderRadius.circular(12),
        borderSide: const BorderSide(color: AppColors.recording),
      ),
    );
  }

  Widget _buildModeSwitch() {
    return Container(
      height: 46,
      padding: const EdgeInsets.all(4),
      decoration: BoxDecoration(
        color: const Color(0xFFEAF2FF),
        borderRadius: BorderRadius.circular(14),
        border: Border.all(color: const Color(0xFFD6E3F7)),
      ),
      child: Stack(
        children: [
          AnimatedAlign(
            duration: const Duration(milliseconds: 260),
            curve: Curves.easeOutCubic,
            alignment:
                _isRegister ? Alignment.centerRight : Alignment.centerLeft,
            child: FractionallySizedBox(
              widthFactor: 0.5,
              child: Container(
                decoration: BoxDecoration(
                  gradient: const LinearGradient(
                    colors: [Color(0xFF0D2A52), Color(0xFF2563EB)],
                  ),
                  borderRadius: BorderRadius.circular(10),
                  boxShadow: [
                    BoxShadow(
                      color: const Color(0xFF102A56).withValues(alpha: 0.22),
                      blurRadius: 12,
                      offset: const Offset(0, 6),
                    ),
                  ],
                ),
              ),
            ),
          ),
          Row(
            children: [
              _ModeButton(
                label: 'Masuk',
                isSelected: !_isRegister,
                onTap: () {
                  setState(() {
                    _isRegister = false;
                    _errorMessage = null;
                    _confirmPasswordController.clear();
                  });
                },
              ),
              _ModeButton(
                label: 'Daftar',
                isSelected: _isRegister,
                onTap: () {
                  setState(() {
                    _isRegister = true;
                    _errorMessage = null;
                  });
                },
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildAuthForm() {
    return SizedBox(
      height: 270,
      child: AnimatedSwitcher(
        duration: const Duration(milliseconds: 260),
        transitionBuilder: (child, animation) {
          final offset = Tween<Offset>(
            begin: Offset(_isRegister ? 0.10 : -0.10, 0),
            end: Offset.zero,
          ).animate(CurvedAnimation(parent: animation, curve: Curves.easeOut));
          return FadeTransition(
            opacity: animation,
            child: SlideTransition(position: offset, child: child),
          );
        },
        child:
            _isRegister
                ? Column(
                  key: const ValueKey('register-form'),
                  children: [
                    TextField(
                      controller: _fullNameController,
                      style: const TextStyle(color: Color(0xFF0F172A)),
                      decoration: _authFieldDecoration(
                        label: 'Nama lengkap',
                        icon: Icons.badge_outlined,
                      ),
                      textInputAction: TextInputAction.next,
                    ),
                    const SizedBox(height: 12),
                    TextField(
                      controller: _usernameController,
                      style: const TextStyle(color: Color(0xFF0F172A)),
                      decoration: _authFieldDecoration(
                        label: 'Username',
                        icon: Icons.person_outline,
                      ),
                      textInputAction: TextInputAction.next,
                    ),
                    const SizedBox(height: 12),
                    _PasswordField(
                      controller: _passwordController,
                      obscureText: _obscurePassword,
                      label: 'Password',
                      icon: Icons.lock_outline,
                      onToggle: () {
                        setState(() {
                          _obscurePassword = !_obscurePassword;
                        });
                      },
                      onSubmitted: (_) => _submit(),
                      decorationBuilder: _authFieldDecoration,
                    ),
                    const SizedBox(height: 12),
                    _PasswordField(
                      controller: _confirmPasswordController,
                      obscureText: _obscureConfirmPassword,
                      label: 'Konfirmasi password',
                      icon: Icons.lock_reset_outlined,
                      onToggle: () {
                        setState(() {
                          _obscureConfirmPassword = !_obscureConfirmPassword;
                        });
                      },
                      onSubmitted: (_) => _submit(),
                      decorationBuilder: _authFieldDecoration,
                    ),
                  ],
                )
                : Column(
                  key: const ValueKey('login-form'),
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    TextField(
                      controller: _usernameController,
                      style: const TextStyle(color: Color(0xFF0F172A)),
                      decoration: _authFieldDecoration(
                        label: 'Username',
                        icon: Icons.person_outline,
                      ),
                      textInputAction: TextInputAction.next,
                    ),
                    const SizedBox(height: 12),
                    _PasswordField(
                      controller: _passwordController,
                      obscureText: _obscurePassword,
                      label: 'Password',
                      icon: Icons.lock_outline,
                      onToggle: () {
                        setState(() {
                          _obscurePassword = !_obscurePassword;
                        });
                      },
                      onSubmitted: (_) => _submit(),
                      decorationBuilder: _authFieldDecoration,
                    ),
                  ],
                ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: const BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [Color(0xFF081A33), Color(0xFF0F3A6D), Color(0xFF5EB2F7)],
          ),
        ),
        child: SafeArea(
          child: Center(
            child: SingleChildScrollView(
              padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 28),
              child: ConstrainedBox(
                constraints: const BoxConstraints(maxWidth: 420),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(
                      Icons.graphic_eq_rounded,
                      color: Colors.white,
                      size: 42,
                    ),
                    const SizedBox(height: 10),
                    const Text(
                      'Confivoice',
                      style: TextStyle(
                        color: Colors.white,
                        fontSize: 30,
                        fontWeight: FontWeight.w800,
                        letterSpacing: 0.2,
                      ),
                    ),
                    const SizedBox(height: 26),
                    Container(
                      padding: const EdgeInsets.all(18),
                      decoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(20),
                        border: Border.all(color: const Color(0xFFE2E8F0)),
                        boxShadow: [
                          BoxShadow(
                            color: Colors.black.withValues(alpha: 0.20),
                            blurRadius: 28,
                            offset: const Offset(0, 18),
                          ),
                        ],
                      ),
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.stretch,
                        children: [
                          _buildModeSwitch(),
                          const SizedBox(height: 22),
                          _buildAuthForm(),
                          const SizedBox(height: 18),
                          SizedBox(
                            height: 50,
                            child: FilledButton(
                              onPressed: _isLoading ? null : _submit,
                              style: FilledButton.styleFrom(
                                backgroundColor: AppColors.navy,
                                foregroundColor: Colors.white,
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(14),
                                ),
                              ),
                              child:
                                  _isLoading
                                      ? const SizedBox.square(
                                        dimension: 20,
                                        child: CircularProgressIndicator(
                                          strokeWidth: 2.2,
                                          color: Colors.white,
                                        ),
                                      )
                                      : Text(
                                        _isRegister ? 'Daftar' : 'Masuk',
                                        style: const TextStyle(
                                          fontSize: 16,
                                          fontWeight: FontWeight.w800,
                                        ),
                                      ),
                            ),
                          ),
                          if (_errorMessage != null) ...[
                            const SizedBox(height: 12),
                            _AuthErrorBox(message: _errorMessage!),
                          ],
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _ModeButton extends StatelessWidget {
  const _ModeButton({
    required this.label,
    required this.isSelected,
    required this.onTap,
  });

  final String label;
  final bool isSelected;
  final VoidCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Expanded(
      child: InkWell(
        borderRadius: BorderRadius.circular(10),
        onTap: onTap,
        child: Center(
          child: AnimatedDefaultTextStyle(
            duration: const Duration(milliseconds: 180),
            style: TextStyle(
              color: isSelected ? Colors.white : const Color(0xFF0F172A),
              fontSize: 16,
              fontWeight: FontWeight.w800,
            ),
            child: Text(label),
          ),
        ),
      ),
    );
  }
}

class _PasswordField extends StatelessWidget {
  const _PasswordField({
    required this.controller,
    required this.obscureText,
    required this.label,
    required this.icon,
    required this.onToggle,
    required this.onSubmitted,
    required this.decorationBuilder,
  });

  final TextEditingController controller;
  final bool obscureText;
  final String label;
  final IconData icon;
  final VoidCallback onToggle;
  final ValueChanged<String> onSubmitted;
  final InputDecoration Function({
    required String label,
    required IconData icon,
    Widget? suffixIcon,
  })
  decorationBuilder;

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      obscureText: obscureText,
      style: const TextStyle(color: Color(0xFF0F172A)),
      decoration: decorationBuilder(
        label: label,
        icon: icon,
        suffixIcon: IconButton(
          tooltip: obscureText ? 'Tampilkan password' : 'Sembunyikan password',
          onPressed: onToggle,
          icon: Icon(
            obscureText
                ? Icons.visibility_outlined
                : Icons.visibility_off_outlined,
            color: AppColors.navy,
          ),
        ),
      ),
      onSubmitted: onSubmitted,
    );
  }
}

class _AuthErrorBox extends StatelessWidget {
  const _AuthErrorBox({required this.message});

  final String message;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: const Color(0xFFFFF7ED),
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFFBBF24)),
      ),
      child: Text(
        message,
        style: const TextStyle(color: Color(0xFF92400E), fontSize: 12),
      ),
    );
  }
}

class ConfiVoiceShell extends StatefulWidget {
  const ConfiVoiceShell({
    super.key,
    required this.session,
    required this.onLogout,
  });

  final UserSession session;
  final Future<void> Function() onLogout;

  @override
  State<ConfiVoiceShell> createState() => _ConfiVoiceShellState();
}

class _ConfiVoiceShellState extends State<ConfiVoiceShell> {
  static const _apiEndpointPreferenceKey = 'confivoice_api_endpoint';
  static const supportedExtensions = [
    'wav',
    'mp3',
    'm4a',
    'ogg',
    'flac',
    'webm',
    'aac',
  ];

  final TextEditingController _apiController = TextEditingController(
    text: 'http://MacBook-Pro-2.local:8000/predict',
  );
  final TextEditingController _studentNameController = TextEditingController();
  final AudioRecorder _recorder = AudioRecorder();
  StreamSubscription<Amplitude>? _amplitudeSubscription;

  SelectedAudio? _selectedAudio;
  PredictionResult? _prediction;
  String? _errorMessage;
  bool _isPredicting = false;
  bool _isSaving = false;
  bool _isRecording = false;
  String _studentGender = '';
  List<String> _studentNames = const [];
  List<double> _liveWavePreview = const [];
  List<double> _wavePreview = const [];

  @override
  void initState() {
    super.initState();
    _initializeShell();
  }

  @override
  void dispose() {
    _amplitudeSubscription?.cancel();
    _apiController.dispose();
    _studentNameController.dispose();
    _recorder.dispose();
    super.dispose();
  }

  Future<void> _loadApiEndpoint() async {
    try {
      final preferences = await SharedPreferences.getInstance();
      final endpoint =
          preferences.getString(_apiEndpointPreferenceKey)?.trim() ?? '';
      if (endpoint.isNotEmpty && mounted) {
        setState(() {
          _apiController.text = endpoint;
        });
      }
    } catch (_) {
      // Gunakan endpoint default jika storage lokal belum dapat dibaca.
    }
  }

  Future<void> _saveApiEndpoint() async {
    final preferences = await SharedPreferences.getInstance();
    await preferences.setString(
      _apiEndpointPreferenceKey,
      _apiController.text.trim(),
    );
  }

  String get _studentNamesPreferenceKey => 'confivoice_student_names_shared';

  Future<void> _initializeShell() async {
    await _loadApiEndpoint();
    await _loadStudentNames();
  }

  Future<void> _loadStudentNames() async {
    try {
      final preferences = await SharedPreferences.getInstance();
      final localNames =
          preferences.getStringList(_studentNamesPreferenceKey) ?? [];
      final savedResultNames = await _loadStudentNamesFromSavedResults();
      final names = _normalizeStudentNames([
        ...localNames,
        ...savedResultNames,
      ]);
      await preferences.setStringList(_studentNamesPreferenceKey, names);
      if (!mounted) {
        return;
      }
      setState(() {
        _studentNames = names;
      });
    } catch (_) {}
  }

  Future<List<String>> _loadStudentNamesFromSavedResults() async {
    try {
      final response = await http
          .get(_apiEndpoint('/predictions'))
          .timeout(const Duration(seconds: 12));

      if (response.statusCode < 200 || response.statusCode >= 300) {
        return const [];
      }

      final decoded = jsonDecode(response.body) as Map<String, dynamic>;
      final data = decoded['data'] as List<dynamic>? ?? [];
      return data
          .map((item) => (item as Map)['student_name']?.toString() ?? '')
          .where((name) => _cleanStudentName(name).isNotEmpty)
          .toList();
    } catch (_) {
      return const [];
    }
  }

  List<String> _normalizeStudentNames(Iterable<String> names) {
    final seen = <String>{};
    final normalized = <String>[];
    for (final name in names) {
      final cleanName = _cleanStudentName(name);
      if (cleanName.isEmpty) {
        continue;
      }
      final key = cleanName.toLowerCase();
      if (seen.add(key)) {
        normalized.add(cleanName);
      }
    }
    normalized.sort((a, b) => a.toLowerCase().compareTo(b.toLowerCase()));
    return normalized;
  }

  String _cleanStudentName(String name) {
    return name
        .trim()
        .split(RegExp(r'\s+'))
        .where((part) => part.isNotEmpty)
        .join(' ');
  }

  Future<void> _rememberStudentName(String name) async {
    final cleanName = _cleanStudentName(name);
    if (cleanName.isEmpty) {
      return;
    }

    final updated = _normalizeStudentNames([..._studentNames, cleanName]);
    final preferences = await SharedPreferences.getInstance();
    await preferences.setStringList(_studentNamesPreferenceKey, updated);
    if (mounted) {
      setState(() {
        _studentNames = updated;
        _studentNameController.text = cleanName;
      });
    }
  }

  Future<void> _pickAudio() async {
    final audio = await pickAudio(supportedExtensions);
    if (audio == null) {
      return;
    }

    final preview = await buildWavePreview(audio);

    setState(() {
      _selectedAudio = audio;
      _wavePreview = preview;
      _prediction = null;
      _errorMessage = null;
    });
  }

  Future<void> _toggleRecording() async {
    if (_isRecording) {
      await _stopRecording();
    } else {
      await _startRecording();
    }
  }

  Future<void> _startRecording() async {
    try {
      final hasPermission = await _recorder.hasPermission();
      if (!hasPermission) {
        setState(() {
          _errorMessage = 'Izin microphone belum diberikan.';
        });
        return;
      }

      final supportsWav = await _recorder.isEncoderSupported(AudioEncoder.wav);
      if (!supportsWav) {
        setState(() {
          _errorMessage = 'Perangkat ini belum mendukung rekaman WAV.';
        });
        return;
      }

      final timestamp = DateTime.now().millisecondsSinceEpoch;
      final recordingPath = recordingFilePath(
        'confivoice_recording_$timestamp.wav',
      );

      await _recorder.start(
        const RecordConfig(
          encoder: AudioEncoder.wav,
          sampleRate: 22050,
          numChannels: 1,
          noiseSuppress: true,
        ),
        path: recordingPath,
      );

      await _amplitudeSubscription?.cancel();
      _amplitudeSubscription = _recorder
          .onAmplitudeChanged(const Duration(milliseconds: 90))
          .listen((amplitude) {
            if (!mounted || !_isRecording) {
              return;
            }
            final normalized = ((amplitude.current + 55) / 55).clamp(0.04, 1.0);
            final nextValues = [..._liveWavePreview, normalized.toDouble()];
            setState(() {
              _liveWavePreview =
                  nextValues.length > 48
                      ? nextValues.sublist(nextValues.length - 48)
                      : nextValues;
            });
          });

      setState(() {
        _isRecording = true;
        _selectedAudio = null;
        _liveWavePreview = List<double>.filled(24, 0.06);
        _wavePreview = const [];
        _prediction = null;
        _errorMessage = null;
      });
    } catch (error) {
      setState(() {
        _isRecording = false;
        _errorMessage = 'Gagal mulai rekam: $error';
      });
    }
  }

  Future<void> _stopRecording() async {
    try {
      await _amplitudeSubscription?.cancel();
      _amplitudeSubscription = null;
      final path = await _recorder.stop();
      if (path == null) {
        setState(() {
          _isRecording = false;
          _liveWavePreview = const [];
          _errorMessage = 'Rekaman gagal disimpan.';
        });
        return;
      }

      final timestamp = DateTime.now().millisecondsSinceEpoch;
      final audio = await audioFromRecorderPath(
        path,
        'confivoice_recording_$timestamp.wav',
      );
      if (audio == null) {
        setState(() {
          _isRecording = false;
          _errorMessage = 'Rekaman gagal dibaca.';
        });
        return;
      }
      final preview = await buildWavePreview(audio);

      setState(() {
        _isRecording = false;
        _selectedAudio = audio;
        _liveWavePreview = const [];
        _wavePreview = preview;
        _prediction = null;
        _errorMessage = null;
      });
    } catch (error) {
      setState(() {
        _isRecording = false;
        _liveWavePreview = const [];
        _errorMessage = 'Gagal berhenti rekam: $error';
      });
    }
  }

  Future<void> _predictAudio() async {
    final audio = _selectedAudio;
    if (audio == null) {
      setState(() {
        _errorMessage = 'Pilih audio terlebih dahulu.';
      });
      return;
    }

    final studentName = _studentNameController.text.trim();
    if (studentName.isEmpty) {
      setState(() {
        _errorMessage = 'Isi nama siswa/i terlebih dahulu.';
      });
      return;
    }
    if (_studentGender.isEmpty) {
      setState(() {
        _errorMessage = 'Pilih jenis kelamin siswa/i terlebih dahulu.';
      });
      return;
    }

    final endpoint = Uri.tryParse(_apiController.text.trim());
    if (endpoint == null || !endpoint.hasScheme || endpoint.host.isEmpty) {
      setState(() {
        _errorMessage = 'Endpoint API tidak valid.';
      });
      return;
    }

    setState(() {
      _isPredicting = true;
      _errorMessage = null;
    });

    try {
      final request = http.MultipartRequest('POST', endpoint);
      request.fields['student_name'] = studentName;
      request.fields['student_gender'] = _studentGender;
      request.files.add(await multipartFileFromAudio('file', audio));

      final response = await request.send().timeout(
        const Duration(seconds: 60),
      );
      final responseBody = await response.stream.bytesToString();

      if (response.statusCode < 200 || response.statusCode >= 300) {
        throw Exception('API error ${response.statusCode}: $responseBody');
      }

      final decoded = jsonDecode(responseBody) as Map<String, dynamic>;
      final result = PredictionResult.fromJson(decoded);
      await _saveApiEndpoint();

      setState(() {
        _prediction = result;
      });
    } catch (error) {
      setState(() {
        final endpointText = _apiController.text.trim();
        if (endpointText.contains('10.0.2.2')) {
          _errorMessage =
              'Gagal prediksi: endpoint 10.0.2.2 hanya untuk emulator Android. '
              'Jika memakai HP fisik, buka ikon pengaturan lalu ganti URL prediksi '
              'ke alamat IP komputer yang menjalankan API, misalnya '
              'http://192.168.1.6:8000/predict.';
        } else {
          _errorMessage =
              'Gagal prediksi: tidak bisa terhubung ke API. Pastikan API berjalan, '
              'HP dan komputer berada di WiFi yang sama, lalu cek URL di pengaturan. Detail: $error';
        }
      });
    } finally {
      if (mounted) {
        setState(() {
          _isPredicting = false;
        });
      }
    }
  }

  Future<void> _savePrediction() async {
    final prediction = _prediction;
    if (prediction == null || !prediction.isValidAudio) {
      setState(() {
        _errorMessage = 'Belum ada hasil analisis yang bisa disimpan.';
      });
      return;
    }

    final predictEndpoint = Uri.tryParse(_apiController.text.trim());
    if (predictEndpoint == null ||
        !predictEndpoint.hasScheme ||
        predictEndpoint.host.isEmpty) {
      setState(() {
        _errorMessage = 'Endpoint API tidak valid.';
      });
      return;
    }

    final saveEndpoint = _apiEndpoint('/predictions');

    setState(() {
      _isSaving = true;
      _errorMessage = null;
    });

    try {
      var response = await http
          .post(
            saveEndpoint,
            headers: {'Content-Type': 'application/json'},
            body: jsonEncode(
              prediction.toSaveJson(widget.session.id, _studentGender),
            ),
          )
          .timeout(const Duration(seconds: 30));

      if (response.statusCode == 405) {
        response = await http
            .post(
              _apiEndpoint('/save'),
              headers: {'Content-Type': 'application/json'},
              body: jsonEncode(
                prediction.toSaveJson(widget.session.id, _studentGender),
              ),
            )
            .timeout(const Duration(seconds: 30));
      }

      if (response.statusCode < 200 || response.statusCode >= 300) {
        throw Exception('API error ${response.statusCode}: ${response.body}');
      }

      if (!mounted) {
        return;
      }

      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Hasil analisis berhasil disimpan.')),
      );

      await _rememberStudentName(prediction.studentName);
      _studentNameController.clear();
      setState(() {
        _studentGender = '';
        _selectedAudio = null;
        _liveWavePreview = const [];
        _wavePreview = const [];
        _prediction = null;
      });
    } catch (error) {
      setState(() {
        _errorMessage = 'Gagal menyimpan hasil analisis: $error';
      });
    } finally {
      if (mounted) {
        setState(() {
          _isSaving = false;
        });
      }
    }
  }

  Uri _apiEndpoint(String path, {Map<String, String>? queryParameters}) {
    return Uri.parse(
      _apiController.text.trim(),
    ).replace(path: path, queryParameters: queryParameters);
  }

  void _showSavedAnalyses() {
    Navigator.push(
      context,
      MaterialPageRoute<void>(
        builder:
            (context) => SavedAnalysesPage(
              endpoint: _apiEndpoint(
                '/predictions',
                queryParameters: {'limit': '1000'},
              ),
            ),
      ),
    );
  }

  Future<void> _confirmLogout() async {
    final shouldLogout = await showDialog<bool>(
      context: context,
      builder:
          (context) => AlertDialog(
            backgroundColor: AppColors.surface,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(14),
              side: const BorderSide(color: AppColors.border),
            ),
            title: const Text('Logout?'),
            content: const Text('Sesi akun akan ditutup dari aplikasi ini.'),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context, false),
                child: const Text('Batal'),
              ),
              FilledButton.icon(
                onPressed: () => Navigator.pop(context, true),
                icon: const Icon(Icons.logout, size: 15),
                label: const Text('Logout'),
              ),
            ],
          ),
    );

    if (shouldLogout == true) {
      await widget.onLogout();
    }
  }

  @override
  Widget build(BuildContext context) {
    final displayUserName =
        widget.session.fullName.trim().isEmpty
            ? widget.session.username
            : widget.session.fullName.trim();

    return Scaffold(
      appBar: AppBar(
        title: const Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(Icons.graphic_eq, color: AppColors.blueSoft),
            SizedBox(width: 8),
            Text('ConfiVoice'),
          ],
        ),
        actions: [
          IconButton(
            tooltip: 'Lihat analisis',
            onPressed: _showSavedAnalyses,
            icon: const Icon(Icons.history_outlined),
          ),
          IconButton(
            tooltip: 'Pengaturan API',
            onPressed: _showApiSettings,
            icon: const Icon(Icons.settings_outlined),
          ),
          Padding(
            padding: const EdgeInsets.only(right: 12),
            child: Center(
              child: ConstrainedBox(
                constraints: const BoxConstraints(maxWidth: 92),
                child: Text(
                  displayUserName,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                  style: Theme.of(context).textTheme.bodyMedium?.copyWith(
                    color: AppColors.text,
                    fontWeight: FontWeight.w700,
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
      body: SafeArea(
        child: _PredictionPage(
          studentNameController: _studentNameController,
          studentNames: _studentNames,
          studentGender: _studentGender,
          selectedAudio: _selectedAudio,
          liveWavePreview: _liveWavePreview,
          wavePreview: _wavePreview,
          prediction: _prediction,
          errorMessage: _errorMessage,
          isPredicting: _isPredicting,
          isSaving: _isSaving,
          isRecording: _isRecording,
          onAddStudentName: _rememberStudentName,
          onGenderChanged: (value) {
            setState(() {
              _studentGender = value ?? '';
              _prediction = null;
              _errorMessage = null;
            });
          },
          onPickAudio: _pickAudio,
          onToggleRecording: _toggleRecording,
          onPredict: _predictAudio,
          onSave: _savePrediction,
        ),
      ),
    );
  }

  Future<void> _showApiSettings() async {
    await showDialog<void>(
      context: context,
      builder:
          (context) => AlertDialog(
            backgroundColor: AppColors.surface,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(14),
              side: const BorderSide(color: AppColors.border),
            ),
            title: const Text('Pengaturan'),
            content: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                TextField(
                  controller: _apiController,
                  decoration: const InputDecoration(
                    border: OutlineInputBorder(),
                    labelText: 'URL prediksi',
                  ),
                  keyboardType: TextInputType.url,
                ),
                const SizedBox(height: 12),
                OutlinedButton(
                  onPressed: () {
                    Navigator.pop(context);
                    _confirmLogout();
                  },
                  child: const Text('Logout'),
                ),
              ],
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context),
                child: const Text('Batal'),
              ),
              FilledButton.icon(
                onPressed: () async {
                  await _saveApiEndpoint();
                  if (context.mounted) {
                    Navigator.pop(context);
                  }
                },
                icon: const Icon(Icons.save_outlined),
                label: const Text('Simpan'),
              ),
            ],
          ),
    );
  }
}

class SavedAnalysesPage extends StatefulWidget {
  const SavedAnalysesPage({super.key, required this.endpoint});

  final Uri endpoint;

  @override
  State<SavedAnalysesPage> createState() => _SavedAnalysesPageState();
}

class _SavedAnalysesPageState extends State<SavedAnalysesPage> {
  late Future<List<SavedAnalysis>> _analyses;

  @override
  void initState() {
    super.initState();
    _analyses = _loadAnalyses();
  }

  Future<List<SavedAnalysis>> _loadAnalyses() async {
    final response = await http
        .get(widget.endpoint)
        .timeout(const Duration(seconds: 30));

    if (response.statusCode < 200 || response.statusCode >= 300) {
      throw Exception('API error ${response.statusCode}: ${response.body}');
    }

    final decoded = jsonDecode(response.body) as Map<String, dynamic>;
    final data = decoded['data'] as List<dynamic>? ?? [];
    return data
        .map(
          (item) =>
              SavedAnalysis.fromJson((item as Map).cast<String, dynamic>()),
        )
        .toList();
  }

  Future<void> _refresh() async {
    setState(() {
      _analyses = _loadAnalyses();
    });
    await _analyses;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Lihat Analisis')),
      body: FutureBuilder<List<SavedAnalysis>>(
        future: _analyses,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }

          if (snapshot.hasError) {
            return Center(
              child: Padding(
                padding: const EdgeInsets.all(24),
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    const Icon(Icons.error_outline, size: 40),
                    const SizedBox(height: 12),
                    Text(
                      'Gagal memuat data analisis.\n${snapshot.error}',
                      textAlign: TextAlign.center,
                    ),
                    const SizedBox(height: 12),
                    FilledButton.icon(
                      onPressed: _refresh,
                      icon: const Icon(Icons.refresh),
                      label: const Text('Coba Lagi'),
                    ),
                  ],
                ),
              ),
            );
          }

          final analyses = snapshot.data ?? [];
          if (analyses.isEmpty) {
            return const Center(child: Text('Belum ada analisis tersimpan.'));
          }
          final groups = StudentAnalysisGroup.fromAnalyses(analyses);

          return RefreshIndicator(
            onRefresh: _refresh,
            child: ListView.separated(
              padding: const EdgeInsets.all(16),
              itemCount: groups.length,
              separatorBuilder: (_, __) => const SizedBox(height: 10),
              itemBuilder: (context, index) {
                final group = groups[index];
                return Card(
                  child: ListTile(
                    leading: CircleAvatar(
                      backgroundColor: AppColors.navy,
                      foregroundColor: AppColors.blueSoft,
                      child: const Icon(Icons.folder_outlined),
                    ),
                    title: Text(group.studentName),
                    subtitle: Text(
                      '${group.latest.genderLabel} | ${group.count} hasil analisis\nRata-rata PD: ${group.averagePdPercent}%',
                    ),
                    isThreeLine: true,
                    trailing: const Icon(Icons.chevron_right),
                    onTap: () {
                      Navigator.push(
                        context,
                        MaterialPageRoute<void>(
                          builder:
                              (context) =>
                                  StudentAnalysisDetailPage(group: group),
                        ),
                      );
                    },
                  ),
                );
              },
            ),
          );
        },
      ),
    );
  }
}

class StudentAnalysisGroup {
  const StudentAnalysisGroup({
    required this.studentName,
    required this.analyses,
  });

  final String studentName;
  final List<SavedAnalysis> analyses;

  SavedAnalysis get latest => analyses.first;
  int get count => analyses.length;
  String get averagePdPercent {
    if (analyses.isEmpty) {
      return '0.0';
    }
    final average =
        analyses.fold<double>(
          0,
          (total, analysis) => total + analysis.probabilityPd,
        ) /
        analyses.length;
    return (average * 100).toStringAsFixed(1);
  }

  static List<StudentAnalysisGroup> fromAnalyses(List<SavedAnalysis> analyses) {
    final grouped = <String, List<SavedAnalysis>>{};
    final displayNames = <String, String>{};

    for (final analysis in analyses) {
      final name =
          analysis.studentName.trim().isEmpty
              ? '-'
              : analysis.studentName.trim();
      final key = name.toLowerCase();
      displayNames.putIfAbsent(key, () => name);
      grouped.putIfAbsent(key, () => []).add(analysis);
    }

    final groups =
        grouped.entries
            .map(
              (entry) => StudentAnalysisGroup(
                studentName: displayNames[entry.key] ?? entry.key,
                analyses: entry.value,
              ),
            )
            .toList();
    groups.sort(
      (a, b) =>
          a.studentName.toLowerCase().compareTo(b.studentName.toLowerCase()),
    );
    return groups;
  }
}

class StudentAnalysisDetailPage extends StatelessWidget {
  const StudentAnalysisDetailPage({super.key, required this.group});

  final StudentAnalysisGroup group;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(group.studentName)),
      body: ListView.separated(
        padding: const EdgeInsets.all(16),
        itemCount: group.analyses.length,
        separatorBuilder: (_, __) => const SizedBox(height: 10),
        itemBuilder: (context, index) {
          final analysis = group.analyses[index];
          return Card(
            child: ListTile(
              leading: CircleAvatar(
                backgroundColor: AppColors.navy,
                foregroundColor: AppColors.blueSoft,
                child: Text(analysis.label),
              ),
              title: Text('Prediksi ${index + 1}'),
              subtitle: Text(
                '${analysis.genderLabel} | ${analysis.description}\nPD ${analysis.pdPercent}%  |  TPD ${analysis.tpdPercent}%',
              ),
              isThreeLine: true,
              trailing: Text('${analysis.confidencePercent}%'),
            ),
          );
        },
      ),
    );
  }
}

class SavedAnalysis {
  const SavedAnalysis({
    required this.studentName,
    required this.studentGender,
    required this.label,
    required this.description,
    required this.confidence,
    required this.probabilityPd,
    required this.probabilityTpd,
  });

  factory SavedAnalysis.fromJson(Map<String, dynamic> json) {
    return SavedAnalysis(
      studentName: json['student_name']?.toString() ?? '-',
      studentGender: json['student_gender']?.toString() ?? '',
      label: json['predicted_label']?.toString() ?? '-',
      description: json['description']?.toString() ?? '-',
      confidence: PredictionResult._asDouble(json['confidence']),
      probabilityPd: PredictionResult._asDouble(json['probability_pd']),
      probabilityTpd: PredictionResult._asDouble(json['probability_tpd']),
    );
  }

  final String studentName;
  final String studentGender;
  final String label;
  final String description;
  final double confidence;
  final double probabilityPd;
  final double probabilityTpd;

  String get confidencePercent => (confidence * 100).toStringAsFixed(1);
  String get pdPercent => (probabilityPd * 100).toStringAsFixed(1);
  String get tpdPercent => (probabilityTpd * 100).toStringAsFixed(1);
  String get genderLabel =>
      studentGender.isEmpty ? 'Belum diisi' : studentGender;
}

class PredictionResult {
  const PredictionResult({
    required this.id,
    required this.studentName,
    required this.studentGender,
    required this.isValidAudio,
    required this.errorMessage,
    required this.label,
    required this.description,
    required this.confidence,
    required this.probabilityPd,
    required this.probabilityTpd,
    required this.audioQuality,
    required this.explanation,
    required this.rawJson,
  });

  factory PredictionResult.fromJson(Map<String, dynamic> json) {
    final probabilities =
        (json['probabilities'] as Map?)?.cast<String, dynamic>() ?? {};
    final predictedLabel = json['predicted_label']?.toString();
    return PredictionResult(
      id:
          json['prediction_id'] is int
              ? json['prediction_id'] as int
              : int.tryParse(json['prediction_id']?.toString() ?? ''),
      studentName: json['student_name']?.toString() ?? '',
      studentGender: json['student_gender']?.toString() ?? '',
      isValidAudio: json['is_valid_audio'] != false,
      errorMessage: json['error_message']?.toString(),
      label: predictedLabel ?? json['label']?.toString() ?? '-',
      description: json['description']?.toString() ?? '-',
      confidence: _asDouble(json['confidence']),
      probabilityPd: _asDouble(json['probability_pd'] ?? probabilities['PD']),
      probabilityTpd: _asDouble(
        json['probability_tpd'] ?? probabilities['TPD'],
      ),
      audioQuality: AudioQuality.fromJson(
        (json['audio_quality'] as Map?)?.cast<String, dynamic>() ?? {},
      ),
      explanation: json['explanation']?.toString() ?? '',
      rawJson: json,
    );
  }

  Map<String, dynamic> toSaveJson(int userId, String studentGender) {
    return {
      ...rawJson,
      'student_name': studentName,
      'student_gender': studentGender,
      'user_id': userId,
    };
  }

  static double _asDouble(Object? value) {
    if (value is num) {
      return value.toDouble();
    }
    return double.tryParse(value?.toString() ?? '') ?? 0.0;
  }

  final int? id;
  final String studentName;
  final String studentGender;
  final bool isValidAudio;
  final String? errorMessage;
  final String label;
  final String description;
  final double confidence;
  final double probabilityPd;
  final double probabilityTpd;
  final AudioQuality audioQuality;
  final String explanation;
  final Map<String, dynamic> rawJson;

  String get genderLabel =>
      studentGender.isEmpty ? 'Belum diisi' : studentGender;
}

class AudioQuality {
  const AudioQuality({
    required this.isClipped,
    required this.isTooQuiet,
    required this.isTooShort,
  });

  factory AudioQuality.fromJson(Map<String, dynamic> json) {
    return AudioQuality(
      isClipped: json['is_clipped'] == true,
      isTooQuiet: json['is_too_quiet'] == true,
      isTooShort: json['is_too_short'] == true,
    );
  }

  final bool isClipped;
  final bool isTooQuiet;
  final bool isTooShort;
}

class _PredictionPage extends StatelessWidget {
  const _PredictionPage({
    required this.studentNameController,
    required this.studentNames,
    required this.studentGender,
    required this.selectedAudio,
    required this.liveWavePreview,
    required this.wavePreview,
    required this.prediction,
    required this.errorMessage,
    required this.isPredicting,
    required this.isSaving,
    required this.isRecording,
    required this.onAddStudentName,
    required this.onGenderChanged,
    required this.onPickAudio,
    required this.onToggleRecording,
    required this.onPredict,
    required this.onSave,
  });

  final TextEditingController studentNameController;
  final List<String> studentNames;
  final String studentGender;
  final SelectedAudio? selectedAudio;
  final List<double> liveWavePreview;
  final List<double> wavePreview;
  final PredictionResult? prediction;
  final String? errorMessage;
  final bool isPredicting;
  final bool isSaving;
  final bool isRecording;
  final Future<void> Function(String name) onAddStudentName;
  final ValueChanged<String?> onGenderChanged;
  final VoidCallback onPickAudio;
  final VoidCallback onToggleRecording;
  final VoidCallback onPredict;
  final VoidCallback onSave;

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: [
        Row(
          children: [
            Container(
              width: 48,
              height: 48,
              decoration: BoxDecoration(
                color: AppColors.navy,
                borderRadius: BorderRadius.circular(8),
                border: Border.all(color: AppColors.border),
              ),
              child: const Icon(
                Icons.record_voice_over_outlined,
                color: AppColors.blueSoft,
              ),
            ),
            const SizedBox(width: 12),
            Expanded(
              child: Text(
                'Analisis Percaya Diri',
                style: Theme.of(context).textTheme.headlineSmall,
              ),
            ),
          ],
        ),
        const SizedBox(height: 4),
        Text(
          'Isi nama siswa/i, pilih atau rekam audio, lalu lihat hasil analisis.',
          style: Theme.of(context).textTheme.bodyMedium,
        ),
        const SizedBox(height: 16),
        Card(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: _StudentNameSearchField(
              controller: studentNameController,
              names: studentNames,
              onAddName: onAddStudentName,
            ),
          ),
        ),
        const SizedBox(height: 12),
        Card(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: _StudentGenderField(
              value: studentGender,
              onChanged: onGenderChanged,
            ),
          ),
        ),
        const SizedBox(height: 16),
        _AudioCard(
          selectedAudio: selectedAudio,
          liveWavePreview: liveWavePreview,
          wavePreview: wavePreview,
          onPickAudio: onPickAudio,
          onToggleRecording: onToggleRecording,
          isRecording: isRecording,
        ),
        const SizedBox(height: 16),
        FilledButton.icon(
          onPressed: isPredicting || isRecording ? null : onPredict,
          icon:
              isPredicting
                  ? const SizedBox.square(
                    dimension: 18,
                    child: CircularProgressIndicator(strokeWidth: 2),
                  )
                  : const Icon(Icons.analytics_outlined),
          label: Text(isPredicting ? 'Memproses...' : 'Prediksi'),
        ),
        if (errorMessage != null) ...[
          const SizedBox(height: 12),
          _WarningBox(message: errorMessage!),
        ],
        if (prediction != null) ...[
          const SizedBox(height: 16),
          _PredictionResultCard(result: prediction!),
          if (prediction!.isValidAudio) ...[
            const SizedBox(height: 12),
            FilledButton.icon(
              onPressed: isSaving ? null : onSave,
              icon:
                  isSaving
                      ? const SizedBox.square(
                        dimension: 18,
                        child: CircularProgressIndicator(strokeWidth: 2),
                      )
                      : const Icon(Icons.save_outlined),
              label: Text(isSaving ? 'Menyimpan...' : 'Simpan Hasil'),
            ),
          ],
        ],
      ],
    );
  }
}

class _StudentNameSearchField extends StatefulWidget {
  const _StudentNameSearchField({
    required this.controller,
    required this.names,
    required this.onAddName,
  });

  final TextEditingController controller;
  final List<String> names;
  final Future<void> Function(String name) onAddName;

  @override
  State<_StudentNameSearchField> createState() =>
      _StudentNameSearchFieldState();
}

class _StudentGenderField extends StatelessWidget {
  const _StudentGenderField({required this.value, required this.onChanged});

  final String value;
  final ValueChanged<String?> onChanged;

  @override
  Widget build(BuildContext context) {
    return DropdownButtonFormField<String>(
      value: value.isEmpty ? null : value,
      decoration: const InputDecoration(
        border: OutlineInputBorder(),
        prefixIcon: Icon(Icons.wc_outlined),
        labelText: 'Jenis kelamin siswa/i',
      ),
      items: const [
        DropdownMenuItem(value: 'Laki-laki', child: Text('Laki-laki')),
        DropdownMenuItem(value: 'Perempuan', child: Text('Perempuan')),
      ],
      onChanged: onChanged,
    );
  }
}

class _StudentNameSearchFieldState extends State<_StudentNameSearchField> {
  final FocusNode _focusNode = FocusNode();
  String _query = '';

  @override
  void initState() {
    super.initState();
    _query = widget.controller.text;
    _focusNode.addListener(_handleFocusChanged);
  }

  @override
  void dispose() {
    _focusNode
      ..removeListener(_handleFocusChanged)
      ..dispose();
    super.dispose();
  }

  @override
  void didUpdateWidget(covariant _StudentNameSearchField oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.controller != widget.controller) {
      _query = widget.controller.text;
    }
  }

  void _handleFocusChanged() {
    if (mounted) {
      setState(() {});
    }
  }

  String _cleanName(String name) {
    return name
        .trim()
        .split(RegExp(r'\s+'))
        .where((part) => part.isNotEmpty)
        .join(' ');
  }

  List<String> get _matches {
    final cleanQuery = _cleanName(_query).toLowerCase();
    if (cleanQuery.isEmpty) {
      return widget.names.take(5).toList();
    }
    return widget.names
        .where((name) => name.toLowerCase().contains(cleanQuery))
        .take(5)
        .toList();
  }

  bool get _hasExactMatch {
    final cleanQuery = _cleanName(_query);
    return cleanQuery.isNotEmpty &&
        widget.names.any(
          (name) => name.toLowerCase() == cleanQuery.toLowerCase(),
        );
  }

  bool get _shouldShowPanel {
    return _focusNode.hasFocus &&
        (_matches.isNotEmpty || _cleanName(_query).isNotEmpty);
  }

  Future<void> _addCurrentName() async {
    final cleanName = _cleanName(_query);
    if (cleanName.isEmpty) {
      return;
    }
    await widget.onAddName(cleanName);
    widget.controller.text = cleanName;
    widget.controller.selection = TextSelection.collapsed(
      offset: cleanName.length,
    );
    if (mounted) {
      setState(() {
        _query = cleanName;
      });
    }
  }

  void _handleAddNameTap() {
    _addCurrentName();
    _focusNode.unfocus();
  }

  void _selectName(String name) {
    widget.controller.text = name;
    widget.controller.selection = TextSelection.collapsed(offset: name.length);
    setState(() {
      _query = name;
    });
    _focusNode.unfocus();
  }

  @override
  Widget build(BuildContext context) {
    final matches = _matches;
    final cleanQuery = _cleanName(_query);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        TextField(
          controller: widget.controller,
          focusNode: _focusNode,
          decoration: const InputDecoration(
            border: OutlineInputBorder(),
            prefixIcon: Icon(Icons.search),
            labelText: 'Cari atau tambah nama siswa/i',
          ),
          textInputAction: TextInputAction.next,
          onChanged: (value) {
            setState(() {
              _query = value;
            });
          },
        ),
        if (_shouldShowPanel) ...[
          const SizedBox(height: 10),
          Container(
            width: double.infinity,
            decoration: BoxDecoration(
              color: AppColors.surfaceRaised,
              borderRadius: BorderRadius.circular(8),
              border: Border.all(color: AppColors.border),
            ),
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                for (final name in matches)
                  _StudentNameOption(
                    icon: Icons.person_outline,
                    label: name,
                    onSelect: () => _selectName(name),
                  ),
                if (cleanQuery.isNotEmpty && !_hasExactMatch)
                  _StudentNameOption(
                    icon: Icons.add_circle_outline,
                    label: 'Tambah "$cleanQuery"',
                    onSelect: _handleAddNameTap,
                  ),
              ],
            ),
          ),
        ],
      ],
    );
  }
}

class _StudentNameOption extends StatelessWidget {
  const _StudentNameOption({
    required this.icon,
    required this.label,
    required this.onSelect,
  });

  final IconData icon;
  final String label;
  final VoidCallback onSelect;

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: InkWell(
        onTapDown: (_) => onSelect(),
        child: Padding(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
          child: Row(
            children: [
              Icon(icon, color: AppColors.blueSoft, size: 20),
              const SizedBox(width: 12),
              Expanded(
                child: Text(
                  label,
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _AudioCard extends StatelessWidget {
  const _AudioCard({
    required this.selectedAudio,
    required this.liveWavePreview,
    required this.wavePreview,
    required this.onPickAudio,
    required this.onToggleRecording,
    required this.isRecording,
  });

  final SelectedAudio? selectedAudio;
  final List<double> liveWavePreview;
  final List<double> wavePreview;
  final VoidCallback onPickAudio;
  final VoidCallback onToggleRecording;
  final bool isRecording;

  @override
  Widget build(BuildContext context) {
    final file = selectedAudio;
    final displayWave = isRecording ? liveWavePreview : wavePreview;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  Icons.audio_file,
                  color: Theme.of(context).colorScheme.primary,
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: Text(
                    file == null ? 'Belum ada audio' : file.name,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            SizedBox(
              height: 120,
              child: WaveformPreview(values: displayWave, isLive: isRecording),
            ),
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: OutlinedButton.icon(
                    onPressed: isRecording ? null : onPickAudio,
                    icon: const Icon(Icons.folder_open),
                    label: const Text('Pilih Audio'),
                  ),
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: FilledButton.icon(
                    onPressed: onToggleRecording,
                    icon: Icon(
                      isRecording ? Icons.stop : Icons.fiber_manual_record,
                    ),
                    label: Text(isRecording ? 'Stop' : 'Rekam'),
                  ),
                ),
              ],
            ),
            if (isRecording) ...[
              const SizedBox(height: 10),
              const _RecordingBanner(),
            ],
          ],
        ),
      ),
    );
  }
}

class _RecordingBanner extends StatelessWidget {
  const _RecordingBanner();

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: AppColors.recordingBackground,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: AppColors.recordingBorder),
      ),
      child: const Row(
        children: [
          Icon(Icons.mic, color: AppColors.recording),
          SizedBox(width: 10),
          Expanded(
            child: Text('Sedang merekam suara... tekan Stop setelah selesai.'),
          ),
        ],
      ),
    );
  }
}

class _PredictionResultCard extends StatelessWidget {
  const _PredictionResultCard({required this.result});

  final PredictionResult result;

  @override
  Widget build(BuildContext context) {
    if (!result.isValidAudio) {
      return _InvalidAudioCard(result: result);
    }

    final pdConfidencePercent = (result.probabilityPd * 100).toStringAsFixed(2);
    final lowConfidence = result.confidence < 0.60;

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Hasil Prediksi',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            if (result.studentName.isNotEmpty || result.id != null) ...[
              const SizedBox(height: 8),
              Text(
                [
                  if (result.studentName.isNotEmpty) result.studentName,
                  result.genderLabel,
                  if (result.id != null) 'ID #${result.id}',
                ].join(' - '),
                style: Theme.of(context).textTheme.bodyMedium,
              ),
            ],
            const SizedBox(height: 12),
            Row(
              children: [
                Expanded(
                  child: _MetricBox(label: 'Label', value: result.label),
                ),
                const SizedBox(width: 10),
                Expanded(
                  child: _MetricBox(
                    label: 'Confidence',
                    value: '$pdConfidencePercent%',f 
                  ),
                ),
              ],
            ),
            const SizedBox(height: 12),
            Text(
              result.description,
              style: Theme.of(context).textTheme.titleMedium,
            ),
            if (result.explanation.isNotEmpty) ...[
              const SizedBox(height: 8),
              Text(result.explanation),
            ],
            const SizedBox(height: 16),
            _ProbabilityBar(label: 'PD', value: result.probabilityPd),
            const SizedBox(height: 10),
            _ProbabilityBar(label: 'TPD', value: result.probabilityTpd),
            const SizedBox(height: 12),
            _DetailRow(
              label: 'Kualitas audio',
              value: _qualityStatus(result.audioQuality),
            ),
            if (lowConfidence) ...[
              const SizedBox(height: 12),
              const _WarningBox(
                message:
                    'Model belum yakin. Confidence di bawah 60%, coba rekam ulang dengan suara lebih jelas.',
              ),
            ],
          ],
        ),
      ),
    );
  }
}

class _InvalidAudioCard extends StatelessWidget {
  const _InvalidAudioCard({required this.result});

  final PredictionResult result;

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              'Audio Tidak Valid',
              style: Theme.of(context).textTheme.titleLarge,
            ),
            const SizedBox(height: 12),
            _WarningBox(message: result.errorMessage ?? result.explanation),
            const SizedBox(height: 12),
            const Text(
              'Silakan rekam ulang dengan durasi minimal 2 detik, suara jelas, dan volume normal.',
            ),
          ],
        ),
      ),
    );
  }
}

String _qualityStatus(AudioQuality quality) {
  if (quality.isTooShort) {
    return 'Terlalu pendek';
  }
  if (quality.isClipped) {
    return 'Terlalu keras';
  }
  if (quality.isTooQuiet) {
    return 'Terlalu pelan';
  }
  return 'Baik';
}

class _DetailRow extends StatelessWidget {
  const _DetailRow({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 3),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label),
          const SizedBox(width: 16),
          Flexible(
            child: Text(
              value,
              textAlign: TextAlign.end,
              style: const TextStyle(fontWeight: FontWeight.w600),
            ),
          ),
        ],
      ),
    );
  }
}

class _MetricBox extends StatelessWidget {
  const _MetricBox({required this.label, required this.value});

  final String label;
  final String value;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: AppColors.surfaceRaised,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: AppColors.border),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(label, style: Theme.of(context).textTheme.labelMedium),
          const SizedBox(height: 4),
          Text(value, style: Theme.of(context).textTheme.titleMedium),
        ],
      ),
    );
  }
}

class _ProbabilityBar extends StatelessWidget {
  const _ProbabilityBar({required this.label, required this.value});

  final String label;
  final double value;

  @override
  Widget build(BuildContext context) {
    final percent = (value * 100).clamp(0, 100).toStringAsFixed(2);

    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [Text(label), Text('$percent%')],
        ),
        const SizedBox(height: 6),
        LinearProgressIndicator(value: value.clamp(0.0, 1.0), minHeight: 10),
      ],
    );
  }
}

class _WarningBox extends StatelessWidget {
  const _WarningBox({required this.message});

  final String message;

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: AppColors.warningBackground,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(color: AppColors.warningBorder),
      ),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Icon(Icons.warning_amber_rounded, color: AppColors.warningText),
          const SizedBox(width: 10),
          Expanded(
            child: Text(
              message,
              style: const TextStyle(color: AppColors.warningText),
            ),
          ),
        ],
      ),
    );
  }
}

class WaveformPreview extends StatefulWidget {
  const WaveformPreview({super.key, required this.values, this.isLive = false});

  final List<double> values;
  final bool isLive;

  @override
  State<WaveformPreview> createState() => _WaveformPreviewState();
}

class _WaveformPreviewState extends State<WaveformPreview>
    with SingleTickerProviderStateMixin {
  late final AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 950),
    );
    if (widget.isLive) {
      _controller.repeat();
    }
  }

  @override
  void didUpdateWidget(covariant WaveformPreview oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.isLive && !_controller.isAnimating) {
      _controller.repeat();
    } else if (!widget.isLive && _controller.isAnimating) {
      _controller.stop();
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final emptyText =
        widget.isLive
            ? 'Mulai berbicara, volume suara akan bergerak di sini.'
            : 'Preview waveform akan muncul setelah audio dipilih.';

    return Container(
      decoration: BoxDecoration(
        color: AppColors.surfaceRaised,
        borderRadius: BorderRadius.circular(8),
        border: Border.all(
          color:
              widget.isLive
                  ? AppColors.blue.withValues(alpha: 0.45)
                  : AppColors.border,
        ),
      ),
      child:
          widget.isLive
              ? AnimatedBuilder(
                animation: _controller,
                builder: (context, _) {
                  return CustomPaint(
                    painter: _LiveFrequencyPainter(
                      progress: _controller.value,
                      color: AppColors.blueSoft,
                    ),
                  );
                },
              )
              : widget.values.isEmpty
              ? Center(child: Text(emptyText, textAlign: TextAlign.center))
              : CustomPaint(
                painter: _WaveformPainter(
                  values: widget.values,
                  color: Theme.of(context).colorScheme.primary,
                ),
              ),
    );
  }
}

class _WaveformPainter extends CustomPainter {
  const _WaveformPainter({required this.values, required this.color});

  final List<double> values;
  final Color color;

  @override
  void paint(Canvas canvas, Size size) {
    final centerY = size.height / 2;
    final axisPaint =
        Paint()
          ..color = AppColors.border
          ..strokeWidth = 1;
    canvas.drawLine(Offset(0, centerY), Offset(size.width, centerY), axisPaint);

    if (values.isEmpty) {
      return;
    }

    final paint =
        Paint()
          ..color = color
          ..strokeWidth = max(2, size.width / values.length * 0.45)
          ..strokeCap = StrokeCap.round;
    final gap = size.width / values.length;

    for (var i = 0; i < values.length; i++) {
      final x = gap * i + gap / 2;
      final amplitude = values[i].abs() * centerY * 0.9;
      canvas.drawLine(
        Offset(x, centerY - amplitude),
        Offset(x, centerY + amplitude),
        paint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant _WaveformPainter oldDelegate) {
    return oldDelegate.values != values || oldDelegate.color != color;
  }
}

class _LiveFrequencyPainter extends CustomPainter {
  const _LiveFrequencyPainter({required this.progress, required this.color});

  final double progress;
  final Color color;

  @override
  void paint(Canvas canvas, Size size) {
    final centerY = size.height / 2;
    final barCount = 34;
    final gap = size.width / barCount;
    final barWidth = max(3.0, gap * 0.48);
    final phase = progress * pi * 2;

    final backgroundPaint =
        Paint()
          ..color = AppColors.blue.withValues(alpha: 0.10)
          ..strokeWidth = 1;
    canvas.drawLine(
      Offset(0, centerY),
      Offset(size.width, centerY),
      backgroundPaint,
    );

    final glowPaint =
        Paint()
          ..color = color.withValues(alpha: 0.20)
          ..strokeWidth = barWidth * 2.4
          ..strokeCap = StrokeCap.round;
    final barPaint =
        Paint()
          ..shader = LinearGradient(
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
            colors: [
              AppColors.blueSoft.withValues(alpha: 0.95),
              AppColors.blue,
              AppColors.blueSoft.withValues(alpha: 0.95),
            ],
          ).createShader(Rect.fromLTWH(0, 0, size.width, size.height))
          ..strokeWidth = barWidth
          ..strokeCap = StrokeCap.round;

    for (var i = 0; i < barCount; i++) {
      final x = gap * i + gap / 2;
      final waveA = sin(phase + i * 0.45);
      final waveB = sin(phase * 1.7 - i * 0.23);
      final envelope = 0.52 + 0.48 * sin((i / barCount) * pi);
      final heightFactor =
          (0.34 + 0.30 * waveA.abs() + 0.20 * waveB.abs()) * envelope;
      final amplitude = max(8.0, centerY * heightFactor);

      canvas.drawLine(
        Offset(x, centerY - amplitude),
        Offset(x, centerY + amplitude),
        glowPaint,
      );
      canvas.drawLine(
        Offset(x, centerY - amplitude),
        Offset(x, centerY + amplitude),
        barPaint,
      );
    }
  }

  @override
  bool shouldRepaint(covariant _LiveFrequencyPainter oldDelegate) {
    return oldDelegate.progress != progress || oldDelegate.color != color;
  }
}
