/// Dart Extension Methods
///
/// Provides useful extension methods for common data types
/// used throughout the app.
library;

import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:intl/intl.dart';

// ============================================================================
// String Extensions
// ============================================================================

extension StringExtensions on String {
  /// Check if string is null or empty
  bool get isNullOrEmpty => trim().isEmpty;

  /// Check if string is not null and not empty
  bool get isNotNullOrEmpty => trim().isNotEmpty;

  /// Capitalize first letter
  String get capitalize {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1)}';
  }

  /// Capitalize each word
  String get capitalizeWords {
    if (isEmpty) return this;
    return split(' ').map((word) => word.capitalize).join(' ');
  }

  /// Convert to title case
  String get titleCase => capitalizeWords;

  /// Remove all whitespace
  String get removeWhitespace => replaceAll(RegExp(r'\s+'), '');

  /// Check if string is a valid email
  bool get isEmail {
    final emailRegex = RegExp(
      r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
    );
    return emailRegex.hasMatch(this);
  }

  /// Check if string is a valid Vietnamese phone number
  bool get isPhoneNumber {
    final phoneRegex = RegExp(r'^(0|\+?84)(3|5|7|8|9)[0-9]{8}$');
    return phoneRegex.hasMatch(replaceAll(RegExp(r'[^\d+]'), ''));
  }

  /// Check if string is numeric
  bool get isNumeric {
    return double.tryParse(this) != null;
  }

  /// Convert string to int (returns null if invalid)
  int? get toIntOrNull => int.tryParse(this);

  /// Convert string to double (returns null if invalid)
  double? get toDoubleOrNull => double.tryParse(this);

  /// Truncate string with ellipsis
  String truncate(int maxLength, {String ellipsis = '...'}) {
    if (length <= maxLength) return this;
    return '${substring(0, maxLength - ellipsis.length)}$ellipsis';
  }

  /// Remove Vietnamese diacritics
  String get removeDiacritics {
    const withDiacritics =
        'àáạảãâầấậẩẫăằắặẳẵèéẹẻẽêềếệểễìíịỉĩòóọỏõôồốộổỗơờớợởỡùúụủũưừứựửữỳýỵỷỹđ';
    const withoutDiacritics =
        'aaaaaaaaaaaaaaaaaeeeeeeeeeeeiiiiioooooooooooooooooouuuuuuuuuuuyyyyyd';

    var result = toLowerCase();
    for (var i = 0; i < withDiacritics.length; i++) {
      result = result.replaceAll(withDiacritics[i], withoutDiacritics[i]);
    }
    return result;
  }

  /// Convert to URL-friendly slug
  String get slugify {
    var slug = removeDiacritics;
    slug = slug.toLowerCase();
    slug = slug.replaceAll(RegExp(r'[^\w\s-]'), '');
    slug = slug.replaceAll(RegExp(r'[-\s]+'), '-');
    return slug;
  }

  /// Mask email (e.g., "j***@example.com")
  String get maskEmail {
    if (!isEmail) return this;
    final parts = split('@');
    final name = parts[0];
    final maskedName = name.length > 2
        ? '${name[0]}${'*' * (name.length - 1)}'
        : name;
    return '$maskedName@${parts[1]}';
  }

  /// Mask phone number (e.g., "0xxx xxx ***")
  String get maskPhone {
    final cleaned = replaceAll(RegExp(r'\D'), '');
    if (cleaned.length < 10) return this;
    return '${cleaned.substring(0, 4)} ${cleaned.substring(4, 7)} ***';
  }
}

// ============================================================================
// DateTime Extensions
// ============================================================================

extension DateTimeExtensions on DateTime {
  /// Check if date is today
  bool get isToday {
    final now = DateTime.now();
    return year == now.year && month == now.month && day == now.day;
  }

  /// Check if date is yesterday
  bool get isYesterday {
    final yesterday = DateTime.now().subtract(const Duration(days: 1));
    return year == yesterday.year &&
        month == yesterday.month &&
        day == yesterday.day;
  }

  /// Check if date is tomorrow
  bool get isTomorrow {
    final tomorrow = DateTime.now().add(const Duration(days: 1));
    return year == tomorrow.year &&
        month == tomorrow.month &&
        day == tomorrow.day;
  }

  /// Check if date is in the past
  bool get isPast => isBefore(DateTime.now());

  /// Check if date is in the future
  bool get isFuture => isAfter(DateTime.now());

  /// Get start of day (00:00:00)
  DateTime get startOfDay => DateTime(year, month, day);

  /// Get end of day (23:59:59)
  DateTime get endOfDay => DateTime(year, month, day, 23, 59, 59, 999);

