/*
 *   Famedly Matrix SDK
 *   Copyright (C) 2020, 2021 Famedly GmbH
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as
 *   published by the Free Software Foundation, either version 3 of the
 *   License, or (at your option) 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 Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see .
 */
const Set validSigils = {'@', '!', '#', '\$', '+'};
const int maxLength = 255;
extension MatrixIdExtension on String {
  List _getParts() {
    final s = substring(1);
    final ix = s.indexOf(':');
    if (ix == -1) {
      return [substring(1)];
    }
    return [s.substring(0, ix), s.substring(ix + 1)];
  }
  bool get isValidMatrixId {
    if (isEmpty) return false;
    if (length > maxLength) return false;
    if (!validSigils.contains(substring(0, 1))) {
      return false;
    }
    // event IDs do not have to have a domain
    if (substring(0, 1) == '\$') {
      return true;
    }
    // all other matrix IDs have to have a domain
    final parts = _getParts();
    // the localpart can be an empty string, e.g. for aliases
    if (parts.length != 2 || parts[1].isEmpty) {
      return false;
    }
    return true;
  }
  String? get sigil => isValidMatrixId ? substring(0, 1) : null;
  String? get localpart => isValidMatrixId ? _getParts().first : null;
  String? get domain => isValidMatrixId ? _getParts().last : null;
  bool equals(String? other) => toLowerCase() == other?.toLowerCase();
  /// Parse a matrix identifier string into a Uri. Primary and secondary identifiers
  /// are stored in pathSegments. The query string is stored as such.
  Uri? _parseIdentifierIntoUri() {
    const matrixUriPrefix = 'matrix:';
    const matrixToPrefix = 'https://matrix.to/#/';
    if (toLowerCase().startsWith(matrixUriPrefix)) {
      final uri = Uri.tryParse(this);
      if (uri == null) return null;
      final pathSegments = uri.pathSegments;
      final identifiers = [];
      for (var i = 0; i < pathSegments.length - 1; i += 2) {
        final thisSigil = {
          'u': '@',
          'roomid': '!',
          'r': '#',
          'e': '\$',
        }[pathSegments[i].toLowerCase()];
        if (thisSigil == null) {
          break;
        }
        identifiers.add(thisSigil + pathSegments[i + 1]);
      }
      return uri.replace(pathSegments: identifiers);
    } else if (toLowerCase().startsWith(matrixToPrefix)) {
      return Uri.tryParse('//' +
          substring(matrixToPrefix.length - 1)
              .replaceAllMapped(
                  RegExp(r'(?<=/)[#!@+][^:]*:|(\?.*$)'),
                  (m) => m[0]!.replaceAllMapped(
                      RegExp(m.group(1) != null ? '' : '[/?]'),
                      (m) => Uri.encodeComponent(m.group(0)!)))
              .replaceAll('#', '%23'));
    } else {
      return Uri(
          pathSegments: RegExp(r'/((?:[#!@+][^:]*:)?[^/?]*)(?:\?.*$)?')
              .allMatches('/$this')
              .map((m) => m[1]!),
          query: RegExp(r'(?:/(?:[#!@+][^:]*:)?[^/?]*)*\?(.*$)')
              .firstMatch('/$this')?[1]);
    }
  }
  /// Separate a matrix identifier string into a primary indentifier, a secondary identifier,
  /// a query string and already parsed `via` parameters. A matrix identifier string
  /// can be an mxid, a matrix.to-url or a matrix-uri.
  MatrixIdentifierStringExtensionResults? parseIdentifierIntoParts() {
    final uri = _parseIdentifierIntoUri();
    if (uri == null) return null;
    final primary = uri.pathSegments.isNotEmpty ? uri.pathSegments[0] : null;
    if (primary == null || !primary.isValidMatrixId) return null;
    final secondary = uri.pathSegments.length > 1 ? uri.pathSegments[1] : null;
    if (secondary != null && !secondary.isValidMatrixId) return null;
    return MatrixIdentifierStringExtensionResults(
      primaryIdentifier: primary,
      secondaryIdentifier: secondary,
      queryString: uri.query.isNotEmpty ? uri.query : null,
      via: (uri.queryParametersAll['via'] ?? []).toSet(),
      action: uri.queryParameters['action'],
    );
  }
}
class MatrixIdentifierStringExtensionResults {
  final String primaryIdentifier;
  final String? secondaryIdentifier;
  final String? queryString;
  final Set via;
  final String? action;
  MatrixIdentifierStringExtensionResults(
      {required this.primaryIdentifier,
      this.secondaryIdentifier,
      this.queryString,
      this.via = const {},
      this.action});
}