/// Core types for API communication with CQRS backend
library;

// =============================================================================
// Serializable Interface
// =============================================================================

/// Interface for objects that can be serialized to/from JSON
/// All queries, commands, and DTOs must implement this interface
abstract interface class Serializable {
  /// Convert this object to a JSON-serializable map
  Map<String, Object?> toJson();
}

// =============================================================================
// Result Type - Functional Error Handling
// =============================================================================

/// Represents the result of an API operation that can either succeed or fail
sealed class Result<T> {
  const Result();

  /// The value for successful results
  T get value;

  /// The error for failed results
  ApiErrorInfo get error;

  /// Returns true if this result represents a success
  bool get isSuccess => this is ApiSuccess<T>;

  /// Returns true if this result represents an error
  bool get isError => this is ApiError<T>;

  /// Transform the success value if present
  Result<R> map<R>(R Function(T value) transform) {
    return switch (this) {
      ApiSuccess<T>(value: final v) => ApiSuccess<R>(transform(v)),
      ApiError<T>(: final error) => ApiError<R>(error),
    };
  }

  /// Execute different actions based on result type
  R when<R>({
    required R Function(T value) success,
    required R Function(ApiErrorInfo error) error,
  }) {
    return switch (this) {
      ApiSuccess<T>(value: final v) => success(v),
      ApiError<T>(error: final e) => error(e),
    };
  }
}

/// Represents a successful API result
final class ApiSuccess<T> extends Result<T> {
  @override
  final T value;

  const ApiSuccess(this.value);

  @override
  ApiErrorInfo get error => throw StateError('Cannot get error from success result');

  @override
  String toString() => 'ApiSuccess($value)';

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ApiSuccess<T> &&
          runtimeType == other.runtimeType &&
          value == other.value;

  @override
  int get hashCode => value.hashCode;
}

/// Represents a failed API result
final class ApiError<T> extends Result<T> {
  @override
  final ApiErrorInfo error;

  const ApiError(this.error);

  @override
  T get value => throw StateError('Cannot get value from error result');

  @override
  String toString() => 'ApiError($error)';

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ApiError<T> &&
          runtimeType == other.runtimeType &&
          error == other.error;

  @override
  int get hashCode => error.hashCode;
}

// =============================================================================
// Error Types
// =============================================================================

/// Information about an API error
class ApiErrorInfo {
  final String message;
  final int? statusCode;
  final String? details;
  final ApiErrorType type;

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

  @override
  String toString() =>
    'ApiError(type: $type, message: $message, status: $statusCode${details != null ? ', details: $details' : ''})';

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is ApiErrorInfo &&
          runtimeType == other.runtimeType &&
          message == other.message &&
          statusCode == other.statusCode &&
          details == other.details &&
          type == other.type;

  @override
  int get hashCode => Object.hash(message, statusCode, details, type);
}

/// Types of API errors
enum ApiErrorType {
  /// Network connectivity issues
  network,

  /// HTTP protocol errors (4xx, 5xx)
  http,

  /// JSON parsing/serialization errors
  serialization,

  /// Request timeout
  timeout,

  /// Validation errors from backend
  validation,

  /// Unknown/unexpected errors
  unknown,
}

// =============================================================================
// Paginated Response Types
// =============================================================================

/// Represents a paginated response from the API
class PaginatedResponse<T> {
  final List<T> items;
  final PageInfo pageInfo;

  const PaginatedResponse({
    required this.items,
    required this.pageInfo,
  });

  factory PaginatedResponse.fromJson(
    Map<String, Object?> json,
    T Function(Map<String, Object?>) itemFromJson,
  ) {
    final dataList = json['data'] as List<Object?>? ?? [];
    final items = dataList
        .whereType<Map<String, Object?>>()
        .map(itemFromJson)
        .toList();

    return PaginatedResponse<T>(
      items: items,
      pageInfo: PageInfo.fromJson(json),
    );
  }

  Map<String, Object?> toJson(Map<String, Object?> Function(T) itemToJson) => {
    'data': items.map(itemToJson).toList(),
    'page': pageInfo.page,
    'pageSize': pageInfo.pageSize,
    'totalItems': pageInfo.totalItems,
    'totalPages': pageInfo.totalPages,
  };

  @override
  String toString() => 'PaginatedResponse(items: ${items.length}, pageInfo: $pageInfo)';
}

/// Pagination metadata
class PageInfo {
  final int page;
  final int pageSize;
  final int totalItems;
  final int totalPages;

  const PageInfo({
    required this.page,
    required this.pageSize,
    required this.totalItems,
    required this.totalPages,
  });

  factory PageInfo.fromJson(Map<String, Object?> json) {
    final page = json['page'] as int? ?? 1;
    final pageSize = json['pageSize'] as int? ?? 10;
    final totalItems = json['totalItems'] as int? ?? 0;
    final totalPages = json['totalPages'] as int? ?? 0;

    return PageInfo(
      page: page,
      pageSize: pageSize,
      totalItems: totalItems,
      totalPages: totalPages,
    );
  }

  Map<String, Object?> toJson() => {
    'page': page,
    'pageSize': pageSize,
    'totalItems': totalItems,
    'totalPages': totalPages,
  };

  @override
  String toString() =>
    'PageInfo(page: $page, pageSize: $pageSize, totalItems: $totalItems, totalPages: $totalPages)';
}

/// Filter criteria for paginated queries
class FilterCriteria {
  final String field;
  final FilterOperator operator;
  final Object? value;

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

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

/// Filter operators matching backend CQRS framework
enum FilterOperator {
  equals,
  notEquals,
  contains,
  startsWith,
  endsWith,
  greaterThan,
  greaterThanOrEqual,
  lessThan,
  lessThanOrEqual,
  isNull,
  isNotNull;

  String toServerString() => switch (this) {
    FilterOperator.equals => 'Equal',
    FilterOperator.notEquals => 'NotEqual',
    FilterOperator.contains => 'Contains',
    FilterOperator.startsWith => 'StartsWith',
    FilterOperator.endsWith => 'EndsWith',
    FilterOperator.greaterThan => 'GreaterThan',
    FilterOperator.greaterThanOrEqual => 'GreaterThanOrEqual',
    FilterOperator.lessThan => 'LessThan',
    FilterOperator.lessThanOrEqual => 'LessThanOrEqual',
    FilterOperator.isNull => 'IsNull',
    FilterOperator.isNotNull => 'IsNotNull',
  };
}

/// Sort criteria for paginated queries
class SortCriteria {
  final String field;
  final SortDirection direction;

  const SortCriteria({
    required this.field,
    this.direction = SortDirection.ascending,
  });

  Map<String, Object?> toJson() => {
    'field': field,
    'direction': direction.toServerString(),
  };
}

/// Sort direction
enum SortDirection {
  ascending,
  descending;

  String toServerString() => switch (this) {
    SortDirection.ascending => 'Ascending',
    SortDirection.descending => 'Descending',
  };
}

// =============================================================================
// Query/Command Models (Generated from OpenAPI Schema)
// =============================================================================

/// Health check query (matches backend HealthQuery record)
class HealthQuery implements Serializable {
  const HealthQuery();

  @override
  Map<String, Object?> toJson() => {};

  factory HealthQuery.fromJson(Map<String, Object?> json) => const HealthQuery();

  @override
  String toString() => 'HealthQuery()';

  @override
  bool operator ==(Object other) =>
      identical(this, other) || other is HealthQuery && runtimeType == other.runtimeType;

  @override
  int get hashCode => 0;
}