  /// Get start of month
  DateTime get startOfMonth => DateTime(year, month, 1);

  /// Get end of month
  DateTime get endOfMonth => DateTime(year, month + 1, 0, 23, 59, 59, 999);

  /// Get start of year
  DateTime get startOfYear => DateTime(year, 1, 1);

  /// Get end of year
  DateTime get endOfYear => DateTime(year, 12, 31, 23, 59, 59, 999);

  /// Add days
  DateTime addDays(int days) => add(Duration(days: days));

  /// Subtract days
  DateTime subtractDays(int days) => subtract(Duration(days: days));

  /// Add months
  DateTime addMonths(int months) => DateTime(year, month + months, day);

  /// Subtract months
  DateTime subtractMonths(int months) => DateTime(year, month - months, day);

  /// Add years
  DateTime addYears(int years) => DateTime(year + years, month, day);

  /// Subtract years
  DateTime subtractYears(int years) => DateTime(year - years, month, day);

  /// Get age in years from this date
  int get ageInYears {
    final today = DateTime.now();
    var age = today.year - year;
    if (today.month < month || (today.month == month && today.day < day)) {
      age--;
    }
    return age;
  }

  /// Get difference in days from now
  int get daysFromNow => DateTime.now().difference(this).inDays;

  /// Get difference in hours from now
  int get hoursFromNow => DateTime.now().difference(this).inHours;

  /// Get difference in minutes from now
  int get minutesFromNow => DateTime.now().difference(this).inMinutes;

  /// Copy with new values
  DateTime copyWith({
    int? year,
    int? month,
    int? day,
    int? hour,
    int? minute,
    int? second,
    int? millisecond,
    int? microsecond,
  }) {
    return DateTime(
      year ?? this.year,
      month ?? this.month,
      day ?? this.day,
      hour ?? this.hour,
      minute ?? this.minute,
      second ?? this.second,
      millisecond ?? this.millisecond,
      microsecond ?? this.microsecond,
    );
  }
}

// ============================================================================
// Duration Extensions
// ============================================================================

extension DurationExtensions on Duration {
  /// Format duration as readable string (e.g., "2 giờ 30 phút")
  String get formatted {
    final hours = inHours;
    final minutes = inMinutes.remainder(60);
    final seconds = inSeconds.remainder(60);

    if (hours > 0) {
      if (minutes > 0) {
        return '$hours giờ $minutes phút';
      }
      return '$hours giờ';
    } else if (minutes > 0) {
      if (seconds > 0) {
        return '$minutes phút $seconds giây';
      }
      return '$minutes phút';
    } else {
      return '$seconds giây';
    }
  }

  /// Format as HH:MM:SS
  String get hhmmss {
    final hours = inHours;
    final minutes = inMinutes.remainder(60);
    final seconds = inSeconds.remainder(60);

    return '${hours.toString().padLeft(2, '0')}:'
        '${minutes.toString().padLeft(2, '0')}:'
        '${seconds.toString().padLeft(2, '0')}';
  }

  /// Format as MM:SS
  String get mmss {
    final minutes = inMinutes.remainder(60);
    final seconds = inSeconds.remainder(60);

    return '${minutes.toString().padLeft(2, '0')}:'
        '${seconds.toString().padLeft(2, '0')}';
  }
}

// ============================================================================
// List Extensions
// ============================================================================

extension ListExtensions<T> on List<T> {
  /// Get first element or null if list is empty
  T? get firstOrNull => isEmpty ? null : first;

  /// Get last element or null if list is empty
  T? get lastOrNull => isEmpty ? null : last;

  /// Get element at index or null if out of bounds
  T? elementAtOrNull(int index) {
    if (index < 0 || index >= length) return null;
    return this[index];
  }

  /// Group list by key
  Map<K, List<T>> groupBy<K>(K Function(T) keySelector) {
    final map = <K, List<T>>{};
    for (final element in this) {
      final key = keySelector(element);
      if (!map.containsKey(key)) {
        map[key] = [];
      }
      map[key]!.add(element);
    }
    return map;
  }

  /// Get distinct elements
  List<T> get distinct => toSet().toList();

  /// Get distinct elements by key
  List<T> distinctBy<K>(K Function(T) keySelector) {
    final seen = <K>{};
    return where((element) => seen.add(keySelector(element))).toList();
  }

  /// Chunk list into smaller lists of specified size
  List<List<T>> chunk(int size) {
    final chunks = <List<T>>[];
    for (var i = 0; i < length; i += size) {
      chunks.add(sublist(i, (i + size) > length ? length : (i + size)));
    }
    return chunks;
  }
}

