import 'package:alt_client/src/generated/alt/v1/backtest.pb.dart';
import 'package:alt_client/src/generated/alt/v1/common.pb.dart';
import 'package:alt_client/src/generated/alt/v1/market.pb.dart';

/// Transport-level failure kind that is independent of any ALT [ErrorInfo].
///
/// 후속 socket/market/backtest repository가 동일한 transport 실패 분류를
/// 재사용하도록 client core에 둔다. UI 문구는 담지 않고 안정적인 enum만 노출한다.
enum AltTransportKind {
  /// 요청이 응답 기한 안에 완료되지 않았다.
  timeout,

  /// 연결이 끊겨 요청을 보내거나 응답을 받지 못했다.
  disconnected,

  /// 응답 payload를 기대한 message로 디코드하지 못했다.
  decodeFailure,

  /// 전송은 성공했으나 기대하지 않은 응답 형태가 돌아왔다.
  unexpectedResponse,

  /// 분류되지 않은 transport 실패.
  unknown,
}

/// Stable, UI-copy-free client core error mapped from an ALT [ErrorInfo].
///
/// `code`/`message`는 server가 보낸 안정 필드를 그대로 보존하고, 표시 문구
/// 결정은 presentation layer에 맡긴다.
final class AltApiError {
  const AltApiError({required this.code, required this.message});

  /// Maps a generated [ErrorInfo] payload into a stable client core error.
  factory AltApiError.fromErrorInfo(ErrorInfo info) =>
      AltApiError(code: info.code, message: info.message);

  /// Stable error code reported by the ALT API.
  final String code;

  /// Stable, non-localized message reported by the ALT API.
  final String message;

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

  @override
  int get hashCode => Object.hash(code, message);

  @override
  String toString() => 'AltApiError(code: $code, message: $message)';
}

/// Common result envelope that ALT client repositories return for an API call.
///
/// success payload, mapped [AltApiError], transport failure를 한 형태로 묶어
/// 후속 socket state / market repository / backtest repository가 공유한다.
sealed class AltApiResult<T> {
  const AltApiResult();

  /// Successful response carrying the decoded [value].
  const factory AltApiResult.success(T value) = AltApiSuccess<T>;

  /// Response that decoded but carried an ALT [AltApiError].
  const factory AltApiResult.apiError(AltApiError error) = AltApiFailure<T>;

  /// Transport-level failure where no ALT [ErrorInfo] is available.
  const factory AltApiResult.transportFailure(
    AltTransportKind kind, {
    String? detail,
  }) = AltTransportFailure<T>;
}

/// Successful [AltApiResult] holding the decoded value.
final class AltApiSuccess<T> extends AltApiResult<T> {
  const AltApiSuccess(this.value);

  final T value;

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

  @override
  int get hashCode => Object.hash(runtimeType, value);

  @override
  String toString() => 'AltApiSuccess<$T>($value)';
}

/// [AltApiResult] that decoded a response carrying an ALT [AltApiError].
final class AltApiFailure<T> extends AltApiResult<T> {
  const AltApiFailure(this.error);

  final AltApiError error;

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

  @override
  int get hashCode => Object.hash(runtimeType, error);

  @override
  String toString() => 'AltApiFailure<$T>($error)';
}

/// [AltApiResult] for transport-level failures with no ALT [ErrorInfo].
final class AltTransportFailure<T> extends AltApiResult<T> {
  const AltTransportFailure(this.kind, {this.detail});

  /// Stable transport failure classification.
  final AltTransportKind kind;

  /// Optional non-localized diagnostic detail (not for direct UI display).
  final String? detail;

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

  @override
  int get hashCode => Object.hash(runtimeType, kind, detail);

  @override
  String toString() => 'AltTransportFailure<$T>(kind: $kind, detail: $detail)';
}

/// Extracts a populated [ErrorInfo] from any supported ALT response message.
///
/// market/backtest response가 각자 가진 `error` field를 통일해서 읽어, client
/// repository가 success/error 분기를 한 곳에서 처리하게 한다. error가 설정되지
/// 않았거나 지원하지 않는 type이면 `null`을 반환해 success path를 깨지 않는다.
ErrorInfo? altResponseError(Object response) {
  return switch (response) {
    ListInstrumentsResponse() when response.hasError() => response.error,
    ListBarsResponse() when response.hasError() => response.error,
    StartBacktestResponse() when response.hasError() => response.error,
    GetBacktestRunResponse() when response.hasError() => response.error,
    GetBacktestResultResponse() when response.hasError() => response.error,
    ListBacktestRunsResponse() when response.hasError() => response.error,
    GetBacktestRunDetailResponse() when response.hasError() => response.error,
    CompareBacktestRunsResponse() when response.hasError() => response.error,
    _ => null,
  };
}
