/// Types for Synor RPC SDK.
library synor_rpc_types;

/// Network environment.
enum RpcNetwork {
  mainnet,
  testnet,
  devnet;

  String toJson() => name;

  static RpcNetwork fromJson(String json) {
    return RpcNetwork.values.firstWhere(
      (e) => e.name == json,
      orElse: () => RpcNetwork.mainnet,
    );
  }
}

/// Transaction priority for fee estimation.
enum RpcPriority {
  low,
  medium,
  high;

  String toJson() => name;

  static RpcPriority fromJson(String json) {
    return RpcPriority.values.firstWhere(
      (e) => e.name == json,
      orElse: () => RpcPriority.medium,
    );
  }
}

/// Transaction status.
enum TransactionStatus {
  pending,
  confirmed,
  failed;

  String toJson() => name;

  static TransactionStatus fromJson(String json) {
    return TransactionStatus.values.firstWhere(
      (e) => e.name == json,
      orElse: () => TransactionStatus.pending,
    );
  }
}

/// Configuration for the RPC client.
class RpcConfig {
  final String apiKey;
  final String endpoint;
  final String wsEndpoint;
  final RpcNetwork network;
  final Duration timeout;
  final int retries;
  final bool debug;

  const RpcConfig({
    required this.apiKey,
    this.endpoint = 'https://rpc.synor.io/v1',
    this.wsEndpoint = 'wss://rpc.synor.io/v1/ws',
    this.network = RpcNetwork.mainnet,
    this.timeout = const Duration(seconds: 30),
    this.retries = 3,
    this.debug = false,
  });
}

/// Block header information.
class BlockHeader {
  final String hash;
  final int height;
  final String previousHash;
  final int timestamp;
  final int version;
  final String merkleRoot;
  final int nonce;
  final double difficulty;

  const BlockHeader({
    required this.hash,
    required this.height,
    required this.previousHash,
    required this.timestamp,
    required this.version,
    required this.merkleRoot,
    required this.nonce,
    required this.difficulty,
  });

  factory BlockHeader.fromJson(Map<String, dynamic> json) {
    return BlockHeader(
      hash: json['hash'] as String,
      height: json['height'] as int,
      previousHash: json['previous_hash'] as String,
      timestamp: json['timestamp'] as int,
      version: json['version'] as int,
      merkleRoot: json['merkle_root'] as String,
      nonce: json['nonce'] as int,
      difficulty: (json['difficulty'] as num).toDouble(),
    );
  }
}

/// A blockchain block.
class Block {
  final String hash;
  final int height;
  final String previousHash;
  final int timestamp;
  final int version;
  final String merkleRoot;
  final int nonce;
  final double difficulty;
  final List<RpcTransaction> transactions;
  final int size;
  final int weight;

  const Block({
    required this.hash,
    required this.height,
    required this.previousHash,
    required this.timestamp,
    required this.version,
    required this.merkleRoot,
    required this.nonce,
    required this.difficulty,
    required this.transactions,
    required this.size,
    required this.weight,
  });

  factory Block.fromJson(Map<String, dynamic> json) {
    return Block(
      hash: json['hash'] as String,
      height: json['height'] as int,
      previousHash: json['previous_hash'] as String,
      timestamp: json['timestamp'] as int,
      version: json['version'] as int,
      merkleRoot: json['merkle_root'] as String,
      nonce: json['nonce'] as int,
      difficulty: (json['difficulty'] as num).toDouble(),
      transactions: (json['transactions'] as List)
          .map((e) => RpcTransaction.fromJson(e as Map<String, dynamic>))
          .toList(),
      size: json['size'] as int,
      weight: json['weight'] as int,
    );
  }
}

/// A blockchain transaction.
class RpcTransaction {
  final String txid;
  final String hash;
  final int version;
  final int size;
  final int weight;
  final int lockTime;
  final List<RpcTransactionInput> inputs;
  final List<RpcTransactionOutput> outputs;
  final int fee;
  final int confirmations;
  final String? blockHash;
  final int? blockHeight;
  final int? timestamp;
  final TransactionStatus status;

  const RpcTransaction({
    required this.txid,
    required this.hash,
    required this.version,
    required this.size,
    required this.weight,
    required this.lockTime,
    required this.inputs,
    required this.outputs,
    required this.fee,
    required this.confirmations,
    this.blockHash,
    this.blockHeight,
    this.timestamp,
    this.status = TransactionStatus.pending,
  });

  factory RpcTransaction.fromJson(Map<String, dynamic> json) {
    return RpcTransaction(
      txid: json['txid'] as String,
      hash: json['hash'] as String,
      version: json['version'] as int,
      size: json['size'] as int,
      weight: json['weight'] as int,
      lockTime: json['lock_time'] as int,
      inputs: (json['inputs'] as List)
          .map((e) => RpcTransactionInput.fromJson(e as Map<String, dynamic>))
          .toList(),
      outputs: (json['outputs'] as List)
          .map((e) => RpcTransactionOutput.fromJson(e as Map<String, dynamic>))
          .toList(),
      fee: json['fee'] as int,
      confirmations: json['confirmations'] as int,
      blockHash: json['block_hash'] as String?,
      blockHeight: json['block_height'] as int?,
      timestamp: json['timestamp'] as int?,
      status: TransactionStatus.fromJson(json['status'] as String? ?? 'pending'),
    );
  }
}

