import 'dart:async';
import 'dart:io';

import 'package:http/http.dart' as http;

import 'errors/auth_exception.dart';
import 'errors/network_exception.dart';
import 'errors/not_found_exception.dart';
import 'errors/server_exception.dart';

/// Runs [send] and converts transport-level failures (socket/timeout/client
/// errors) into a [NetworkException] tagged with [label] (e.g. `Talk <uri>`).
/// Passes through whatever [send] produces, including `null` for the base-class
/// request hooks that may skip the call.
Future<http.Response?> sendGuarded(
  String label,
  Future<http.Response>? Function() send,
) async {
  try {
    return await send();
  } on SocketException catch (e) {
    throw NetworkException(technicalDetails: '$label: ${e.message}');
  } on TimeoutException catch (e) {
    throw NetworkException.timeout(technicalDetails: '$label: $e');
  } on http.ClientException catch (e) {
    throw NetworkException(technicalDetails: '$label: ${e.message}');
  }
}

/// Collapses whitespace and caps an HTTP error body at 500 chars so it can be
/// embedded in an [AppException]'s technical details without dumping headers.
String previewBody(String body) {
  final collapsed = body.replaceAll(RegExp(r'\s+'), ' ').trim();
  return collapsed.length > 500 ? '${collapsed.substring(0, 500)}…' : collapsed;
}

/// Builds a `<label> -> HTTP <status>[ body=<preview>]` technical detail line.
String httpErrorDetail(String label, String body, int status) {
  final preview = previewBody(body);
  return preview.isEmpty
      ? '$label -> HTTP $status'
      : '$label -> HTTP $status body=$preview';
}

/// Throws the [AppException] matching a non-2xx HTTP [status], carrying
/// [detail] as technical details: 401/403 map to auth errors, 404 to
/// not-found, everything else to a generic server error.
Never throwForStatus(int status, String detail) {
  if (status == 401) throw AuthException.unauthorized(technicalDetails: detail);
  if (status == 403) throw AuthException.forbidden(technicalDetails: detail);
  if (status == 404) throw NotFoundException(technicalDetails: detail);
  throw ServerException(statusCode: status, technicalDetails: detail);
}