// ============================================================================
// Map Extensions
// ============================================================================

extension MapExtensions<K, V> on Map<K, V> {
  /// Get value or default if key doesn't exist
  V getOrDefault(K key, V defaultValue) {
    return containsKey(key) ? this[key] as V : defaultValue;
  }

  /// Get value or null if key doesn't exist
  V? getOrNull(K key) {
    return containsKey(key) ? this[key] : null;
  }
}

// ============================================================================
// BuildContext Extensions
// ============================================================================

extension BuildContextExtensions on BuildContext {
  /// Get screen size
  Size get screenSize => MediaQuery.of(this).size;

  /// Get screen width
  double get screenWidth => MediaQuery.of(this).size.width;

  /// Get screen height
  double get screenHeight => MediaQuery.of(this).size.height;

  /// Check if screen is small (<600dp)
  bool get isSmallScreen => MediaQuery.of(this).size.width < 600;

  /// Check if screen is medium (600-960dp)
  bool get isMediumScreen {
    final width = MediaQuery.of(this).size.width;
    return width >= 600 && width < 960;
  }

  /// Check if screen is large (>=960dp)
  bool get isLargeScreen => MediaQuery.of(this).size.width >= 960;

  /// Get theme
  ThemeData get theme => Theme.of(this);

  /// Get text theme
  TextTheme get textTheme => Theme.of(this).textTheme;

  /// Get color scheme
  ColorScheme get colorScheme => Theme.of(this).colorScheme;

  /// Get primary color
  Color get primaryColor => Theme.of(this).primaryColor;

  /// Check if dark mode is enabled
  bool get isDarkMode => Theme.of(this).brightness == Brightness.dark;

  /// Get safe area padding
  EdgeInsets get safeAreaPadding => MediaQuery.of(this).padding;

  /// Get bottom safe area padding (for devices with notch)
  double get bottomSafeArea => MediaQuery.of(this).padding.bottom;

  /// Get top safe area padding (for status bar)
  double get topSafeArea => MediaQuery.of(this).padding.top;

  /// Show snackbar
  void showSnackBar(String message, {Duration? duration}) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: duration ?? const Duration(seconds: 3),
      ),
    );
  }

  /// Show error snackbar
  void showErrorSnackBar(String message, {Duration? duration}) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: colorScheme.error,
        duration: duration ?? const Duration(seconds: 4),
      ),
    );
  }

  /// Show success snackbar
  void showSuccessSnackBar(String message, {Duration? duration}) {
    ScaffoldMessenger.of(this).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.green,
        duration: duration ?? const Duration(seconds: 3),
      ),
    );
  }

  /// Hide keyboard
  void hideKeyboard() {
    FocusScope.of(this).unfocus();
  }

  /// Navigate to route
  // Future<T?> push<T>(Widget page) {
  //   return Navigator.of(this).push<T>(MaterialPageRoute(builder: (_) => page));
  // }

  /// Navigate and replace current route
  // Future<T?> pushReplacement<T>(Widget page) {
  //   return Navigator.of(
  //     this,
  //   ).pushReplacement<T, void>(MaterialPageRoute(builder: (_) => page));
  // }

  /// Pop current route
  // void pop<T>([T? result]) {
  //   GoRouter.of(this).pop(result);
  // }

  /// Pop until first route
  // void popUntilFirst() {
  //   Navigator.of(this).popUntil((route) => route.isFirst);
  // }
}

// ============================================================================
// Num Extensions
// ============================================================================

extension NumExtensions on num {
  /// Check if number is positive
  bool get isPositive => this > 0;

  /// Check if number is negative
  bool get isNegative => this < 0;

  /// Check if number is zero
  bool get isZero => this == 0;

  /// Clamp number between min and max
  num clampTo(num min, num max) => clamp(min, max);

  /// Round to specified decimal places
  double roundToDecimal(int places) {
    final mod = math.pow(10.0, places);
    return ((this * mod).round().toDouble() / mod);
  }

  /// Format as Vietnamese currency (đồng)
  /// Returns formatted string like "1.153.434đ"
  String get toVNCurrency {
    final formatter = NumberFormat.currency(
      locale: 'vi_VN',
      symbol: 'đ',
      decimalDigits: 0,
    );
    return formatter.format(this);
  }

  /// Format as Vietnamese currency with custom symbol
  /// Returns formatted string with custom symbol
  String toCurrency({String symbol = 'đ', int decimalDigits = 0}) {
    final formatter = NumberFormat.currency(
      locale: 'vi_VN',
      symbol: symbol,
      decimalDigits: decimalDigits,
    );
    return formatter.format(this);
  }
}
