import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:lanebow/services/public_event_service.dart';
import 'package:dio/dio.dart';
import 'package:lanebow/screens/public_event/public_event_entry_screen.dart';
import 'package:lanebow/models/public_event.dart';
import 'package:lanebow/models/public_participant.dart';
import 'package:lanebow/models/public_team.dart';
import 'package:lanebow/utils/ranking_utils_public.dart';
import 'package:lanebow/utils/enum_mappings.dart';
import 'package:lanebow/theme/badges.dart';
import 'package:lanebow/screens/public_event/join_dialog.dart';
import 'package:lanebow/screens/public_event/widgets/personal_views.dart';
import 'package:lanebow/widgets/public_user_widgets.dart';

class PublicEventScreen extends StatefulWidget {
  final String publicHash;
  final PublicEventService? service;
  const PublicEventScreen({super.key, required this.publicHash, this.service});

  @override
  State<PublicEventScreen> createState() => _PublicEventScreenState();
}

class _PublicEventScreenState extends State<PublicEventScreen> {
  late final PublicEventService _service =
      widget.service ?? PublicEventService();
  bool _loading = true;
  String? _error;
  PublicEvent? _eventModel;
  List<PublicParticipant> _participants = const [];
  List<PublicTeam> _teams = const [];
  String? _clubName;
  final Map<String, String> _scoreStates =
      {}; // key: pid-g -> idle|saving|saved|error
  final Map<String, Timer> _debouncers = {};
  final Map<String, Timer> _postTimers = {};
  final Map<int, bool> _teamExpanded = {};
  Timer? _refreshDebounce;
  int _savedTabIndex = 0;
  bool _tabListenerAttached = false;
  bool _personalTableView = false; // 개인별 보기 전환 상태

  @override
  void initState() {
    super.initState();
    // 최초 진입 시 공개 이벤트 데이터 로드
    _load();
    _loadUiPrefs();
  }