/// Transaction input.
class RpcTransactionInput {
  final String txid;
  final int vout;
  final String? scriptSig;
  final int sequence;
  final List<String>? witness;

  const RpcTransactionInput({
    required this.txid,
    required this.vout,
    this.scriptSig,
    required this.sequence,
    this.witness,
  });

  factory RpcTransactionInput.fromJson(Map<String, dynamic> json) {
    return RpcTransactionInput(
      txid: json['txid'] as String,
      vout: json['vout'] as int,
      scriptSig: json['script_sig'] as String?,
      sequence: json['sequence'] as int,
      witness: (json['witness'] as List?)?.cast<String>(),
    );
  }
}

/// Transaction output.
class RpcTransactionOutput {
  final int value;
  final int n;
  final ScriptPubKey scriptPubKey;

  const RpcTransactionOutput({
    required this.value,
    required this.n,
    required this.scriptPubKey,
  });

  factory RpcTransactionOutput.fromJson(Map<String, dynamic> json) {
    return RpcTransactionOutput(
      value: json['value'] as int,
      n: json['n'] as int,
      scriptPubKey:
          ScriptPubKey.fromJson(json['script_pubkey'] as Map<String, dynamic>),
    );
  }
}

/// Script public key.
class ScriptPubKey {
  final String asm;
  final String hex;
  final String type;
  final String? address;

  const ScriptPubKey({
    required this.asm,
    required this.hex,
    required this.type,
    this.address,
  });

  factory ScriptPubKey.fromJson(Map<String, dynamic> json) {
    return ScriptPubKey(
      asm: json['asm'] as String,
      hex: json['hex'] as String,
      type: json['type'] as String,
      address: json['address'] as String?,
    );
  }
}

/// Chain information.
class ChainInfo {
  final String chain;
  final int blocks;
  final int headers;
  final String bestBlockHash;
  final double difficulty;
  final int medianTime;
  final double verificationProgress;
  final String chainWork;
  final bool pruned;

  const ChainInfo({
    required this.chain,
    required this.blocks,
    required this.headers,
    required this.bestBlockHash,
    required this.difficulty,
    required this.medianTime,
    required this.verificationProgress,
    required this.chainWork,
    required this.pruned,
  });

  factory ChainInfo.fromJson(Map<String, dynamic> json) {
    return ChainInfo(
      chain: json['chain'] as String,
      blocks: json['blocks'] as int,
      headers: json['headers'] as int,
      bestBlockHash: json['best_block_hash'] as String,
      difficulty: (json['difficulty'] as num).toDouble(),
      medianTime: json['median_time'] as int,
      verificationProgress: (json['verification_progress'] as num).toDouble(),
      chainWork: json['chain_work'] as String,
      pruned: json['pruned'] as bool,
    );
  }
}

/// Mempool information.
class MempoolInfo {
  final int size;
  final int bytes;
  final int usage;
  final int maxMempool;
  final double mempoolMinFee;
  final double minRelayTxFee;

  const MempoolInfo({
    required this.size,
    required this.bytes,
    required this.usage,
    required this.maxMempool,
    required this.mempoolMinFee,
    required this.minRelayTxFee,
  });

  factory MempoolInfo.fromJson(Map<String, dynamic> json) {
    return MempoolInfo(
      size: json['size'] as int,
      bytes: json['bytes'] as int,
      usage: json['usage'] as int,
      maxMempool: json['max_mempool'] as int,
      mempoolMinFee: (json['mempool_min_fee'] as num).toDouble(),
      minRelayTxFee: (json['min_relay_tx_fee'] as num).toDouble(),
    );
  }
}

/// Fee estimate for transactions.
class FeeEstimate {
  final RpcPriority priority;
  final int feeRate;
  final int estimatedBlocks;

  const FeeEstimate({
    required this.priority,
    required this.feeRate,
    required this.estimatedBlocks,
  });

  factory FeeEstimate.fromJson(Map<String, dynamic> json) {
    return FeeEstimate(
      priority: RpcPriority.fromJson(json['priority'] as String),
      feeRate: json['fee_rate'] as int,
      estimatedBlocks: json['estimated_blocks'] as int,
    );
  }
}

/// Result of transaction submission.
class SubmitResult {
  final String txid;
  final bool accepted;
  final String? reason;

  const SubmitResult({
    required this.txid,
    required this.accepted,
    this.reason,
  });

  factory SubmitResult.fromJson(Map<String, dynamic> json) {
    return SubmitResult(
      txid: json['txid'] as String,
      accepted: json['accepted'] as bool,
      reason: json['reason'] as String?,
    );
  }
}

/// Subscription handle for WebSocket events.
class Subscription {
  final String id;
  final String channel;
  final void Function() _onCancel;

  Subscription({
    required this.id,
    required this.channel,
    required void Function() onCancel,
  }) : _onCancel = onCancel;

  /// Cancel the subscription.
  void cancel() => _onCancel();
}

/// Exception thrown by RPC operations.
class RpcException implements Exception {
  final String message;
  final String? code;
  final int? statusCode;

  const RpcException(this.message, {this.code, this.statusCode});

  @override
  String toString() => 'RpcException: $message';
}
