class Campeao {
  const Campeao({
    required this.userId,
    required this.displayName,
    required this.points,
  });

  factory Campeao.doJson(Map<String, dynamic> j) => Campeao(
    userId: j['userId'] as String,
    displayName: j['displayName'] as String,
    points: j['points'] as int,
  );

  final String userId;
  final String displayName;
  final int points;
}

class LinhaFinal {
  const LinhaFinal({
    required this.posicao,
    required this.displayName,
    required this.points,
  });

  factory LinhaFinal.doJson(Map<String, dynamic> j) => LinhaFinal(
    posicao: j['position'] as int,
    displayName: j['displayName'] as String,
    points: j['points'] as int,
  );

  final int posicao;
  final String displayName;
  final int points;
}

class Encerramento {
  const Encerramento({
    required this.encerradoEm,
    required this.antecipado,
    required this.campeoes,
    this.rankingFinal = const [],
  });

  factory Encerramento.doJson(Map<String, dynamic> j) => Encerramento(
    encerradoEm: DateTime.parse(j['closedAt'] as String),
    antecipado: j['closedEarly'] as bool,
    campeoes: (j['champions'] as List<dynamic>)
        .cast<Map<String, dynamic>>()
        .map(Campeao.doJson)
        .toList(),
    rankingFinal: ((j['finalRanking'] as List<dynamic>?) ?? const [])
        .cast<Map<String, dynamic>>()
        .map(LinhaFinal.doJson)
        .toList(),
  );

  final DateTime encerradoEm;

  /// Encerrado antes da última rodada. Precisa ser distinguível: título de
  /// bolão encerrado na rodada 5 não vale o mesmo.
  final bool antecipado;

  /// Mais de um = co-campeões, empate que resistiu a todos os critérios.
  final List<Campeao> campeoes;

  /// Classificação no momento do encerramento. Não é congelada: correção de
  /// placar posterior recalcula tudo, campeão incluído.
  final List<LinhaFinal> rankingFinal;
}

class ParticipacaoEncerrada {
  const ParticipacaoEncerrada({
    required this.poolId,
    required this.nome,
    required this.encerradoEm,
    required this.antecipado,
    required this.arquivado,
    required this.resultado,
    required this.posicao,
    required this.pontos,
    required this.souAdmin,
  });

  factory ParticipacaoEncerrada.doJson(Map<String, dynamic> j) =>
      ParticipacaoEncerrada(
        poolId: j['poolId'] as String,
        nome: j['name'] as String,
        encerradoEm: DateTime.parse(j['closedAt'] as String),
        antecipado: j['closedEarly'] as bool,
        arquivado: j['archived'] as bool,
        resultado: j['outcome'] as String,
        posicao: j['position'] as int?,
        pontos: j['points'] as int?,
        souAdmin: j['isAdmin'] as bool? ?? false,
      );

  final String poolId;
  final String nome;
  final DateTime encerradoEm;
  final bool antecipado;
  final bool arquivado;

  /// `CAMPEAO`, `CO_CAMPEAO`, `PARTICIPOU` ou `ABANDONADO`.
  final String resultado;
  final int? posicao;
  final int? pontos;

  /// Arquivar é ação de admin: sem isto a tela oferecia o botão a todos e o
  /// participante comum descobria pelo 403.
  final bool souAdmin;
}

abstract class CicloVidaRepository {
  /// Campeão e ranking final de bolão encerrado. **Todo participante** lê, não
  /// só quem encerrou.
  Future<Encerramento> lerEncerramento(String poolId);

  Future<Encerramento> encerrar(String poolId);

  Future<Encerramento> encerrarAntecipado(String poolId);

  Future<void> arquivar(String poolId);

  Future<void> desarquivar(String poolId);

  Future<List<ParticipacaoEncerrada>> historico();

  Future<({String id, String nome, int herdados, int totalJogos})> recriar(
    String poolId, {
    required int rodadaInicial,
    required int rodadaFinal,
    String? nome,
  });
}

/// Texto do resultado final, na voz do participante.
String descreverResultado(String outcome, int? posicao) => switch (outcome) {
  'CAMPEAO' => 'campeão',
  'CO_CAMPEAO' => 'co-campeão',
  'ABANDONADO' => 'você saiu antes do fim — sem posição final',
  _ => posicao == null ? 'participou' : '$posicaoº lugar',
};
