import 'package:dio/dio.dart';

import 'errors/marianumconnect_error.dart';
import 'marianumconnect_api.dart';
import 'marianumconnect_endpoint.dart';

/// Shared base for MarianumConnect API queries. Owns the [dio] client (the
/// shared authenticated singleton by default) and routes calls through [guard]
/// so every query maps a DioException to the app's typed AppExceptions the same
/// way instead of repeating the try/catch. Subclasses with bespoke error or
/// lifecycle handling (own dio, silent failure, custom status mapping) may skip
/// [guard] and still reuse [dio]/[endpoint].
abstract class MarianumConnectQuery {
  final Dio dio;

  MarianumConnectQuery({Dio? dio}) : dio = dio ?? MarianumConnectApi.dio();

  /// Resolves [path] against the active mobile-API base URL.
  String endpoint(String path) => MarianumConnectEndpoint.resolve(path);

  /// Runs [body], converting any DioException into the matching AppException.
  Future<T> guard<T>(Future<T> Function() body) async {
    try {
      return await body();
    } on DioException catch (e) {
      throw mapMarianumConnectError(e);
    }
  }

  /// GETs [path] and parses the JSON object body with [fromJson].
  Future<T> getObject<T>(
    String path,
    T Function(Map<String, dynamic> json) fromJson, {
    Map<String, dynamic>? queryParameters,
  }) => guard(() async {
    final response = await dio.get<Map<String, dynamic>>(
      endpoint(path),
      queryParameters: queryParameters,
    );
    return fromJson(response.data!);
  });

  /// GETs [path] and maps each element of the JSON array body with [fromJson].
  Future<List<T>> getList<T>(
    String path,
    T Function(Map<String, dynamic> json) fromJson, {
    Map<String, dynamic>? queryParameters,
  }) => guard(() async {
    final response = await dio.get<List<dynamic>>(
      endpoint(path),
      queryParameters: queryParameters,
    );
    return response.data!
        .map((e) => fromJson(e as Map<String, dynamic>))
        .toList();
  });

  /// Formats [d] as an ISO `yyyy-MM-dd` day for MarianumConnect query params.
  String isoDate(DateTime d) =>
      '${d.year.toString().padLeft(4, '0')}-${d.month.toString().padLeft(2, '0')}-${d.day.toString().padLeft(2, '0')}';
}
