import 'dart:typed_data'; import 'package:pdf/pdf.dart'; import 'package:pdf/widgets.dart' as pw; import 'package:printing/printing.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class PdfExportService { static Future generateAndPrintBoxScore({ required String gameId, required String myTeam, required String opponentTeam, required String myScore, required String opponentScore, required String season, }) async { final supabase = Supabase.instance.client; final gameData = await supabase.from('games').select().eq('id', gameId).single(); final teamsData = await supabase.from('teams').select('id, name').inFilter('name', [myTeam, opponentTeam]); String? myTeamId, oppTeamId; for (var t in teamsData) { if (t['name'] == myTeam) myTeamId = t['id'].toString(); if (t['name'] == opponentTeam) oppTeamId = t['id'].toString(); } List myPlayers = myTeamId != null ? await supabase.from('members').select().eq('team_id', myTeamId).eq('type', 'Jogador') : []; List oppPlayers = oppTeamId != null ? await supabase.from('members').select().eq('team_id', oppTeamId).eq('type', 'Jogador') : []; final statsData = await supabase.from('player_stats').select().eq('game_id', gameId); Map> statsMap = {}; for (var s in statsData) { statsMap[s['member_id'].toString()] = s; } List> myTeamTable = _buildTeamTableData(myPlayers, statsMap); List> oppTeamTable = _buildTeamTableData(oppPlayers, statsMap); final pdf = pw.Document(); pdf.addPage( pw.Page( // 1. Trocado de MultiPage para Page pageFormat: PdfPageFormat.a4.landscape, margin: const pw.EdgeInsets.all(16), // Margens ligeiramente reduzidas para aproveitar o espaço build: (pw.Context context) { // 2. Envolvemos tudo num FittedBox return pw.FittedBox( fit: pw.BoxFit.scaleDown, // Reduz o tamanho apenas se não couber na página child: pw.Container( // Fixamos a largura do contentor à largura útil da página width: PdfPageFormat.a4.landscape.availableWidth, // 3. Colocamos todos os elementos dentro de uma Column child: pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ pw.Row( mainAxisAlignment: pw.MainAxisAlignment.spaceBetween, children: [ pw.Text('Relatório Estatístico', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)), pw.Column( crossAxisAlignment: pw.CrossAxisAlignment.end, children: [ pw.Text('$myTeam vs $opponentTeam', style: pw.TextStyle(fontSize: 16, fontWeight: pw.FontWeight.bold)), pw.Text('Resultado: $myScore - $opponentScore', style: const pw.TextStyle(fontSize: 14)), pw.Text('Época: $season', style: const pw.TextStyle(fontSize: 12)), ] ) ] ), pw.SizedBox(height: 15), // Espaçamentos verticais um pouco mais otimizados pw.Text('Equipa: $myTeam', style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold, color: const PdfColor.fromInt(0xFFA00000))), pw.SizedBox(height: 4), _buildPdfTable(myTeamTable, const PdfColor.fromInt(0xFFA00000)), pw.SizedBox(height: 15), pw.Text('Equipa: $opponentTeam', style: pw.TextStyle(fontSize: 14, fontWeight: pw.FontWeight.bold, color: PdfColors.grey700)), pw.SizedBox(height: 4), _buildPdfTable(oppTeamTable, PdfColors.grey700), pw.SizedBox(height: 15), pw.Row( crossAxisAlignment: pw.CrossAxisAlignment.start, children: [ _buildSummaryBox('Melhor Marcador', gameData['top_pts_name'] ?? '---'), pw.SizedBox(width: 10), _buildSummaryBox('Melhor Ressaltador', gameData['top_rbs_name'] ?? '---'), pw.SizedBox(width: 10), _buildSummaryBox('Melhor Passador', gameData['top_ast_name'] ?? '---'), pw.SizedBox(width: 10), _buildSummaryBox('MVP', gameData['mvp_name'] ?? '---'), ] ), ], ), ), ); }, ), ); await Printing.layoutPdf( onLayout: (PdfPageFormat format) async => pdf.save(), name: 'BoxScore_${myTeam}_vs_${opponentTeam}.pdf', ); } static List> _buildTeamTableData(List players, Map> statsMap) { List> tableData = []; int tPts = 0, tFgm = 0, tFga = 0, tFtm = 0, tFta = 0, tFls = 0; int tOrb = 0, tDrb = 0, tTr = 0, tStl = 0, tAst = 0, tTov = 0, tBlk = 0; int tP3m = 0, tP2m = 0, tP3a = 0, tP2a = 0; players.sort((a, b) { int numA = int.tryParse(a['number']?.toString() ?? '0') ?? 0; int numB = int.tryParse(b['number']?.toString() ?? '0') ?? 0; return numA.compareTo(numB); }); for (var p in players) { String id = p['id'].toString(); String name = p['name'] ?? 'Desconhecido'; String number = p['number']?.toString() ?? '-'; var stat = statsMap[id] ?? {}; int pts = stat['pts'] ?? 0; int fgm = stat['fgm'] ?? 0; int fga = stat['fga'] ?? 0; int ftm = stat['ftm'] ?? 0; int fta = stat['fta'] ?? 0; int p2m = stat['p2m'] ?? 0; int p2a = stat['p2a'] ?? 0; int p3m = stat['p3m'] ?? 0; int p3a = stat['p3a'] ?? 0; int fls = stat['fls'] ?? 0; int orb = stat['orb'] ?? 0; int drb = stat['drb'] ?? 0; int tr = orb + drb; int stl = stat['stl'] ?? 0; int ast = stat['ast'] ?? 0; int tov = stat['tov'] ?? 0; int blk = stat['blk'] ?? 0; tPts += pts; tFgm += fgm; tFga += fga; tFtm += ftm; tFta += fta; tFls += fls; tOrb += orb; tDrb += drb; tTr += tr; tStl += stl; tAst += ast; tTov += tov; tBlk += blk; tP3m += p3m; tP2m += p2m; tP3a += p3a; tP2a += p2a; String p2Pct = p2a > 0 ? ((p2m / p2a) * 100).toStringAsFixed(0) + '%' : '0%'; String p3Pct = p3a > 0 ? ((p3m / p3a) * 100).toStringAsFixed(0) + '%' : '0%'; String globalPct = fga > 0 ? ((fgm / fga) * 100).toStringAsFixed(0) + '%' : '0%'; String llPct = fta > 0 ? ((ftm / fta) * 100).toStringAsFixed(0) + '%' : '0%'; tableData.add([ number, name, pts.toString(), p2m.toString(), p2a.toString(), p2Pct, p3m.toString(), p3a.toString(), p3Pct, fgm.toString(), fga.toString(), globalPct, ftm.toString(), fta.toString(), llPct, fls.toString(), orb.toString(), drb.toString(), tr.toString(), stl.toString(), ast.toString(), tov.toString(), blk.toString() ]); } if (tableData.isEmpty) { tableData.add([ '-', 'Sem jogadores registados', '0', '0', '0', '0%', '0', '0', '0%', '0', '0', '0%', '0', '0', '0%', '0', '0', '0', '0', '0', '0', '0', '0' ]); } String tP2Pct = tP2a > 0 ? ((tP2m / tP2a) * 100).toStringAsFixed(0) + '%' : '0%'; String tP3Pct = tP3a > 0 ? ((tP3m / tP3a) * 100).toStringAsFixed(0) + '%' : '0%'; String tGlobalPct = tFga > 0 ? ((tFgm / tFga) * 100).toStringAsFixed(0) + '%' : '0%'; String tLlPct = tFta > 0 ? ((tFtm / tFta) * 100).toStringAsFixed(0) + '%' : '0%'; tableData.add([ '', 'TOTAIS', tPts.toString(), tP2m.toString(), tP2a.toString(), tP2Pct, tP3m.toString(), tP3a.toString(), tP3Pct, tFgm.toString(), tFga.toString(), tGlobalPct, tFtm.toString(), tFta.toString(), tLlPct, tFls.toString(), tOrb.toString(), tDrb.toString(), tTr.toString(), tStl.toString(), tAst.toString(), tTov.toString(), tBlk.toString() ]); return tableData; } static pw.Widget _buildPdfTable(List> data, PdfColor headerColor) { final headerStyle = pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 8); final subHeaderStyle = pw.TextStyle(color: PdfColors.white, fontWeight: pw.FontWeight.bold, fontSize: 7); final cellStyle = const pw.TextStyle(fontSize: 8); // Agora usamos apenas 15 colunas principais na tabela. // Os grupos (2P, 3P, etc.) são subdivididos INTERNAMENTE para evitar erros de colSpan. return pw.Table( border: pw.TableBorder.all(color: PdfColors.grey400, width: 0.5), columnWidths: { 0: const pw.FlexColumnWidth(1.2), // Nº 1: const pw.FlexColumnWidth(5.0), // NOME (Maior para caber nomes como S.Gilgeous-alexander) 2: const pw.FlexColumnWidth(1.5), // PT 3: const pw.FlexColumnWidth(4.5), // 2 PONTOS (Grupo de 3) 4: const pw.FlexColumnWidth(4.5), // 3 PONTOS (Grupo de 3) 5: const pw.FlexColumnWidth(4.5), // GLOBAL (Grupo de 3) 6: const pw.FlexColumnWidth(4.5), // L. LIVRES (Grupo de 3) 7: const pw.FlexColumnWidth(1.5), // FLS 8: const pw.FlexColumnWidth(1.5), // RO 9: const pw.FlexColumnWidth(1.5), // RD 10: const pw.FlexColumnWidth(1.5), // TR 11: const pw.FlexColumnWidth(1.5), // BR 12: const pw.FlexColumnWidth(1.5), // AS 13: const pw.FlexColumnWidth(1.5), // BP 14: const pw.FlexColumnWidth(1.5), // BLK }, children: [ // --- LINHA 1: CABEÇALHOS --- pw.TableRow( decoration: pw.BoxDecoration(color: headerColor), children: [ _simpleHeader('Nº', subHeaderStyle), _simpleHeader('NOME', subHeaderStyle, align: pw.Alignment.centerLeft), _simpleHeader('PT', subHeaderStyle), _groupHeader('2 PONTOS', headerStyle, subHeaderStyle), _groupHeader('3 PONTOS', headerStyle, subHeaderStyle), _groupHeader('GLOBAL', headerStyle, subHeaderStyle), _groupHeader('L. LIVRES', headerStyle, subHeaderStyle), _simpleHeader('FLS', subHeaderStyle), _simpleHeader('RO', subHeaderStyle), _simpleHeader('RD', subHeaderStyle), _simpleHeader('TR', subHeaderStyle), _simpleHeader('BR', subHeaderStyle), _simpleHeader('AS', subHeaderStyle), _simpleHeader('BP', subHeaderStyle), _simpleHeader('BLK', subHeaderStyle), ], ), // --- LINHAS 2+: DADOS --- ...data.map((row) { bool isTotais = row[1] == 'TOTAIS'; var rowStyle = isTotais ? pw.TextStyle(fontSize: 8, fontWeight: pw.FontWeight.bold) : cellStyle; return pw.TableRow( decoration: pw.BoxDecoration( color: isTotais ? PdfColors.grey200 : PdfColors.white, ), children: [ _simpleData(row[0], rowStyle), _simpleData(row[1], rowStyle, align: pw.Alignment.centerLeft), _simpleData(row[2], rowStyle), _groupData(row[3], row[4], row[5], rowStyle), // 2P: C, T, % _groupData(row[6], row[7], row[8], rowStyle), // 3P: C, T, % _groupData(row[9], row[10], row[11], rowStyle), // GLOBAL: C, T, % _groupData(row[12], row[13], row[14], rowStyle), // L. LIVRES: C, T, % _simpleData(row[15], rowStyle), _simpleData(row[16], rowStyle), _simpleData(row[17], rowStyle), _simpleData(row[18], rowStyle), _simpleData(row[19], rowStyle), _simpleData(row[20], rowStyle), _simpleData(row[21], rowStyle), _simpleData(row[22], rowStyle), ], ); }), ], ); } // ==== WIDGETS AUXILIARES PARA RESOLVER A ESTRUTURA DO PDF ==== // Cabeçalho simples (Colunas que não se dividem) static pw.Widget _simpleHeader(String text, pw.TextStyle style, {pw.Alignment align = pw.Alignment.center}) { return pw.Container( alignment: align, padding: const pw.EdgeInsets.symmetric(vertical: 2, horizontal: 2), child: pw.Text(text, style: style), ); } // Dados simples static pw.Widget _simpleData(String text, pw.TextStyle style, {pw.Alignment align = pw.Alignment.center}) { return pw.Container( alignment: align, padding: const pw.EdgeInsets.symmetric(vertical: 3, horizontal: 2), child: pw.Text(text, style: style), ); } // Cria a divisão do Cabeçalho (O falso ColSpan que une "2 PONTOS" sobre "C | T | %") static pw.Widget _groupHeader(String title, pw.TextStyle hStyle, pw.TextStyle sStyle) { return pw.Column( children: [ pw.Container( width: double.infinity, alignment: pw.Alignment.center, padding: const pw.EdgeInsets.symmetric(vertical: 2), decoration: const pw.BoxDecoration( border: pw.Border(bottom: pw.BorderSide(color: PdfColors.white, width: 0.5)), ), child: pw.Text(title, style: hStyle), ), pw.Row( children: [ pw.Expanded(child: pw.Container(alignment: pw.Alignment.center, child: pw.Text('C', style: sStyle))), pw.Container(width: 0.5, height: 10, color: PdfColors.white), // Divisória vertical manual pw.Expanded(child: pw.Container(alignment: pw.Alignment.center, child: pw.Text('T', style: sStyle))), pw.Container(width: 0.5, height: 10, color: PdfColors.white), // Divisória vertical manual pw.Expanded(child: pw.Container(alignment: pw.Alignment.center, child: pw.Text('%', style: sStyle))), ], ), ], ); } static pw.Widget _groupData(String c, String t, String pct, pw.TextStyle style) { return pw.Row( children: [ pw.Expanded( child: pw.Container( alignment: pw.Alignment.center, padding: const pw.EdgeInsets.symmetric(vertical: 3), child: pw.Text(c, style: style), ), ), pw.Container(width: 0.5, height: 12, color: PdfColors.grey400), // Divisória cinza pw.Expanded( child: pw.Container( alignment: pw.Alignment.center, padding: const pw.EdgeInsets.symmetric(vertical: 3), child: pw.Text(t, style: style), ), ), pw.Container(width: 0.5, height: 12, color: PdfColors.grey400), // Divisória cinza pw.Expanded( child: pw.Container( alignment: pw.Alignment.center, padding: const pw.EdgeInsets.symmetric(vertical: 3), child: pw.Text(pct, style: style), ), ), ], ); } static pw.Widget _buildSummaryBox(String title, String value) { return pw.Container( width: 120, decoration: pw.BoxDecoration( border: pw.TableBorder.all(color: PdfColors.black, width: 1), ), child: pw.Column( children: [ pw.Container( width: double.infinity, padding: const pw.EdgeInsets.all(4), color: const PdfColor.fromInt(0xFFA00000), child: pw.Text(title, style: pw.TextStyle(color: PdfColors.white, fontSize: 9, fontWeight: pw.FontWeight.bold), textAlign: pw.TextAlign.center), ), pw.Container( width: double.infinity, padding: const pw.EdgeInsets.all(6), child: pw.Text(value, style: pw.TextStyle(fontSize: 10, fontWeight: pw.FontWeight.bold), textAlign: pw.TextAlign.center), ), ] ) ); } }