/// Synor IBC SDK Types for Flutter/Dart
///
/// Inter-Blockchain Communication (IBC) protocol types.
library synor_ibc_types;

/// IBC Height representation
class Height {
  final int revisionNumber;
  final int revisionHeight;

  const Height({this.revisionNumber = 0, this.revisionHeight = 1});

  bool get isZero => revisionNumber == 0 && revisionHeight == 0;

  Height increment() => Height(revisionNumber: revisionNumber, revisionHeight: revisionHeight + 1);

  factory Height.fromJson(Map<String, dynamic> json) => Height(
    revisionNumber: json['revision_number'] ?? 0,
    revisionHeight: json['revision_height'] ?? 1,
  );

  Map<String, dynamic> toJson() => {
    'revision_number': revisionNumber,
    'revision_height': revisionHeight,
  };
}

/// Chain identifier
class ChainId {
  final String id;
  const ChainId(this.id);

  factory ChainId.fromJson(Map<String, dynamic> json) => ChainId(json['id']);
  Map<String, dynamic> toJson() => {'id': id};
}

/// IBC Version with features
class Version {
  final String identifier;
  final List<String> features;

  const Version({required this.identifier, required this.features});

  factory Version.defaultConnection() => const Version(
    identifier: '1',
    features: ['ORDER_ORDERED', 'ORDER_UNORDERED'],
  );

  factory Version.fromJson(Map<String, dynamic> json) => Version(
    identifier: json['identifier'],
    features: List<String>.from(json['features']),
  );

  Map<String, dynamic> toJson() => {'identifier': identifier, 'features': features};
}

/// Light client types
enum ClientType { tendermint, soloMachine, localhost, wasm }

/// Light client identifier
class ClientId {
  final String id;
  const ClientId(this.id);

  factory ClientId.fromJson(Map<String, dynamic> json) => ClientId(json['id']);
  Map<String, dynamic> toJson() => {'id': id};
}

/// Trust level configuration
class TrustLevel {
  final int numerator;
  final int denominator;

  const TrustLevel({this.numerator = 1, this.denominator = 3});

  factory TrustLevel.fromJson(Map<String, dynamic> json) => TrustLevel(
    numerator: json['numerator'] ?? 1,
    denominator: json['denominator'] ?? 3,
  );

  Map<String, dynamic> toJson() => {'numerator': numerator, 'denominator': denominator};
}

/// Light client state
class ClientState {
  final String chainId;
  final TrustLevel trustLevel;
  final int trustingPeriod;
  final int unbondingPeriod;
  final int maxClockDrift;
  final Height latestHeight;
  final Height? frozenHeight;

  const ClientState({
    required this.chainId,
    required this.trustLevel,
    required this.trustingPeriod,
    required this.unbondingPeriod,
    required this.maxClockDrift,
    required this.latestHeight,
    this.frozenHeight,
  });

  factory ClientState.fromJson(Map<String, dynamic> json) => ClientState(
    chainId: json['chain_id'],
    trustLevel: TrustLevel.fromJson(json['trust_level']),
    trustingPeriod: json['trusting_period'],
    unbondingPeriod: json['unbonding_period'],
    maxClockDrift: json['max_clock_drift'],
    latestHeight: Height.fromJson(json['latest_height']),
    frozenHeight: json['frozen_height'] != null ? Height.fromJson(json['frozen_height']) : null,
  );

  Map<String, dynamic> toJson() => {
    'chain_id': chainId,
    'trust_level': trustLevel.toJson(),
    'trusting_period': trustingPeriod,
    'unbonding_period': unbondingPeriod,
    'max_clock_drift': maxClockDrift,
    'latest_height': latestHeight.toJson(),
    if (frozenHeight != null) 'frozen_height': frozenHeight!.toJson(),
  };
}

/// Connection state
enum ConnectionState { uninitialized, init, tryopen, open }

/// Connection identifier
class ConnectionId {
  final String id;
  const ConnectionId(this.id);

  factory ConnectionId.newId(int sequence) => ConnectionId('connection-$sequence');
  factory ConnectionId.fromJson(Map<String, dynamic> json) => ConnectionId(json['id']);
  Map<String, dynamic> toJson() => {'id': id};
}

/// Port identifier
class PortId {
  final String id;
  const PortId(this.id);

  factory PortId.transfer() => const PortId('transfer');
  factory PortId.fromJson(Map<String, dynamic> json) => PortId(json['id']);
  Map<String, dynamic> toJson() => {'id': id};
}

/// Channel identifier
class ChannelId {
  final String id;
  const ChannelId(this.id);

  factory ChannelId.newId(int sequence) => ChannelId('channel-$sequence');
  factory ChannelId.fromJson(Map<String, dynamic> json) => ChannelId(json['id']);
  Map<String, dynamic> toJson() => {'id': id};
}

/// Channel ordering
enum ChannelOrder { unordered, ordered }

/// Channel state
enum ChannelState { uninitialized, init, tryopen, open, closed }

/// IBC Packet
class Packet {
  final BigInt sequence;
  final PortId sourcePort;
  final ChannelId sourceChannel;
  final PortId destPort;
  final ChannelId destChannel;
  final List<int> data;
  final Height timeoutHeight;
  final BigInt timeoutTimestamp;

