// 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:equatable/equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:wyatt_architecture/wyatt_architecture.dart';
import 'package:wyatt_i18n/src/core/exceptions/exceptions.dart';
import 'package:wyatt_i18n/src/core/utils/parsers/i18n_file_parser.dart';
import 'package:wyatt_i18n/src/core/utils/parsers/locale_parser.dart';

/// {@template i18n_file}
/// Data structure for i18n files.
/// {@endtemplate}
class I18n extends Entity with EquatableMixin {
  /// {@macro i18n_file}
  const I18n({
    required this.unparsedData,
    required this.data,
    this.locale,
  }) : super();

  /// Creates an empty i18n file.
  const I18n.empty() : this(unparsedData: '', data: const {});

  /// The locale of the i18n file.
  final Locale? locale;

  /// The unparsed data of the i18n file.
  final String unparsedData;

  /// The data of the i18n file.
  final Map<String, dynamic> data;

  /// Gets the locale of the i18n file.
  /// If [strict] is true, it will throw an exception if the locale is not
  /// present in the i18n file.
  /// If [strict] is false, it will return the locale of the i18n file if it is
  /// present, otherwise it will return the locale passed to the constructor.
  Locale getLocale({bool strict = false}) {
    if (strict) {
      if (data.containsKey('@@locale')) {
        if (data['@@locale'] is! String) {
          throw NoLocaleException();
        }

        return const LocaleParser().parse(data['@@locale'] as String);
      } else {
        throw NoLocaleException();
      }
    } else {
      if (data.containsKey('@@locale')) {
        if (data['@@locale'] is! String) {
          if (locale != null) {
            return locale!;
          } else {
            throw NoLocaleException();
          }
        }

        return const LocaleParser().parse(data['@@locale'] as String);
      } else {
        if (locale != null) {
          return locale!;
        } else {
          throw NoLocaleException();
        }
      }
    }
  }

  /// Gets the value of a key.
  /// If the key is not present in the i18n file, it will throw a
  /// [KeyNotFoundException].
  /// If the key is present in the i18n file, it will return the value of the
  /// key parsed with ICU message format.
  String get(String key, [Map<String, dynamic>? args]) {
    final arguments = args ?? const <String, dynamic>{};

    /// The parser used to parse the i18n file with ICU message format.
    final I18nFileParser parser =
        I18nFileParser(i18n: this, arguments: arguments);

    if (containsKey(key)) {
      return parser.parse(this[key] as String, key: key);
    } else {
      throw KeyNotFoundException(key, arguments);
    }
  }

  /// Get the translation of the given [key].
  String call(String key, [Map<String, dynamic>? args]) => get(key, args);

  /// Checks if the i18n file contains a key.
  bool containsKey(String key) => data.containsKey(key);

  /// Gets the value of a key.
  dynamic operator [](String key) => data[key];

  /// Checks if the i18n file is empty.
  bool get isEmpty => data.isEmpty && unparsedData.isEmpty && locale == null;

  @override
  List<Object?> get props => [locale, unparsedData, data];
}
