enum LoginChallengeSource { widget, uriQuery, rawSearch, rawHref, missing }

class LoginChallengeResolution {
  final String? value;
  final LoginChallengeSource source;
  final bool uriHasLoginChallenge;
  final bool rawSearchHasLoginChallenge;
  final bool rawHrefHasLoginChallenge;

  const LoginChallengeResolution({
    required this.value,
    required this.source,
    required this.uriHasLoginChallenge,
    required this.rawSearchHasLoginChallenge,
    required this.rawHrefHasLoginChallenge,
  });

  Map<String, Object?> toDiagnostics() {
    return {
      'resolved_value_len': value?.length ?? 0,
      'resolved_source': source.name,
      'uri_has_login_challenge': uriHasLoginChallenge,
      'raw_search_has_login_challenge': rawSearchHasLoginChallenge,
      'raw_href_has_login_challenge': rawHrefHasLoginChallenge,
    };
  }
}

LoginChallengeResolution resolveLoginChallenge({
  String? widgetLoginChallenge,
  required Uri uri,
  String? rawSearch,
  String? rawHref,
}) {
  final widgetValue = _normalizeChallenge(widgetLoginChallenge);
  if (widgetValue != null) {
    return const LoginChallengeResolution(
      value: null,
      source: LoginChallengeSource.widget,
      uriHasLoginChallenge: false,
      rawSearchHasLoginChallenge: false,
      rawHrefHasLoginChallenge: false,
    ).copyWith(value: widgetValue);
  }

  final uriValue = _normalizeChallenge(uri.queryParameters['login_challenge']);
  if (uriValue != null) {
    return const LoginChallengeResolution(
      value: null,
      source: LoginChallengeSource.uriQuery,
      uriHasLoginChallenge: true,
      rawSearchHasLoginChallenge: false,
      rawHrefHasLoginChallenge: false,
    ).copyWith(value: uriValue);
  }

  final rawSearchValue = _normalizeChallenge(
    _extractQueryParamFromRawQuery(rawSearch, 'login_challenge'),
  );
  if (rawSearchValue != null) {
    return const LoginChallengeResolution(
      value: null,
      source: LoginChallengeSource.rawSearch,
      uriHasLoginChallenge: false,
      rawSearchHasLoginChallenge: true,
      rawHrefHasLoginChallenge: false,
    ).copyWith(value: rawSearchValue);
  }

  final rawHrefValue = _normalizeChallenge(
    _extractQueryParamFromRawHref(rawHref, 'login_challenge'),
  );
  if (rawHrefValue != null) {
    return const LoginChallengeResolution(
      value: null,
      source: LoginChallengeSource.rawHref,
      uriHasLoginChallenge: false,
      rawSearchHasLoginChallenge: false,
      rawHrefHasLoginChallenge: true,
    ).copyWith(value: rawHrefValue);
  }

  return const LoginChallengeResolution(
    value: null,
    source: LoginChallengeSource.missing,
    uriHasLoginChallenge: false,
    rawSearchHasLoginChallenge: false,
    rawHrefHasLoginChallenge: false,
  );
}

String? _normalizeChallenge(String? value) {
  final trimmed = value?.trim();
  if (trimmed == null || trimmed.isEmpty) {
    return null;
  }
  return trimmed;
}

String? _extractQueryParamFromRawHref(String? rawHref, String key) {
  final href = rawHref?.trim();
  if (href == null || href.isEmpty) {
    return null;
  }

  final parsed = Uri.tryParse(href);
  final fromParsed = parsed?.queryParameters[key];
  final normalizedParsed = _normalizeChallenge(fromParsed);
  if (normalizedParsed != null) {
    return normalizedParsed;
  }

  final question = href.indexOf('?');
  if (question < 0) {
    return null;
  }
  final hash = href.indexOf('#', question + 1);
  final rawQuery = hash < 0
      ? href.substring(question + 1)
      : href.substring(question + 1, hash);
  return _extractQueryParamFromRawQuery(rawQuery, key);
}

String? _extractQueryParamFromRawQuery(String? rawQuery, String key) {
  final query = rawQuery?.trim();
  if (query == null || query.isEmpty) {
    return null;
  }

  final normalizedQuery = query.startsWith('?') ? query.substring(1) : query;
  if (normalizedQuery.isEmpty) {
    return null;
  }

  try {
    final parsed = Uri.splitQueryString(normalizedQuery);
    final value = _normalizeChallenge(parsed[key]);
    if (value != null) {
      return value;
    }
  } catch (_) {
    // URI 파싱이 실패하면 수동 파싱으로 보완합니다.
  }

  for (final pair in normalizedQuery.split('&')) {
    if (pair.isEmpty) {
      continue;
    }
    final equalIndex = pair.indexOf('=');
    final rawKey = equalIndex < 0 ? pair : pair.substring(0, equalIndex);
    final decodedKey = _decodeQueryComponentSafe(rawKey);
    if (decodedKey != key) {
      continue;
    }
    if (equalIndex < 0) {
      return null;
    }
    final rawValue = pair.substring(equalIndex + 1);
    final decodedValue = _normalizeChallenge(
      _decodeQueryComponentSafe(rawValue),
    );
    if (decodedValue != null) {
      return decodedValue;
    }
  }
  return null;
}

String _decodeQueryComponentSafe(String value) {
  try {
    return Uri.decodeQueryComponent(value);
  } catch (_) {
    return value;
  }
}

extension on LoginChallengeResolution {
  LoginChallengeResolution copyWith({
    String? value,
    LoginChallengeSource? source,
    bool? uriHasLoginChallenge,
    bool? rawSearchHasLoginChallenge,
    bool? rawHrefHasLoginChallenge,
  }) {
    return LoginChallengeResolution(
      value: value ?? this.value,
      source: source ?? this.source,
      uriHasLoginChallenge: uriHasLoginChallenge ?? this.uriHasLoginChallenge,
      rawSearchHasLoginChallenge:
          rawSearchHasLoginChallenge ?? this.rawSearchHasLoginChallenge,
      rawHrefHasLoginChallenge:
          rawHrefHasLoginChallenge ?? this.rawHrefHasLoginChallenge,
    );
  }
}
