abstract interface class Serializable {
  Map<String, Object?> toJson();
}

enum ApiErrorType {
  network,
  timeout,
  validation,
  http,
  unknown,
}

class ApiError {
  final ApiErrorType type;
  final String message;
  final int? statusCode;
  final Map<String, List<String>>? details;
  final Exception? originalException;

  const ApiError({
    required this.type,
    required this.message,
    this.statusCode,
    this.details,
    this.originalException,
  });

  factory ApiError.network(String message) => ApiError(
    type: ApiErrorType.network,
    message: message,
  );

  factory ApiError.timeout() => const ApiError(
    type: ApiErrorType.timeout,
    message: 'Request timeout',
  );

  factory ApiError.validation(String message, Map<String, List<String>>? details) => ApiError(
    type: ApiErrorType.validation,
    message: message,
    details: details,
  );

  factory ApiError.http({
    required int statusCode,
    required String message,
  }) => ApiError(
    type: ApiErrorType.http,
    message: message,
    statusCode: statusCode,
  );

  factory ApiError.unknown(String message, {Exception? exception}) => ApiError(
    type: ApiErrorType.unknown,
    message: message,
    originalException: exception,
  );
}

sealed class Result<T> {
  const Result();

  factory Result.success(T data) => Success<T>(data);

  factory Result.error(ApiError error) => Error<T>(error);

  R when<R>({
    required R Function(T data) success,
    required R Function(ApiError error) onError,
  }) {
    return switch (this) {
      Success<T>(:final data) => success(data),
      Error<T>(:final error) => onError(error),
    };
  }

  R? whenSuccess<R>(R Function(T data) fn) {
    return switch (this) {
      Success<T>(:final data) => fn(data),
      Error<T>() => null,
    };
  }

  R? whenError<R>(R Function(ApiError error) fn) {
    return switch (this) {
      Success<T>() => null,
      Error<T>(:final error) => fn(error),
    };
  }

  bool get isSuccess => this is Success<T>;
  bool get isError => this is Error<T>;

  T? getOrNull() => whenSuccess((data) => data);
  ApiError? getErrorOrNull() => whenError((error) => error);
}

final class Success<T> extends Result<T> {
  final T data;

  const Success(this.data);
}

final class Error<T> extends Result<T> {
  final ApiError error;

  const Error(this.error);
}

class PaginatedResult<T> {
  final List<T> items;
  final int page;
  final int pageSize;
  final int totalCount;

  const PaginatedResult({
    required this.items,
    required this.page,
    required this.pageSize,
    required this.totalCount,
  });

  int get totalPages => (totalCount / pageSize).ceil();
  bool get hasNextPage => page < totalPages;
}

enum FilterOperator {
  equals('eq'),
  notEquals('neq'),
  greaterThan('gt'),
  greaterThanOrEqual('gte'),
  lessThan('lt'),
  lessThanOrEqual('lte'),
  contains('contains'),
  startsWith('startsWith'),
  endsWith('endsWith'),
  in_('in');

  final String operator;
  const FilterOperator(this.operator);
}

class FilterCriteria implements Serializable {
  final String field;
  final FilterOperator operator;
  final Object? value;

  FilterCriteria({
    required this.field,
    required this.operator,
    required this.value,
  });

  @override
  Map<String, Object?> toJson() => {
    'field': field,
    'operator': operator.operator,
    'value': value,
  };
}