  Future<void> _savePersonalViewPref(bool table) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setBool(_prefsKey('personalTableView'), table);
    } catch (_) {}
  }

  String _fmt2(int n) => n.toString().padLeft(2, '0');
  String _fmtLocal(DateTime dt) {
    final l = dt.toLocal();
    return '${l.year}-${_fmt2(l.month)}-${_fmt2(l.day)} ${_fmt2(l.hour)}:${_fmt2(l.minute)}';
  }

  String _prefsKey(String name) => 'publicEvent/${widget.publicHash}/$name';

  Future<void> _loadUiPrefs() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final rawIndex = prefs.getInt(_prefsKey('tabIndex'));
      if (rawIndex == null || (rawIndex != 0 && rawIndex != 1)) {
        _savedTabIndex = 0;
        await prefs.setInt(_prefsKey('tabIndex'), 0);
      } else {
        _savedTabIndex = rawIndex;
      }
      final teamJson = prefs.getString(_prefsKey('teamExpanded'));
      if (teamJson != null && teamJson.isNotEmpty) {
        final decoded = jsonDecode(teamJson);
        if (decoded is Map) {
          _teamExpanded.clear();
          decoded.forEach((k, v) {
            final key = int.tryParse(k.toString());
            if (key != null) {
              _teamExpanded[key] = v == true;
            }
          });
        }
      }
      // 개인별 보기 전환 상태 복원 (기본값: false -> 카드형)
      final pv = prefs.getBool(_prefsKey('personalTableView'));
      _personalTableView = pv == true;
      if (mounted) setState(() {});
    } catch (_) {}
  }

  Future<void> _saveTabIndex(int index) async {
    try {
      final prefs = await SharedPreferences.getInstance();
      await prefs.setInt(_prefsKey('tabIndex'), index);
    } catch (_) {}
  }

  Future<void> _saveTeamExpandedPrefs() async {
    try {
      final prefs = await SharedPreferences.getInstance();
      final map = <String, bool>{};
      _teamExpanded.forEach((k, v) => map[k.toString()] = v);
      await prefs.setString(_prefsKey('teamExpanded'), jsonEncode(map));
    } catch (_) {}
  }

  int? _rankForParticipant(String id, Map<String, Map> rankMap) {
    final data = rankMap[id];
    if (data == null) return null;
    final val = data['rank'];
    if (val is int) return val;
    return int.tryParse((val ?? '').toString());
  }

  Future<void> _saveInlineHandicap(
    PublicParticipant p,
    int gameNumber,
    int handicap,
  ) async {
    final key = _keyFor(p.participantId, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    try {
      // Use the latest score from current state to avoid sending stale value
      int currentScore = 0;
      final idxNow = _participants.indexWhere((pp) => pp.participantId == p.participantId);
      if (idxNow >= 0) {
        final now = _participants[idxNow];
        if (gameNumber - 1 >= 0 && gameNumber - 1 < now.rawScores.length) {
          currentScore = now.rawScores[gameNumber - 1] ?? 0;
        }
      } else {
        currentScore = p.rawScores[gameNumber - 1] ?? 0;
      }
      await _service.updateScore(
        widget.publicHash,
        participantId: p.participantId,
        gameNumber: gameNumber,
        score: currentScore,
        handicap: handicap,
      );
      final idx = _participants.indexWhere(
        (pp) => pp.participantId == p.participantId,
      );
      if (idx >= 0) {
        final cur = _participants[idx];
        final newHc = List<int>.from(cur.handicaps);
        if (gameNumber >= 1 && gameNumber <= newHc.length) {
          newHc[gameNumber - 1] = handicap;
        }
        final updated = PublicParticipant(
          participantId: cur.participantId,
          memberId: cur.memberId,
          name: cur.name,
          gender: cur.gender,
          memberType: cur.memberType,
          average: cur.average,
          handicap: cur.handicap,
          status: cur.status,
          paymentStatus: cur.paymentStatus,
          comment: cur.comment,
          isGuest: cur.isGuest,
          rawScores: cur.rawScores,
          handicaps: newHc,
        );
        final newList = List<PublicParticipant>.from(_participants);
        newList[idx] = updated;
        _participants = newList;
      }
      _scoreStates[key] = 'saved';
      setState(() {});
      _postTimers[key]?.cancel();
      _postTimers[key] = Timer(const Duration(milliseconds: 1200), () {
        if (!mounted) return;
        _scoreStates[key] = 'idle';
        setState(() {});
      });
      _refreshDebounce?.cancel();
      _refreshDebounce = Timer(const Duration(milliseconds: 400), () {
        if (!mounted) return;
        _load();
      });
    } catch (_) {
      _scoreStates[key] = 'error';
      setState(() {});
    }
  }

  Future<void> _saveTeamMemberHandicap(
    int teamNumber,
    PublicTeamMemberSummary m,
    int gameNumber,
    int handicap,
  ) async {
    final key = _keyFor(m.participantId ?? '0', gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    try {
      // Use the latest score from current state to avoid sending stale value
      int currentScore = 0;
      final tIdxNow = _teams.indexWhere((tt) => tt.teamNumber == teamNumber);
      if (tIdxNow >= 0) {
        final teamNow = _teams[tIdxNow];
        final mIdxNow = teamNow.members.indexWhere((mm) => mm.participantId == m.participantId);
        if (mIdxNow >= 0) {
          final mm = teamNow.members[mIdxNow];
          if (gameNumber - 1 >= 0 && gameNumber - 1 < mm.rawScores.length) {
            currentScore = mm.rawScores[gameNumber - 1] ?? 0;
          }
        }
      } else {
        currentScore = m.rawScores[gameNumber - 1] ?? 0;
      }
      await _service.updateScore(
        widget.publicHash,
        participantId: m.participantId!,
        gameNumber: gameNumber,
        score: currentScore,
        handicap: handicap,
        teamNumber: teamNumber,
      );
      final tIdx = _teams.indexWhere((tt) => tt.teamNumber == teamNumber);
      if (tIdx >= 0) {
        final team = _teams[tIdx];
        final members = List<PublicTeamMemberSummary>.from(team.members);
        final mIdx = members.indexWhere(
          (mm) => mm.participantId == m.participantId,
        );
        if (mIdx >= 0) {
          final cur = members[mIdx];
          final newHc = List<int>.from(cur.handicaps);
          if (gameNumber >= 1 && gameNumber <= newHc.length) {
            newHc[gameNumber - 1] = handicap;
          }
          members[mIdx] = PublicTeamMemberSummary(
            participantId: cur.participantId,
            memberId: cur.memberId,
            name: cur.name,
            gender: cur.gender,
            memberType: cur.memberType,
            status: cur.status,
            paymentStatus: cur.paymentStatus,
            comment: cur.comment,
            isGuest: cur.isGuest,
            rawScores: cur.rawScores,
            handicaps: newHc,
            average: cur.average,
            memberHandicap: cur.memberHandicap,
          );
          final updatedTeam = PublicTeam(
            teamId: team.teamId,
            teamNumber: team.teamNumber,
            handicap: team.handicap,
            members: members,
          );
          final newTeams = List<PublicTeam>.from(_teams);
          newTeams[tIdx] = updatedTeam;
          _teams = newTeams;
        }
      }
      _scoreStates[key] = 'saved';
      setState(() {});
      _postTimers[key]?.cancel();
      _postTimers[key] = Timer(const Duration(milliseconds: 1200), () {
        if (!mounted) return;
        _scoreStates[key] = 'idle';
        setState(() {});
      });
      _refreshDebounce?.cancel();
      _refreshDebounce = Timer(const Duration(milliseconds: 400), () {
        if (!mounted) return;
        _load();
      });
    } catch (_) {
      _scoreStates[key] = 'error';
      setState(() {});
    }
  }

  Widget _buildTeamSection(
    PublicEvent? event,
    List<PublicTeam> teams,
    Map<int, Map> teamRankMap,
    bool canInputScore,
  ) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const SizedBox(height: 24),
        Text('총 ${teams.length}팀', key: const Key('teams_count')),
        const SizedBox(height: 8),
        teams.isEmpty
            ? const ListTile(key: Key('empty_teams'), title: Text('팀이 없습니다'))
            : Column(
                children: [
                  for (final t in teams)
                    Padding(
                      padding: const EdgeInsets.symmetric(
                        vertical: 6,
                        horizontal: 8,
                      ),
                      child: Stack(
                        clipBehavior: Clip.none,
                        children: [
                          Container(
                            decoration: BoxDecoration(
                              color: Colors.grey.shade100,
                              borderRadius: BorderRadius.circular(12),
                              border: Border.all(color: Colors.grey.shade300),
                            ),
                            padding: const EdgeInsets.all(12),
                            child: Column(
                              crossAxisAlignment: CrossAxisAlignment.start,
                              children: [
                                Row(
                                  children: [
                                    Container(
                                      padding: const EdgeInsets.symmetric(
                                        horizontal: 10,
                                        vertical: 4,
                                      ),
                                      decoration: BoxDecoration(
                                        color: Colors.blueGrey.withOpacity(
                                          0.08,
                                        ),
                                        borderRadius: BorderRadius.circular(12),
                                        border: Border.all(
                                          color: Colors.blueGrey.withOpacity(
                                            0.25,
                                          ),
                                        ),
                                      ),
                                      child: Text(
                                        '팀 ${t.teamNumber}',
                                        style: const TextStyle(
                                          fontSize: 13,
                                          fontWeight: FontWeight.w700,
                                          color: Colors.black87,
                                        ),
                                      ),
                                    ),
                                    const SizedBox(width: 6),
                                    Text(
                                      '${t.members.length}명',
                                      style: TextStyle(
                                        fontSize: 12,
                                        color: Colors.black.withOpacity(0.55),
                                      ),
                                    ),
                                    const SizedBox(width: 4),
                                    // Team total & average on header row
                                    Builder(
                                      builder: (context) {
                                        int membersTotal = 0;
                                        int validGames = 0; // 유효 게임 수(0 제외)
                                        for (final m in t.members) {
                                          int mt = 0;
                                          for (
                                            int i = 0;
                                            i < m.rawScores.length;
                                            i++
                                          ) {
                                            final raw = m.rawScores[i];
                                            final hc = (i < m.handicaps.length)
                                                ? m.handicaps[i]
                                                : 0;
                                            if (raw == null) continue;
                                            mt += PublicRankingUtils.capScore(
                                              raw + hc,
                                            );
                                            if (raw > 0) validGames++;
                                          }
                                          membersTotal += mt;
                                        }
                                        final total = membersTotal + t.handicap;
                                        final avg = validGames > 0
                                            ? (total / validGames)
                                                  .toStringAsFixed(1)
                                            : '-';
                                        return Container(
                                          padding: const EdgeInsets.symmetric(
                                            horizontal: 8,
                                            vertical: 4,
                                          ),
                                          decoration: BoxDecoration(
                                            color: Colors.blueGrey.withOpacity(
                                              0.08,
                                            ),
                                            borderRadius: BorderRadius.circular(
                                              12,
                                            ),
                                            border: Border.all(
                                              color: Colors.blueGrey
                                                  .withOpacity(0.25),
                                            ),
                                          ),
                                          child: Text(
                                            '총 $total · 평균 $avg',
                                            style: const TextStyle(
                                              fontSize: 12,
                                              fontWeight: FontWeight.w600,
                                              color: Colors.blueGrey,
                                            ),
                                          ),
                                        );
                                      },
                                    ),
                                    if (canInputScore)
                                      Container(
                                        width: 65,
                                        margin: const EdgeInsets.only(
                                          left: 6,
                                        ), // 원하는 값
                                        child: Stack(
                                          clipBehavior: Clip.none,
                                          children: [
                                            TextFormField(
                                              key: Key(
                                                'team_hc_input_${t.teamNumber}',
                                              ),
                                              initialValue: t.handicap
                                                  .toString(),
                                              keyboardType:
                                                  TextInputType.number,
                                              decoration: const InputDecoration(
                                                isDense: true,
                                                labelText: '팀 핸디캡',
                                                floatingLabelBehavior:
                                                    FloatingLabelBehavior.auto,
                                                labelStyle: TextStyle(
                                                  fontSize: 10,
                                                ),
                                                floatingLabelStyle: TextStyle(
                                                  fontSize: 10,
                                                ),
                                                counterText: '',
                                                contentPadding:
                                                    EdgeInsets.symmetric(
                                                      horizontal: 8,
                                                      vertical: 8,
                                                    ),
                                              ),
                                              maxLength: 3,
                                              style: TextStyle(fontSize: 14),
                                              onChanged: (v) async {
                                                final k = _teamKey(
                                                  t.teamNumber,
                                                );
                                                final val = int.tryParse(v);
                                                if (val == null ||
                                                    val < 0 ||
                                                    val > 300)
                                                  return;
                                                _scoreStates[k] = 'saving';
                                                setState(() {});
                                                _debouncers[k]?.cancel();
                                                _debouncers[k] = Timer(
                                                  const Duration(
                                                    milliseconds: 500,
                                                  ),
                                                  () async {
                                                    try {
                                                      await _service
                                                          .updateTeamHandicap(
                                                            widget.publicHash,
                                                            teamNumber:
                                                                t.teamNumber,
                                                            handicap: val,
                                                          );
                                                      final idx = _teams
                                                          .indexWhere(
                                                            (tt) =>
                                                                tt.teamNumber ==
                                                                t.teamNumber,
                                                          );
                                                      if (idx >= 0) {
                                                        _teams[idx] = PublicTeam(
                                                          teamId: _teams[idx]
                                                              .teamId,
                                                          teamNumber:
                                                              _teams[idx]
                                                                  .teamNumber,
                                                          handicap: val,
                                                          members: _teams[idx]
                                                              .members,
                                                        );
                                                      }
                                                      _scoreStates[k] = 'saved';
                                                      setState(() {});
                                                      _postTimers[k]?.cancel();
                                                      _postTimers[k] = Timer(
                                                        const Duration(
                                                          milliseconds: 1200,
                                                        ),
                                                        () {
                                                          if (!mounted) return;
                                                          _scoreStates[k] =
                                                              'idle';
                                                          setState(() {});
                                                        },
                                                      );
                                                    } on DioException catch (
                                                      e
                                                    ) {
                                                      final status = e
                                                          .response
                                                          ?.statusCode;
                                                      if (status == 401 ||
                                                          status == 403) {
                                                        if (!mounted) return;
                                                        Navigator.of(
                                                          context,
                                                        ).pushReplacement(
                                                          MaterialPageRoute(
                                                            builder: (_) =>
                                                                PublicEventEntryScreen(
                                                                  publicHash: widget
                                                                      .publicHash,
                                                                ),
                                                          ),
                                                        );
                                                        return;
                                                      }
                                                      _scoreStates[k] = 'error';
                                                      setState(() {});
                                                    } catch (_) {
                                                      _scoreStates[k] = 'error';
                                                      setState(() {});
                                                    }
                                                  },
                                                );
                                              },
                                            ),
                                          ],
                                        ),
                                      ),
                                    if (!canInputScore && t.handicap != 0)
                                      Flexible(
                                        child: Text(
                                          '팀 핸디캡 ${t.handicap}',
                                          key: Key(
                                            'team_hc_text_${t.teamNumber}',
                                          ),
                                          overflow: TextOverflow.ellipsis,
                                        ),
                                      ),
                                    const SizedBox(width: 6),
                                    const Spacer(),
                                    IconButton(
                                      iconSize: 18,
                                      padding: EdgeInsets.zero,
                                      constraints: const BoxConstraints(
                                        minWidth: 24,
                                        minHeight: 28,
                                      ),
                                      icon: Icon(
                                        (_teamExpanded[t.teamNumber] ?? true)
                                            ? Icons.expand_less
                                            : Icons.expand_more,
                                      ),
                                      onPressed: () {
                                        final v =
                                            _teamExpanded[t.teamNumber] ?? true;
                                        _teamExpanded[t.teamNumber] = !v;
                                        setState(() {});
                                        _saveTeamExpandedPrefs();
                                      },
                                    ),
                                  ],
                                ),
                                const SizedBox(height: 6),
                                const SizedBox(height: 6),
                                if ((_teamExpanded[t.teamNumber] ?? true))
                                  Column(
                                    crossAxisAlignment:
                                        CrossAxisAlignment.start,
                                    children: [
                                      for (
                                        int i = 0;
                                        i < t.members.length;
                                        i++
                                      ) ...[
                                        Padding(
                                          padding: const EdgeInsets.symmetric(
                                            vertical: 6,
                                          ),
                                          child: InkWell(
                                            // Popup removed: tapping team member no longer opens a dialog.
                                            child: Column(
                                              crossAxisAlignment:
                                                  CrossAxisAlignment.start,
                                              children: [
                                                PublicUserNameRow(
                                                  name: t.members[i].name,
                                                  gender: t.members[i].gender,
                                                  memberType:
                                                      t.members[i].memberType,
                                                  userHandicap:
                                                      t
                                                              .members[i]
                                                              .memberHandicap ==
                                                          0
                                                      ? null
                                                      : t
                                                            .members[i]
                                                            .memberHandicap,
                                                  userAverage:
                                                      t.members[i].average ??
                                                      _calcAvg(
                                                        t.members[i].rawScores,
                                                      )?.toDouble(),
                                                ),
                                                const SizedBox(height: 6),
                                                PublicUserChipsRow(
                                                  status: t.members[i].status,
                                                  paymentStatus: t
                                                      .members[i]
                                                      .paymentStatus,
                                                  memberType:
                                                      t.members[i].memberType,
                                                ),
                                                const SizedBox(height: 6),
                                                if (event != null)
                                                  PublicUserGameEditors(
                                                    gameCount: event.gameCount,
                                                    participantId:
                                                        t
                                                            .members[i]
                                                            .participantId ??
                                                        '0',
                                                    rawScores:
                                                        t.members[i].rawScores,
                                                    handicaps:
                                                        t.members[i].handicaps,
                                                    enabled: canInputScore,
                                                    keyFor: _keyFor,
                                                    stateIcon: _stateIcon,
                                                    onScoreChanged: (g, val) =>
                                                        _saveTeamMemberScoreDebounced(
                                                          t.teamNumber,
                                                          t.members[i],
                                                          g,
                                                          val,
                                                        ),
                                                    onHandicapChanged: (g, val) =>
                                                        _saveTeamMemberHandicapDebounced(
                                                          t.teamNumber,
                                                          t.members[i],
                                                          g,
                                                          val,
                                                        ),
                                                    eventAverage: _calcAvg(
                                                      t.members[i].rawScores,
                                                    )?.toDouble(),
                                                    memberHandicap: t
                                                        .members[i]
                                                        .memberHandicap,
                                                  ),
                                              ],
                                            ),
                                          ),
                                        ),
                                        if (i < t.members.length - 1)
                                          const Divider(
                                            height: 12,
                                            thickness: 0.6,
                                          ),
                                      ],
                                    ],
                                  ),
                              ],
                            ),
                          ),
                          Positioned(
                            top: -8,
                            left: -8,
                            child: KeyedSubtree(
                              key: Key('team_rank_${t.teamNumber}'),
                              child: _rankBadge(
                                teamRankMap[t.teamNumber]?['rank'] as int?,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ),
                ],
              ),
      ],
    );
  }

  Future<void> _saveTeamMemberScore(
    int teamNumber,
    PublicTeamMemberSummary m,
    int gameNumber,
    int score,
  ) async {
    if (m.participantId == null || m.participantId!.isEmpty) return;
    final key = _keyFor(m.participantId!, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    try {
      await _service.updateScore(
        widget.publicHash,
        participantId: m.participantId!,
        gameNumber: gameNumber,
        score: score,
        teamNumber: teamNumber,
      );
      final tIdx = _teams.indexWhere((tt) => tt.teamNumber == teamNumber);
      if (tIdx >= 0) {
        final team = _teams[tIdx];
        final members = List<PublicTeamMemberSummary>.from(team.members);
        final mIdx = members.indexWhere(
          (mm) => mm.participantId == m.participantId,
        );
        if (mIdx >= 0) {
          final cur = members[mIdx];
          final newScores = List<int?>.from(cur.rawScores);
          if (gameNumber >= 1 && gameNumber <= newScores.length) {
            newScores[gameNumber - 1] = score;
          }
          members[mIdx] = PublicTeamMemberSummary(
            participantId: cur.participantId,
            memberId: cur.memberId,
            name: cur.name,
            gender: cur.gender,
            memberType: cur.memberType,
            status: cur.status,
            paymentStatus: cur.paymentStatus,
            comment: cur.comment,
            isGuest: cur.isGuest,
            rawScores: newScores,
            handicaps: cur.handicaps,
            average: cur.average,
            memberHandicap: cur.memberHandicap,
          );
          final updatedTeam = PublicTeam(
            teamId: team.teamId,
            teamNumber: team.teamNumber,
            handicap: team.handicap,
            members: members,
          );
          final newTeams = List<PublicTeam>.from(_teams);
          newTeams[tIdx] = updatedTeam;
          _teams = newTeams;
        }
      }
      _scoreStates[key] = 'saved';
      setState(() {});
      _postTimers[key]?.cancel();
      _postTimers[key] = Timer(const Duration(milliseconds: 1200), () {
        if (!mounted) return;
        _scoreStates[key] = 'idle';
        setState(() {});
      });
      _refreshDebounce?.cancel();
      _refreshDebounce = Timer(const Duration(milliseconds: 400), () {
        if (!mounted) return;
        _load();
      });
    } catch (_) {
      _scoreStates[key] = 'error';
      setState(() {});
    }
  }

  double? _calcAvg(List<int?> raw) {
    final vals = raw.whereType<int>().toList();
    if (vals.isEmpty) return null;
    final sum = vals.fold<int>(0, (a, b) => a + b);
    return sum / vals.length;
  }

  Widget _stateIcon(String key) {
    final st = _scoreStates[key];
    if (st == 'saving') {
      return const Icon(Icons.autorenew, size: 14, color: Colors.grey);
    }
    if (st == 'saved') {
      return const Icon(Icons.check_circle, size: 14, color: Colors.green);
    }
    if (st == 'error') {
      return const Icon(Icons.error_outline, size: 14, color: Colors.orange);
    }
    return const SizedBox.shrink();
  }

  Widget _rankBadge(int? rank) {
    final r = rank ?? 0;
    Color bg;
    Color fg;
    String label;
    if (r == 1) {
      bg = Colors.amber.shade100;
      fg = Colors.amber.shade900;
      label = '1위';
    } else if (r == 2) {
      bg = Colors.blueGrey.shade100;
      fg = Colors.blueGrey.shade900;
      label = '2위';
    } else if (r == 3) {
      bg = Colors.brown.shade100;
      fg = Colors.brown.shade900;
      label = '3위';
    } else {
      bg = Colors.blue.shade50;
      fg = Colors.blueGrey.shade700;
      label = (r > 0) ? ('R $r') : 'R -';
    }
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
      decoration: BoxDecoration(
        color: bg,
        borderRadius: BorderRadius.circular(10),
      ),
      child: Text(
        label,
        style: TextStyle(fontSize: 12, color: fg, fontWeight: FontWeight.w600),
      ),
    );
  }

  @override
  void dispose() {
    for (final t in _debouncers.values) {
      t.cancel();
    }
    _debouncers.clear();
    for (final t in _postTimers.values) {
      t.cancel();
    }
    _postTimers.clear();
    super.dispose();
  }

  String _keyFor(String pid, int g) => '$pid-$g';
  String _teamKey(int teamNumber) => 'team-$teamNumber';

  void _saveInlineScoreDebounced(
    PublicParticipant p,
    int gameNumber,
    int score,
  ) {
    final key = _keyFor(p.participantId, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    _debouncers[key]?.cancel();
    _debouncers[key] = Timer(const Duration(milliseconds: 500), () {
      _saveInlineScore(p, gameNumber, score);
    });
  }

  void _saveInlineHandicapDebounced(
    PublicParticipant p,
    int gameNumber,
    int handicap,
  ) {
    final key = _keyFor(p.participantId, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    _debouncers[key]?.cancel();
    _debouncers[key] = Timer(const Duration(milliseconds: 500), () {
      _saveInlineHandicap(p, gameNumber, handicap);
    });
  }

  void _saveTeamMemberScoreDebounced(
    int teamNumber,
    PublicTeamMemberSummary m,
    int gameNumber,
    int score,
  ) {
    final pid = m.participantId ?? '0';
    final key = _keyFor(pid, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    _debouncers[key]?.cancel();
    _debouncers[key] = Timer(const Duration(milliseconds: 500), () {
      _saveTeamMemberScore(teamNumber, m, gameNumber, score);
    });
  }

  void _saveTeamMemberHandicapDebounced(
    int teamNumber,
    PublicTeamMemberSummary m,
    int gameNumber,
    int handicap,
  ) {
    final pid = m.participantId ?? '0';
    final key = _keyFor(pid, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    _debouncers[key]?.cancel();
    _debouncers[key] = Timer(const Duration(milliseconds: 500), () {
      _saveTeamMemberHandicap(teamNumber, m, gameNumber, handicap);
    });
  }

  Future<void> _saveInlineScore(
    PublicParticipant p,
    int gameNumber,
    int score,
  ) async {
    final key = _keyFor(p.participantId, gameNumber);
    _scoreStates[key] = 'saving';
    setState(() {});
    try {
      await _service.updateScore(
        widget.publicHash,
        participantId: p.participantId,
        gameNumber: gameNumber,
        score: score,
      );
      // 로컬 모델 즉시 갱신
      final idx = _participants.indexWhere(
        (pp) => pp.participantId == p.participantId,
      );
      if (idx >= 0) {
        final cur = _participants[idx];
        final newScores = List<int?>.from(cur.rawScores);
        if (gameNumber >= 1 && gameNumber <= newScores.length) {
          newScores[gameNumber - 1] = score;
        }
        final updated = PublicParticipant(
          participantId: cur.participantId,
          memberId: cur.memberId,
          name: cur.name,
          gender: cur.gender,
          memberType: cur.memberType,
          average: cur.average,
          handicap: cur.handicap,
          status: cur.status,
          paymentStatus: cur.paymentStatus,
          comment: cur.comment,
          isGuest: cur.isGuest,
          rawScores: newScores,
          handicaps: cur.handicaps,
        );
        final newList = List<PublicParticipant>.from(_participants);
        newList[idx] = updated;
        _participants = newList;
      }
      _scoreStates[key] = 'saved';
      setState(() {});
      _postTimers[key]?.cancel();
      _postTimers[key] = Timer(const Duration(milliseconds: 1200), () {
        if (!mounted) return;
        _scoreStates[key] = 'idle';
        setState(() {});
      });
      // Debounced full sync load
      _refreshDebounce?.cancel();
      _refreshDebounce = Timer(const Duration(milliseconds: 400), () {
        if (!mounted) return;
        _load();
      });
    } on DioException catch (e) {
      final status = e.response?.statusCode;
      if (status == 401 || status == 403) {
        if (!mounted) return;
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(
            builder: (_) =>
                PublicEventEntryScreen(publicHash: widget.publicHash),
          ),
        );
        return;
      }
      _scoreStates[key] = 'error';
      setState(() {});
    } catch (_) {
      _scoreStates[key] = 'error';
      setState(() {});
    }
  }

  Future<void> _load() async {
    setState(() {
      _loading = true;
      _error = null;
    });
    try {
      final d = await _service.fetchEvent(widget.publicHash);
      if (!mounted) return;
      final eventJson =
          (d['event'] as Map?)?.cast<String, dynamic>() ?? <String, dynamic>{};
      final eventModel = PublicEvent.fromJson(eventJson);
      final gc = eventModel.gameCount;
      final parts = (d['participants'] is List)
          ? (d['participants'] as List)
                .whereType<Map>()
                .map(
                  (e) => PublicParticipant.fromJson(
                    e.cast<String, dynamic>(),
                    gameCount: gc,
                  ),
                )
                .toList()
          : <PublicParticipant>[];
      final teams = (d['teams'] is List)
          ? (d['teams'] as List)
                .whereType<Map>()
                .map(
                  (e) => PublicTeam.fromJson(
                    e.cast<String, dynamic>(),
                    gameCount: gc,
                  ),
                )
                .toList()
          : <PublicTeam>[];

      setState(() {
        _eventModel = eventModel;
        _participants = parts;
        _teams = teams;
        _clubName = (d['clubName']?.toString().isNotEmpty == true)
            ? d['clubName'].toString()
            : null;
      });
    } on DioException catch (e) {
      if (!mounted) return;
      final status = e.response?.statusCode;
      if (status == 401 || status == 403) {
        // 인증 만료: 엔트리 화면으로 복귀
        Navigator.of(context).pushReplacement(
          MaterialPageRoute(
            builder: (_) =>
                PublicEventEntryScreen(publicHash: widget.publicHash),
          ),
        );
        return;
      } else if (status == 404) {
        setState(() {
          _error = '이벤트를 찾을 수 없습니다';
        });
        return;
      }
      setState(() {
        _error = e.toString();
      });
    } catch (e) {
      if (!mounted) return;
      setState(() {
        _error = e.toString();
      });
    } finally {
      if (mounted) {
        setState(() {
          _loading = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    final event = _eventModel;
    final participants = _participants;
    final teams = _teams;
    final partRankMap = PublicRankingUtils.buildParticipantRankMap(
      participants,
    );
    final teamRankMap = PublicRankingUtils.buildTeamRankMap(teams);
    final unassigned = PublicRankingUtils.unassignedParticipants(
      participants,
      teams,
    );
    final now = DateTime.now();
    final registrationClosed =
        (event?.registrationDeadline != null) &&
        now.isAfter(event!.registrationDeadline!);
    final canJoin =
        (event?.isReady == true || event?.isActive == true) &&
        !registrationClosed;
    final canInputScore = (event?.isActive == true);

    return Scaffold(
      appBar: AppBar(
        title: Text(
          (_clubName != null && _clubName!.trim().isNotEmpty)
              ? _clubName!.trim()
              : '공개 이벤트',
        ),
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
        child: Builder(
          builder: (context) {
            if (_loading) {
              return const Center(child: CircularProgressIndicator());
            }
            if (_error != null) {
              return Center(
                child: Column(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Text(_error!, style: const TextStyle(color: Colors.red)),
                    const SizedBox(height: 12),
                    ElevatedButton(
                      key: const Key('go_entry_button'),
                      onPressed: () {
                        Navigator.of(context).pushReplacement(
                          MaterialPageRoute(
                            builder: (_) => PublicEventEntryScreen(
                              publicHash: widget.publicHash,
                            ),
                          ),
                        );
                      },
                      child: const Text('입장 화면으로 돌아가기'),
                    ),
                  ],
                ),
              );
            }
            return SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  if (event != null) ...[
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceBetween,
                      children: [
                        Expanded(
                          child: Text(
                            event.title,
                            key: const Key('event_title'),
                          ),
                        ),
                        if (canJoin)
                          ElevatedButton(
                            key: const Key('join_button'),
                            onPressed: () async {
                              final ok = await showDialog<bool>(
                                context: context,
                                builder: (_) => JoinDialog(
                                  publicHash: widget.publicHash,
                                  service: _service,
                                ),
                              );
                              if (ok == true) {
                                await _load();
                              }
                            },
                            child: const Text('참가 신청/수정'),
                          ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    Wrap(
                      spacing: 8,
                      runSpacing: 8,
                      children: [
                        if (_clubName != null && _clubName!.trim().isNotEmpty)
                          if (!(_clubName!.trim().toLowerCase() ==
                                  (event.location ?? '').trim().toLowerCase() ||
                              _clubName!.trim().toLowerCase() ==
                                  event.title.trim().toLowerCase()))
                            Chip(
                              key: const Key('club_chip'),
                              label: Text(_clubName!.trim()),
                            ),
                        // 이벤트 상태 배지 (공용 스타일)
                        BadgeStyles.eventStatus(
                          getEventStatusLabel(event.status ?? ''),
                        ),
                        if (event.eventType != null &&
                            event.eventType!.isNotEmpty)
                          Chip(
                            key: const Key('type_chip'),
                            label: Text(getEventTypeLabel(event.eventType)),
                          ),
                        if (event.startDate != null || event.endDate != null)
                          Chip(
                            key: const Key('period_chip'),
                            label: Text(
                              ((event.startDate != null)
                                      ? _fmtLocal(event.startDate!)
                                      : '') +
                                  (event.endDate != null
                                      ? ' ~ ${_fmtLocal(event.endDate!)}'
                                      : ''),
                            ),
                          ),
                        Chip(
                          key: const Key('game_chip'),
                          label: Text('게임 ${event.gameCount}'),
                        ),
                        Chip(
                          key: const Key('participants_chip'),
                          label: Text(
                            '참가자 ${_participants.length}${event.maxParticipants != null ? '/${event.maxParticipants!}' : ''}',
                          ),
                        ),
                        if (event.registrationDeadline != null)
                          Chip(
                            key: const Key('deadline_chip'),
                            label: Text(
                              '신청마감 ${event.registrationDeadline!.toLocal().toString().split('.').first}',
                            ),
                          ),
                        if (event.participantFee != null)
                          Chip(
                            key: const Key('fee_chip'),
                            label: Text('참가비 ₩${event.participantFee!}'),
                          ),
                        if (event.location != null &&
                            event.location!.trim().isNotEmpty)
                          if (!(event.location!.trim().toLowerCase() ==
                                  (_clubName ?? '').trim().toLowerCase() ||
                              event.location!.trim().toLowerCase() ==
                                  event.title.trim().toLowerCase()))
                            Chip(
                              key: const Key('location_chip'),
                              label: Text(event.location!.trim()),
                            ),
                      ],
                    ),
                    const SizedBox(height: 8),
                  ],
                  if (teams.isEmpty) ...[
                    const SizedBox(height: 24),
                  ] else ...[
                    DefaultTabController(
                      length: 2,
                      initialIndex: _savedTabIndex < 0
                          ? 0
                          : (_savedTabIndex > 1 ? 1 : _savedTabIndex),
                      child: Builder(
                        builder: (context) {
                          final controller = DefaultTabController.of(context);
                          if (!_tabListenerAttached) {
                            _tabListenerAttached = true;
                            controller.addListener(() {
                              if (!controller.indexIsChanging) {
                                _saveTabIndex(controller.index);
                              }
                            });
                          }
                          return Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              const TabBar(
                                tabs: [
                                  Tab(text: '팀별'),
                                  Tab(text: '개인별'),
                                ],
                              ),
                              const SizedBox(height: 12),
                              AnimatedBuilder(
                                animation: controller,
                                builder: (ctx, _) {
                                  final idx = controller.index;
                                  if (idx == 0) {
                                    return Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: [
                                        _buildTeamSection(
                                          event,
                                          teams,
                                          teamRankMap,
                                          canInputScore,
                                        ),
                                        if (unassigned.isNotEmpty) ...[
                                          const SizedBox(height: 24),
                                          const Text(
                                            '미배정',
                                            style: TextStyle(
                                              fontWeight: FontWeight.bold,
                                            ),
                                            key: Key('section_unassigned'),
                                          ),
                                          Text(
                                            '총 ${unassigned.length}명',
                                            key: const Key('unassigned_count'),
                                          ),
                                          const SizedBox(height: 8),
                                          Card(
                                            child: Column(
                                              children: [
                                                for (
                                                  int i = 0;
                                                  i < unassigned.length;
                                                  i++
                                                ) ...[
                                                  ListTile(
                                                    key: Key(
                                                      'unassigned_${unassigned[i].participantId}',
                                                    ),
                                                    title: Text(
                                                      unassigned[i].name,
                                                    ),
                                                  ),
                                                  if (i < unassigned.length - 1)
                                                    const Divider(
                                                      height: 12,
                                                      thickness: 0.6,
                                                    ),
                                                ],
                                              ],
                                            ),
                                          ),
                                        ],
                                      ],
                                    );
                                  } else {
                                    return Column(
                                      crossAxisAlignment:
                                          CrossAxisAlignment.start,
                                      children: [
                                        // 개인별 보기 전환 아이콘
                                        Row(
                                          mainAxisAlignment:
                                              MainAxisAlignment.end,
                                          children: [
                                            IconButton(
                                              tooltip: '카드형',
                                              onPressed: () {
                                                if (_personalTableView ==
                                                    true) {
                                                  setState(
                                                    () => _personalTableView =
                                                        false,
                                                  );
                                                  _savePersonalViewPref(false);
                                                }
                                              },
                                              icon: Icon(
                                                Icons.view_module,
                                                color:
                                                    (_personalTableView == true)
                                                    ? Colors.blueGrey
                                                    : Colors.indigo,
                                              ),
                                            ),
                                            IconButton(
                                              tooltip: '표형',
                                              onPressed: () {
                                                if (_personalTableView !=
                                                    true) {
                                                  setState(
                                                    () => _personalTableView =
                                                        true,
                                                  );
                                                  _savePersonalViewPref(true);
                                                }
                                              },
                                              icon: Icon(
                                                Icons.table_chart,
                                                color:
                                                    (_personalTableView == true)
                                                    ? Colors.indigo
                                                    : Colors.blueGrey,
                                              ),
                                            ),
                                          ],
                                        ),
                                        const SizedBox(height: 8),
                                        LayoutBuilder(
                                          builder: (ctx, _) {
                                            final size = MediaQuery.of(ctx).size;
                                            final padding = MediaQuery.of(ctx).padding;
                                            final available = size.height - padding.top - padding.bottom - 180;
                                            final boxHeight = available < 360 ? 360.0 : available;
                                            return SizedBox(
                                              height: boxHeight,
                                              child: _personalTableView == true
                                                  ? PersonalTableView(
                                                      event: event!,
                                                      participants: participants,
                                                      canInputScore: canInputScore,
                                                      teams: teams,
                                                      saveInlineScore: (p, g, v) =>
                                                          _saveInlineScoreDebounced(p, g, v),
                                                      saveInlineHandicap: (p, g, v) =>
                                                          _saveInlineHandicapDebounced(p, g, v),
                                                      partRankMap: partRankMap,
                                                      rankForParticipant: (pid, map) =>
                                                          _rankForParticipant(pid, map),
                                                    )
                                                  : PersonalCardView(
                                                      event: event,
                                                      participants: participants,
                                                      partRankMap: partRankMap,
                                                      canInputScore: canInputScore,
                                                      teams: teams,
                                                      keyFor: _keyFor,
                                                      stateIcon: _stateIcon,
                                                      saveInlineScore: (p, g, v) =>
                                                          _saveInlineScoreDebounced(p, g, v),
                                                      saveInlineHandicap: (p, g, v) =>
                                                          _saveInlineHandicapDebounced(p, g, v),
                                                      calcAvg: _calcAvg,
                                                      rankForParticipant: (pid, map) =>
                                                          _rankForParticipant(pid, map),
                                                    ),
                                            );
                                          },
                                        ),
                                      ],
                                    );
                                  }
                                },
                              ),
                            ],
                          );
                        },
                      ),
                    ),
                  ],
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}
