/// Lifecycle state of a single bracket match.
enum MatchStatus { scheduled, inProgress, completed }

/// Lightweight team reference stored inline on a bracket match. The full team
/// record (roster, record, etc.) lives in the teams feature; brackets only
/// need an id, a display name, and an optional logo.
class BracketTeam {
  const BracketTeam({
    required this.id,
    required this.name,
    this.logoUrl,
  });

  final String id;
  final String name;
  final String? logoUrl;

  BracketTeam copyWith({String? id, String? name, String? logoUrl}) {
    return BracketTeam(
      id: id ?? this.id,
      name: name ?? this.name,
      logoUrl: logoUrl ?? this.logoUrl,
    );
  }

  factory BracketTeam.fromMap(Map<String, dynamic> data) {
    return BracketTeam(
      id: (data['id'] as String?) ?? '',
      name: (data['name'] as String?) ?? '',
      logoUrl: data['logo_url'] as String?,
    );
  }

  Map<String, Object?> toMap() {
    return <String, Object?>{
      'id': id,
      'name': name,
      'logo_url': logoUrl,
    };
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is BracketTeam &&
        other.id == id &&
        other.name == name &&
        other.logoUrl == logoUrl;
  }

  @override
  int get hashCode => Object.hash(id, name, logoUrl);
}

/// A single match within a bracket. Either team may be null while previous
/// rounds are still being decided (a `null` team renders as "TBD").
class BracketMatch {
  const BracketMatch({
    required this.id,
    required this.status,
    this.teamA,
    this.teamB,
    this.scoreA,
    this.scoreB,
    this.scheduledAt,
    this.winnerId,
  });

  final String id;
  final BracketTeam? teamA;
  final BracketTeam? teamB;
  final int? scoreA;
  final int? scoreB;
  final MatchStatus status;
  final DateTime? scheduledAt;
  final String? winnerId;

  bool get isTeamAWinner =>
      winnerId != null && teamA != null && winnerId == teamA!.id;
  bool get isTeamBWinner =>
      winnerId != null && teamB != null && winnerId == teamB!.id;

  BracketMatch copyWith({
    String? id,
    BracketTeam? teamA,
    BracketTeam? teamB,
    int? scoreA,
    int? scoreB,
    MatchStatus? status,
    DateTime? scheduledAt,
    String? winnerId,
  }) {
    return BracketMatch(
      id: id ?? this.id,
      teamA: teamA ?? this.teamA,
      teamB: teamB ?? this.teamB,
      scoreA: scoreA ?? this.scoreA,
      scoreB: scoreB ?? this.scoreB,
      status: status ?? this.status,
      scheduledAt: scheduledAt ?? this.scheduledAt,
      winnerId: winnerId ?? this.winnerId,
    );
  }

  factory BracketMatch.fromMap(Map<String, dynamic> data) {
    return BracketMatch(
      id: (data['id'] as String?) ?? '',
      teamA: data['team_a'] is Map<String, dynamic>
          ? BracketTeam.fromMap(data['team_a'] as Map<String, dynamic>)
          : null,
      teamB: data['team_b'] is Map<String, dynamic>
          ? BracketTeam.fromMap(data['team_b'] as Map<String, dynamic>)
          : null,
      scoreA: (data['score_a'] as num?)?.toInt(),
      scoreB: (data['score_b'] as num?)?.toInt(),
      status: _readStatus(data['status']),
      scheduledAt: _readTimestamp(data['scheduled_at']),
      winnerId: data['winner_id'] as String?,
    );
  }

  Map<String, Object?> toMap() {
    return <String, Object?>{
      'id': id,
      'team_a': teamA?.toMap(),
      'team_b': teamB?.toMap(),
      'score_a': scoreA,
      'score_b': scoreB,
      'status': status.name,
      'scheduled_at': scheduledAt?.toIso8601String(),
      'winner_id': winnerId,
    };
  }

  static MatchStatus _readStatus(Object? value) {
    if (value is String) {
      for (final s in MatchStatus.values) {
        if (s.name == value) return s;
      }
    }
    return MatchStatus.scheduled;
  }

  static DateTime? _readTimestamp(Object? value) {
    if (value is String && value.isNotEmpty) return DateTime.tryParse(value);
    if (value is DateTime) return value;
    return null;
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is BracketMatch &&
        other.id == id &&
        other.teamA == teamA &&
        other.teamB == teamB &&
        other.scoreA == scoreA &&
        other.scoreB == scoreB &&
        other.status == status &&
        other.scheduledAt == scheduledAt &&
        other.winnerId == winnerId;
  }