  const Packet({
    required this.sequence,
    required this.sourcePort,
    required this.sourceChannel,
    required this.destPort,
    required this.destChannel,
    required this.data,
    required this.timeoutHeight,
    required this.timeoutTimestamp,
  });

  factory Packet.fromJson(Map<String, dynamic> json) => Packet(
    sequence: BigInt.parse(json['sequence'].toString()),
    sourcePort: PortId.fromJson(json['source_port']),
    sourceChannel: ChannelId.fromJson(json['source_channel']),
    destPort: PortId.fromJson(json['dest_port']),
    destChannel: ChannelId.fromJson(json['dest_channel']),
    data: List<int>.from(json['data']),
    timeoutHeight: Height.fromJson(json['timeout_height']),
    timeoutTimestamp: BigInt.parse(json['timeout_timestamp'].toString()),
  );
}

/// Timeout information
class Timeout {
  final Height height;
  final BigInt timestamp;

  Timeout({required this.height, BigInt? timestamp}) : timestamp = timestamp ?? BigInt.zero;

  factory Timeout.fromHeight(int height) => Timeout(
    height: Height(revisionHeight: height),
  );

  factory Timeout.fromTimestamp(BigInt timestamp) => Timeout(
    height: const Height(),
    timestamp: timestamp,
  );

  Map<String, dynamic> toJson() => {
    'height': height.toJson(),
    'timestamp': timestamp.toString(),
  };
}

/// Transfer packet data (ICS-20)
class FungibleTokenPacketData {
  final String denom;
  final String amount;
  final String sender;
  final String receiver;
  final String memo;

  const FungibleTokenPacketData({
    required this.denom,
    required this.amount,
    required this.sender,
    required this.receiver,
    this.memo = '',
  });

  bool get isNative => !denom.contains('/');

  factory FungibleTokenPacketData.fromJson(Map<String, dynamic> json) => FungibleTokenPacketData(
    denom: json['denom'],
    amount: json['amount'],
    sender: json['sender'],
    receiver: json['receiver'],
    memo: json['memo'] ?? '',
  );

  Map<String, dynamic> toJson() => {
    'denom': denom,
    'amount': amount,
    'sender': sender,
    'receiver': receiver,
    'memo': memo,
  };
}

/// Swap state
enum SwapState { pending, locked, completed, refunded, expired, cancelled }

/// Swap identifier
class SwapId {
  final String id;
  const SwapId(this.id);

  factory SwapId.fromJson(Map<String, dynamic> json) => SwapId(json['id']);
  Map<String, dynamic> toJson() => {'id': id};
}

/// Swap asset - native token
class NativeAsset {
  final BigInt amount;
  const NativeAsset(this.amount);

  Map<String, dynamic> toJson() => {'native': {'amount': amount.toString()}};
}

/// Swap asset - ICS-20 token
class Ics20Asset {
  final String denom;
  final BigInt amount;
  const Ics20Asset(this.denom, this.amount);

  Map<String, dynamic> toJson() => {'ics20': {'denom': denom, 'amount': amount.toString()}};
}

/// Hashlock - hash of the secret
class Hashlock {
  final List<int> hash;
  const Hashlock(this.hash);

  factory Hashlock.fromJson(Map<String, dynamic> json) => Hashlock(List<int>.from(json['hash']));
  Map<String, dynamic> toJson() => {'hash': hash};
}

/// Timelock - expiration time
class Timelock {
  final BigInt expiry;
  const Timelock(this.expiry);

  bool isExpired(BigInt current) => current >= expiry;

  factory Timelock.fromJson(Map<String, dynamic> json) => Timelock(
    BigInt.parse(json['expiry'].toString()),
  );
  Map<String, dynamic> toJson() => {'expiry': expiry.toString()};
}

/// Atomic swap
class AtomicSwap {
  final SwapId swapId;
  final SwapState state;
  final Map<String, dynamic> initiatorHtlc;
  final Map<String, dynamic>? responderHtlc;

  const AtomicSwap({
    required this.swapId,
    required this.state,
    required this.initiatorHtlc,
    this.responderHtlc,
  });

  factory AtomicSwap.fromJson(Map<String, dynamic> json) => AtomicSwap(
    swapId: SwapId.fromJson(json['swap_id']),
    state: SwapState.values.firstWhere((e) => e.name == json['state']),
    initiatorHtlc: json['initiator_htlc'],
    responderHtlc: json['responder_htlc'],
  );
}

/// IBC SDK configuration
class IbcConfig {
  final String apiKey;
  final String endpoint;
  final String wsEndpoint;
  final int timeout;
  final int retries;
  final String chainId;
  final bool debug;

  const IbcConfig({
    required this.apiKey,
    this.endpoint = 'https://ibc.synor.io/v1',
    this.wsEndpoint = 'wss://ibc.synor.io/v1/ws',
    this.timeout = 30,
    this.retries = 3,
    this.chainId = 'synor-1',
    this.debug = false,
  });
}

/// IBC exception
class IbcException implements Exception {
  final String message;
  final String? code;
  final int? status;

  const IbcException(this.message, [this.code, this.status]);

  @override
  String toString() => 'IbcException: $message${code != null ? ' ($code)' : ''}';
}
