// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'package:collection/collection.dart';
import 'package:intl/intl.dart';
import 'package:wyatt_i18n/src/domain/entities/tokens.dart';
import 'package:wyatt_i18n/wyatt_i18n.dart';

/// {@template i18n_file_parser}
/// This class is responsible for parsing the [I18n] and returning the
/// translated string using the [arguments] provided and the [IcuParser].
/// {@endtemplate}
class I18nFileParser extends Parser<String, String> {
  /// {@macro i18n_file_parser}
  const I18nFileParser({
    required this.i18n,
    this.arguments = const {},
  }) : super();

  /// The [I18n] to be parsed.
  final I18n i18n;

  /// The arguments to be used in the translation.
  final Map<String, dynamic> arguments;

  dynamic getArgument(String key) {
    final arg = arguments[key];
    if (arg == null) {
      throw ArgumentsRequiredException(key, 'value');
    }

    return arg;
  }

  String pluralToString(Plural token) {
    final List<Option?> options = token.options;
    final zero =
        options.firstWhereOrNull((o) => o!.name == 'zero' || o.name == '=0');
    final one =
        options.firstWhereOrNull((o) => o!.name == 'one' || o.name == '=1');
    final two =
        options.firstWhereOrNull((o) => o!.name == 'two' || o.name == '=2');
    final few = options.firstWhereOrNull((o) => o!.name == 'few');
    final many = options.firstWhereOrNull((o) => o!.name == 'many');
    final other = options.firstWhereOrNull((o) => o!.name == 'other');
    final zeroStr = zero?.value.map(parsedElementToString).join() ?? '';
    final oneStr = one?.value.map(parsedElementToString).join() ?? '';
    final twoStr = two?.value.map(parsedElementToString).join() ?? '';
    final fewStr = few?.value.map(parsedElementToString).join() ?? '';
    final manyStr = many?.value.map(parsedElementToString).join() ?? '';
    final otherStr = other?.value.map(parsedElementToString).join() ?? '';

    return Intl.plural(
      getArgument(token.value) as num,
      zero: zeroStr,
      two: twoStr,
      one: oneStr,
      few: fewStr,
      other: otherStr,
      many: manyStr,
    );
  }

  String genderToString(Gender token) {
    final List<Option?> options = token.options;
    final other = options.firstWhereOrNull((o) => o!.name == 'other');
    final male = options.firstWhereOrNull((o) => o!.name == 'male');
    final female = options.firstWhereOrNull((o) => o!.name == 'female');
    final otherStr = other?.value.map(parsedElementToString).join() ?? '';
    final maleStr = male?.value.map(parsedElementToString).join() ?? '';
    final femaleStr = female?.value.map(parsedElementToString).join() ?? '';

    return Intl.gender(
      getArgument(token.value) as String,
      other: otherStr,
      female: femaleStr,
      male: maleStr,
    );
  }

  String selectToString(Select token) {
    final Map<Object, String> cases = {
      for (var e in token.options.map(
        (o) => MapEntry(
          o.name,
          o.value.map(parsedElementToString).join(),
        ),
      ))
        e.key: e.value,
    };

    return Intl.select(
      getArgument(token.value) as String,
      cases,
    );
  }

  String parsedElementToString(Token token) {
    switch (token.type) {
      case TokenType.literal:
        return token.value;
      case TokenType.plural:
        return pluralToString(token as Plural);
      case TokenType.gender:
        return genderToString(token as Gender);
      case TokenType.argument:
        return getArgument(token.value).toString();
      case TokenType.select:
        return selectToString(token as Select);
    }
  }

  @override
  String parse(String input, {String? key}) {
    String? result;
    try {
      result = IcuParser().parse(input)?.map(parsedElementToString).join();
      if (result == null) {
        throw MalformedValueException(key ?? '', input);
      }
    } on ArgumentsRequiredException catch (_) {
      rethrow;
    } catch (e) {
      throw MalformedValueException(key ?? '', input);
    }

    return result;
  }
}