  @override
  int get hashCode => Object.hash(
        id,
        teamA,
        teamB,
        scoreA,
        scoreB,
        status,
        scheduledAt,
        winnerId,
      );
}

/// A round (column) in a bracket — quarterfinals, semifinals, final, etc.
class BracketRound {
  const BracketRound({
    required this.roundNumber,
    required this.label,
    required this.matches,
  });

  final int roundNumber;
  final String label;
  final List<BracketMatch> matches;

  BracketRound copyWith({
    int? roundNumber,
    String? label,
    List<BracketMatch>? matches,
  }) {
    return BracketRound(
      roundNumber: roundNumber ?? this.roundNumber,
      label: label ?? this.label,
      matches: matches ?? this.matches,
    );
  }

  factory BracketRound.fromMap(Map<String, dynamic> data) {
    final rawMatches = (data['matches'] as List?) ?? const [];
    return BracketRound(
      roundNumber: (data['round_number'] as num?)?.toInt() ?? 0,
      label: (data['label'] as String?) ?? '',
      matches: rawMatches
          .whereType<Map<String, dynamic>>()
          .map(BracketMatch.fromMap)
          .toList(growable: false),
    );
  }

  Map<String, Object?> toMap() {
    return <String, Object?>{
      'round_number': roundNumber,
      'label': label,
      'matches': matches.map((m) => m.toMap()).toList(growable: false),
    };
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is! BracketRound) return false;
    if (other.roundNumber != roundNumber) return false;
    if (other.label != label) return false;
    if (other.matches.length != matches.length) return false;
    for (var i = 0; i < matches.length; i++) {
      if (other.matches[i] != matches[i]) return false;
    }
    return true;
  }

  @override
  int get hashCode =>
      Object.hash(roundNumber, label, Object.hashAll(matches));
}

/// Top-level bracket. A single event may have multiple brackets (e.g. main
/// draw + consolation), so brackets carry an [eventId].
class Bracket {
  const Bracket({
    required this.id,
    required this.eventId,
    required this.name,
    required this.rounds,
    required this.createdAt,
  });

  final String id;
  final String eventId;
  final String name;
  final List<BracketRound> rounds;
  final DateTime createdAt;

  Bracket copyWith({
    String? id,
    String? eventId,
    String? name,
    List<BracketRound>? rounds,
    DateTime? createdAt,
  }) {
    return Bracket(
      id: id ?? this.id,
      eventId: eventId ?? this.eventId,
      name: name ?? this.name,
      rounds: rounds ?? this.rounds,
      createdAt: createdAt ?? this.createdAt,
    );
  }

  factory Bracket.fromJson(Map<String, dynamic> data) {
    final rawRounds = (data['rounds'] as List?) ?? const [];
    return Bracket(
      id: (data['id'] as String?) ?? '',
      eventId: (data['event_id'] as String?) ?? '',
      name: (data['name'] as String?) ?? '',
      rounds: rawRounds
          .whereType<Map<String, dynamic>>()
          .map(BracketRound.fromMap)
          .toList(growable: false),
      createdAt: _readDate(data['created_at']) ?? DateTime.now(),
    );
  }

  Map<String, Object?> toJson() {
    return <String, Object?>{
      'event_id': eventId,
      'name': name,
      'rounds': rounds.map((r) => r.toMap()).toList(growable: false),
      'created_at': createdAt.toIso8601String(),
    };
  }

  static DateTime? _readDate(Object? value) {
    if (value is String && value.isNotEmpty) return DateTime.tryParse(value);
    if (value is DateTime) return value;
    return null;
  }

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    if (other is! Bracket) return false;
    if (other.id != id) return false;
    if (other.eventId != eventId) return false;
    if (other.name != name) return false;
    if (other.createdAt != createdAt) return false;
    if (other.rounds.length != rounds.length) return false;
    for (var i = 0; i < rounds.length; i++) {
      if (other.rounds[i] != rounds[i]) return false;
    }
    return true;
  }

  @override
  int get hashCode =>
      Object.hash(id, eventId, name, createdAt, Object.hashAll(rounds));

  @override
  String toString() => 'Bracket(id: $id, name: $name, rounds: ${rounds.length})';
}
