import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:intl/intl.dart';
import 'package:sf_localizations/sf_localizations.dart';
import 'package:sf_localizations/src/asset_loaders/root_bundle_asset_loader.dart';
import 'package:sf_localizations/src/asset_loaders/test_asset_loader.dart';
import 'asset_loaders/asset_loader.dart';
import 'package:utils/utils.dart';

class SFLocalizations {
  SFLocalizations._init(this._locale) {
    _instance = this;
  }

  @visibleForTesting
  SFLocalizations.testInit() : _locale = const Locale('es') {
    _instance = this;
  }

  static SFLocalizations? _instance;
  final _translationCache = <String, dynamic>{};
  final Locale _locale;

  Locale get locale => _locale;

  // Static getter for accessing translations without context
  static SFLocalizations get current {
    if (_instance == null) {
      throw Exception('$SFLocalizations not initialized yet');
    }

    return _instance!;
  }

  Future<void> load(AssetLoader assetLoader) async {
    _translationCache.clear();
    try {
      final translations = await assetLoader.load(_locale);
      _translationCache.addAll(translations);
    } catch (e) {
      if (kDebugMode) print('Error loading locale $_locale: $e');
    }
  }

  String tr(String key, [Map<String, dynamic>? args]) => _resolve(key, args);

  String plural(String key, [Map<String, dynamic>? args, int? howMany]) =>
      _resolvePlural(key, args, howMany);

  static SFLocalizations of(BuildContext context) {
    final translator = Localizations.of<SFLocalizations>(
      context,
      SFLocalizations,
    );

    if (translator == null) {
      throw Exception('$SFLocalizations not in the context');
    }

    return translator;
  }

  // Add support for more languages by adding their respective message files
  static const LocalizationsDelegate<SFLocalizations> delegate =
      _SFLocalizationsDelegate();

  String _resolve(String key, [Map<String, dynamic>? args]) {
    if (!_translationCache.containsKey(key)) {
      return key;
    }

    final translationValue = _translationCache[key] as String;

    return _format(translationValue, args);
  }

  String _resolvePlural(
    String key, [
    Map<String, dynamic>? args,
    int? howMany,
  ]) {
    if (!_translationCache.containsKey(key)) {
      return key;
    }

    final translationValue = _translationCache[key]!;

    if (translationValue is! Map) {
      return key;
    }

    if (!translationValue.containsKey('other')) {
      return key;
    }

    if (howMany == null) {
      return _format(translationValue['other'] as String, args);
    }

    return _format(
      Intl.withLocale<String>(
            _locale.languageCode,
            () => Intl.plural(
              howMany,
              zero: _getPluralValue(translationValue, 'zero'),
              one: _getPluralValue(translationValue, 'one'),
              two: _getPluralValue(translationValue, 'two'),
              few: _getPluralValue(translationValue, 'few'),
              many: _getPluralValue(translationValue, 'many'),
              other: translationValue['other'] as String,
            ),
          )
          as String,
      args,
    );
  }

  String _getPluralValue(Map<dynamic, dynamic> translationValue, String key) {
    if (translationValue[key] != null &&
        (translationValue[key] as String).isEmpty) {
      return translationValue['other'] as String;
    }
    return translationValue[key] as String;
  }

  String _format(String value, [Map<String, dynamic>? args]) {
    return (args?.isNotEmpty ?? false) ? _sprintf(value, args!) : value;
  }

  String _sprintf(String format, Map<String, dynamic> values) {
    return format.replaceAllMapped(RegExp(r'\{(\w+)\}'), (match) {
      final key = match.group(1)!;
      if (!values.containsKey(key)) {
        throw ArgumentError('Missing value for placeholder {$key}');
      }
      return values[key].toString();
    });
  }
}

class _SFLocalizationsDelegate extends LocalizationsDelegate<SFLocalizations> {
  const _SFLocalizationsDelegate();

  static const _assetLoader = isTest
      ? TestAssetLoader()
      : RootBundleAssetLoader();

  @override
  bool isSupported(Locale locale) {
    return supportedLanguages.contains(locale.languageCode);
  }

  @override
  Future<SFLocalizations> load(Locale locale) async {
    final localizations = SFLocalizations._init(locale);

    await localizations.load(_assetLoader);

    return localizations;
  }

  @override
  bool shouldReload(_SFLocalizationsDelegate old) => false;
}
