<template>
  <div class="team-management-view">
    <TeamManagementOverview
      :selected-season-id="selectedSeasonId"
      :scheduler-jobs="schedulerJobs"
      :show-global-job-details="showGlobalJobDetails"
      :teams-count="teams.length"
      :fully-configured-teams-count="fullyConfiguredTeamsCount"
      :partially-configured-teams-count="partiallyConfiguredTeamsCount"
      :teams-without-league-count="teamsWithoutLeagueCount"
      :filtered-team-details="getFilteredTeamDetails(schedulerJobs.match_results?.teamDetails || [])"
      :format-job-date="formatJobDate"
      :filtered-teams="filteredTeams"
      :current-season-label="currentSeason?.season || ''"
      :team-search-query="teamSearchQuery"
      :team-filter="teamFilter"
      :active-team-id="teamToEdit ? teamToEdit.id : null"
      :get-my-tischtennis-status="getMyTischtennisStatus"
      :format-date="formatDate"
      :get-team-job-info="getTeamJobInfo"
      :get-team-documents="getTeamDocuments"
      @update:selected-season-id="selectedSeasonId = $event"
      @season-change="onSeasonChange"
      @toggle-job-details="showGlobalJobDetails = !showGlobalJobDetails"
      @update:team-search-query="teamSearchQuery = $event"
      @update:team-filter="teamFilter = $event"
      @create-new-team="resetToNewTeam(); teamFormIsOpen = true;"
      @edit-team="editTeam"
      @delete-team="deleteTeam"
      @show-pdf-dialog="showPDFDialog"
    />

    <div class="team-management-mode-switcher">
      <button type="button" class="workspace-section-button" :class="{ active: activeMainSection === 'overview' }" @click="activeMainSection = 'overview'">
        {{ t('teamManagement.teams') }}
      </button>
      <button type="button" class="workspace-section-button" :class="{ active: activeMainSection === 'planning' }" @click="activeMainSection = 'planning'">
        {{ t('teamManagement.planningTitle') }}
      </button>
    </div>

    <TeamPlanningBoard
      v-if="activeMainSection === 'planning'"
      :t="t"
      :pool-members="planningPoolMembers"
      :selectable-members="planningSelectableMembers"
      :planned-teams="planningTeamsWithMembers"
      :team-age-group-options="teamAgeGroupOptions"
      :team-gender-options="teamGenderOptions"
      :team-save-status-map="planningTeamSaveStatus"
      :unassigned-members="planningUnassignedMembers"
      :member-search-query="planningMemberSearchQuery"
      :marking-interest="markingPlanningInterest"
      @update:member-search-query="planningMemberSearchQuery = $event"
      @add-planning-team="addPlanningTeam"
      @drag-member="onPlanningDragMember"
      @drop-member-to-team="onPlanningDropToTeam"
      @drop-member-to-unassigned="onPlanningDropToUnassigned"
      @move-member-inside-team="onPlanningMoveInsideTeam"
      @remove-member-from-team="onPlanningRemoveFromTeam"
      @mark-member-interested="onPlanningMarkMemberInterested"
      @update-team-field="updatePlanningTeamField"
      @remove-planning-team="removePlanningTeam"
      @convert-planning-team="convertPlanningTeamToRegular"
    />

    <div v-if="activeMainSection === 'overview'" class="newteam">
      <div class="toggle-new-team">
        <span @click="toggleNewTeam">
          <span class="add">{{ teamFormIsOpen ? '-' : '+' }}</span>
          {{ teamToEdit === null ? t('teamManagement.newTeam') : t('teamManagement.editTeam') }}
        </span>
        <button v-if="teamToEdit !== null" @click="resetToNewTeam">{{ t('teamManagement.createNewTeam') }}</button>
      </div>
      
      <div v-if="teamFormIsOpen" class="new-team-form">
        <div class="workspace-header">
          <div>
            <div class="workspace-label">{{ teamToEdit ? t('teamManagement.activeTeam') : t('teamManagement.newTeam') }}</div>
            <h3>{{ teamToEdit ? teamToEdit.name : t('teamManagement.newTeam') }}</h3>
          </div>
          <div v-if="teamToEdit" class="workspace-meta">
            <span class="workspace-badge" :class="getMyTischtennisStatus(teamToEdit).complete ? 'complete' : (getMyTischtennisStatus(teamToEdit).partial ? 'partial' : 'missing')">
              {{
                getMyTischtennisStatus(teamToEdit).complete
                  ? t('teamManagement.fullyConfigured')
                  : (getMyTischtennisStatus(teamToEdit).partial
                    ? t('teamManagement.partiallyConfigured')
                    : t('teamManagement.notConfigured'))
              }}
            </span>
            <span class="workspace-subtle">{{ teamToEdit.league ? teamToEdit.league.name : t('teamManagement.noLeague') }}</span>
          </div>
        </div>

        <div
          v-if="teamToEdit && !getMyTischtennisStatus(teamToEdit).complete && getMyTischtennisStatus(teamToEdit).missingItemLabels.length"
          class="workspace-missing-banner"
          role="status"
        >
          <span class="workspace-missing-banner-label">{{ t('teamManagement.missingConfigSummary') }}</span>
          <ul class="workspace-missing-list">
            <li v-for="(label, idx) in getMyTischtennisStatus(teamToEdit).missingItemLabels" :key="idx">{{ label }}</li>
          </ul>
        </div>

        <div class="workspace-section-switcher">
          <button
            type="button"
            class="workspace-section-button"
            :class="{ active: activeEditorSection === 'basic', 'needs-attention': editorTabNeedsAttention('basic') }"
            @click="activeEditorSection = 'basic'"
          >
            {{ t('teamManagement.basicSettings') }}
          </button>
          <button v-if="teamToEdit && teamToEdit.leagueId" type="button" class="workspace-section-button" :class="{ active: activeEditorSection === 'stats' }" @click="activeEditorSection = 'stats'">
            {{ t('teamManagement.playerStats') }}
          </button>
          <button v-if="teamToEdit" type="button" class="workspace-section-button" :class="{ active: activeEditorSection === 'lineup' }" @click="activeEditorSection = 'lineup'">
            {{ t('teamManagement.lineupProposal') }}
          </button>
          <button v-if="teamToEdit" type="button" class="workspace-section-button" :class="{ active: activeEditorSection === 'documents' }" @click="activeEditorSection = 'documents'">
            {{ t('teamManagement.documents') }}
          </button>
          <button v-if="teamToEdit" type="button" class="workspace-section-button" :class="{ active: activeEditorSection === 'jobs' }" @click="activeEditorSection = 'jobs'">
            {{ t('teamManagement.automaticJobs') }}
          </button>
          <button
            v-if="teamToEdit"
            type="button"
            class="workspace-section-button"
            :class="{ active: activeEditorSection === 'myTischtennis', 'needs-attention': editorTabNeedsAttention('myTischtennis') }"
            @click="activeEditorSection = 'myTischtennis'"
          >
            {{ t('teamManagement.myTischtennis') }}
          </button>
        </div>

        <div v-if="activeEditorSection === 'basic'" class="workspace-section-panel basic-settings">
            <div class="workspace-panel-header">
              <div>
                <span class="section-title">⚙️ {{ t('teamManagement.basicSettings') }}</span>
                <p class="workspace-panel-copy">{{ t('teamManagement.basicSettingsIntro') }}</p>
              </div>
            </div>
            <div v-if="teamToEdit" class="settings-summary-grid">
              <div class="settings-summary-card">
                <span class="settings-summary-label">{{ t('teamManagement.teamName') }}</span>
                <strong>{{ teamToEdit.name }}</strong>
              </div>
              <div
                class="settings-summary-card"
                :class="{ 'settings-summary-card--warning': getMyTischtennisStatus(teamToEdit).needsLeague }"
              >
                <span class="settings-summary-label">{{ t('teamManagement.league') }}</span>
                <strong>{{ teamToEdit.league ? teamToEdit.league.name : t('teamManagement.noLeague') }}</strong>
              </div>
              <div class="settings-summary-card">
                <span class="settings-summary-label">{{ t('teamManagement.plannedLeague') }}</span>
                <strong>{{ teamToEdit.plannedLeagueName?.trim() ? teamToEdit.plannedLeagueName : '–' }}</strong>
              </div>
              <div class="settings-summary-card">
                <span class="settings-summary-label">{{ t('teamManagement.season') }}</span>
                <strong>{{ teamToEdit.season?.season || t('teamManagement.seasonUnknown') }}</strong>
              </div>
              <div class="settings-summary-card">
                <span class="settings-summary-label">{{ t('teamManagement.teamGender') }}</span>
                <strong>{{ labelTeamGender(effectiveTeamGender) }}</strong>
              </div>
              <div class="settings-summary-card">
                <span class="settings-summary-label">{{ t('teamManagement.teamAgeGroup') }}</span>
                <strong>{{ labelAgeGroup(effectiveTeamAgeGroup) }}</strong>
              </div>
            </div>
            <div class="settings-form-grid">
              <label>
                <span>{{ t('teamManagement.teamName') }}:</span> 
                <input type="text" v-model="newTeamName" :placeholder="t('teamManagement.teamNamePlaceholder')">
              </label>
              
              <label
                class="settings-field-league"
                :class="{ 'field-needs-attention': getMyTischtennisStatus(teamToEdit).needsLeague }"
              >
                <span>{{ t('teamManagement.league') }}:</span>
                <select v-model="newLeagueId" :aria-invalid="getMyTischtennisStatus(teamToEdit).needsLeague ? 'true' : 'false'">
                  <option value="">{{ t('teamManagement.noLeague') }}</option>
                  <option v-for="league in filteredLeagues" :key="league.id" :value="league.id">
                    {{ league.name }}
                  </option>
                </select>
                <span v-if="getMyTischtennisStatus(teamToEdit).needsLeague" class="field-attention-hint">{{ t('teamManagement.leagueFieldRequired') }}</span>
              </label>

              <label class="settings-field-span-2">
                <span>{{ t('teamManagement.plannedLeague') }}:</span>
                <input type="text" v-model="newPlannedLeagueName" :placeholder="t('teamManagement.plannedLeaguePlaceholder')" maxlength="512" autocomplete="off">
                <span class="field-hint">{{ t('teamManagement.plannedLeagueHint') }}</span>
              </label>

              <label>
                <span>{{ t('teamManagement.teamGender') }}:</span>
                <select v-model="newTeamGender">
                  <option value="open">{{ t('teamManagement.teamGenderOpen') }}</option>
                  <option value="female">{{ t('teamManagement.teamGenderFemale') }}</option>
                </select>
              </label>

              <label>
                <span>{{ t('teamManagement.teamAgeGroup') }}:</span>
                <select v-model="newTeamAgeGroup">
                  <option v-for="option in teamAgeGroupOptions" :key="option.value" :value="option.value">
                    {{ option.label }}
                  </option>
                </select>
              </label>
            </div>
            <div class="form-actions">
              <button @click="addNewTeam" :disabled="!newTeamName.trim()">
                {{ teamToEdit ? t('teamManagement.change') : t('teamManagement.createAndEdit') }}
              </button>
              <button @click="resetNewTeam" v-if="teamToEdit === null" class="cancel-action">
                {{ t('teamManagement.clearFields') }}
              </button>
              <button @click="resetToNewTeam" v-if="teamToEdit !== null" class="cancel-action">
                {{ t('teamManagement.createNewTeam') }}
              </button>
            </div>
        </div>

        <div v-if="teamToEdit && teamToEdit.leagueId && activeEditorSection === 'stats'" class="workspace-section-panel player-stats">
          <div class="workspace-panel-header">
            <div>
              <span class="section-title">📊 {{ t('teamManagement.playerStats') }}</span>
              <p class="workspace-panel-copy">{{ t('teamManagement.playerStatsIntro') }}</p>
            </div>
            <button @click="refreshPlayerStats" :disabled="loadingStats" class="btn-sm">
              {{ loadingStats ? '⏳' : '🔄' }} {{ t('teamManagement.refreshStats') }}
            </button>
          </div>
          <div v-if="playerStats.length > 0" class="stats-summary-grid">
            <div class="settings-summary-card">
              <span class="settings-summary-label">{{ t('teamManagement.player') }}</span>
              <strong>{{ playerStats.length }}</strong>
            </div>
            <div class="settings-summary-card">
              <span class="settings-summary-label">{{ t('teamManagement.season') }}</span>
              <strong>{{ totalSeasonAppearances }}</strong>
            </div>
            <div class="settings-summary-card">
              <span class="settings-summary-label">{{ isSecondHalf ? t('teamManagement.secondHalf') : t('teamManagement.firstHalf') }}</span>
              <strong>{{ totalHalfAppearances }}</strong>
            </div>
          </div>
          
          <div v-if="loadingStats" class="loading-stats">{{ t('teamManagement.loadingStats') }}</div>
          
          <div v-else-if="playerStats.length === 0" class="no-stats">
            {{ t('teamManagement.noPlayerStats') }}
          </div>
          
          <table v-else class="stats-table">
            <thead>
              <tr>
                <th>{{ t('teamManagement.player') }}</th>
                <th :title="t('teamManagement.qttr')">(Q)TTR</th>
                <th :title="t('teamManagement.seasonFull')">{{ t('teamManagement.season') }}</th>
                <th :title="isSecondHalf ? t('teamManagement.secondHalfFull') : t('teamManagement.firstHalfFull')">
                  {{ isSecondHalf ? t('teamManagement.secondHalf') : t('teamManagement.firstHalf') }}
                </th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="stat in playerStats" :key="stat.memberId">
                <td class="player-name">{{ stat.firstName }} {{ stat.lastName }}</td>
                <td class="stat-value">
                  <span v-if="memberById[stat.memberId]">
                    {{ getMemberLineupRatingLabel(memberById[stat.memberId]) }}
                  </span>
                  <span v-else>–</span>
                </td>
                <td class="stat-value">{{ stat.totalSeason }}</td>
                <td class="stat-value">{{ isSecondHalf ? stat.totalSecondHalf : stat.totalFirstHalf }}</td>
              </tr>
            </tbody>
          </table>

        </div>

        <div v-if="teamToEdit && activeEditorSection === 'lineup'" class="workspace-section-panel player-stats">
          <div class="workspace-panel-header">
            <div>
              <span class="section-title">📋 {{ t('teamManagement.lineupProposal') }}</span>
            </div>
            <div class="lineup-toolbar">
              <div class="lineup-period-meta">
                <span class="lineup-period-chip">
                  {{ t('teamManagement.season') }}: {{ teamToEdit?.season?.season || currentSeason?.season || t('teamManagement.seasonUnknown') }}
                </span>
                <label class="lineup-period-select">
                  <span>{{ t('common.period') }}</span>
                  <select v-model="selectedLineupHalf" :disabled="savingTeamLineup || loadingTeamLineup">
                    <option v-for="option in lineupHalfOptions" :key="option.value" :value="option.value">
                      {{ option.label }}
                    </option>
                  </select>
                </label>
              </div>
              <button type="button" @click="downloadLineupPdf" :disabled="!selectedTeamLineupMembers.length" class="btn-sm" :title="t('teamManagement.exportLineupPdf')">
                📄 {{ t('teamManagement.exportLineupPdf') }}
              </button>
              <button @click="loadClubMembers(); loadTeamLineup();" :disabled="loadingLineupMembers || loadingTeamLineup" class="btn-sm">
                {{ (loadingLineupMembers || loadingTeamLineup) ? '⏳' : '🔄' }} {{ t('teamManagement.refreshStats') }}
              </button>
            </div>
          </div>

          <div class="lineup-editor-grid">
            <div class="lineup-proposal-card">
              <div class="lineup-proposal-header">
                <strong>{{ t('teamManagement.selectedLineup') }}</strong>
                <span>{{ selectedTeamLineupMembers.length }}</span>
              </div>

              <table v-if="selectedTeamLineupMembers.length" class="lineup-proposal-table">
                <thead>
                  <tr>
                    <th></th>
                    <th>#</th>
                    <th>{{ t('teamManagement.player') }}</th>
                    <th>{{ t('members.ageGroup') }}</th>
                    <th :title="t('teamManagement.qttr')">(Q)TTR</th>
                    <th>{{ t('teamManagement.eligibility') }}</th>
                    <th>{{ t('common.actions') }}</th>
                  </tr>
                </thead>
                <tbody :key="lineupRenderKey" ref="lineupSortableBody">
                  <tr v-for="(member, index) in selectedTeamLineupMembers" :key="member.id" class="team-lineup-row">
                    <td class="lineup-drag-cell">
                      <span class="drag-handle" :title="t('common.move')">⋮⋮</span>
                    </td>
                    <td class="lineup-rank">{{ index + 1 }}</td>
                    <td>{{ member.firstName }} {{ member.lastName }}</td>
                    <td>{{ member.memberAgeGroupLabel }}</td>
                    <td class="lineup-rating">{{ member.lineupRatingLabel }}</td>
                    <td>{{ member.eligibilityLabel }}</td>
                    <td class="lineup-actions-cell">
                      <button type="button" class="btn-secondary btn-upload-sm" @click="moveLineupMember(member.id, 'up')" :disabled="savingTeamLineup || index === 0">↑</button>
                      <button type="button" class="btn-secondary btn-upload-sm" @click="moveLineupMember(member.id, 'down')" :disabled="savingTeamLineup || index === selectedTeamLineupMembers.length - 1">↓</button>
                      <button type="button" class="btn-secondary btn-upload-sm" @click="removeMemberFromLineup(member.id)" :disabled="savingTeamLineup">−</button>
                    </td>
                  </tr>
                </tbody>
              </table>

              <div v-else class="no-stats">
                {{ t('teamManagement.lineupEmpty') }}
              </div>
            </div>

            <div v-if="lineupProposalGroups.length" class="lineup-proposal-card">
              <div class="lineup-proposal-header">
                <strong>{{ t('teamManagement.availableLineupMembers') }}</strong>
                <span>{{ availableLineupMembers.length }}</span>
              </div>
              <div class="lineup-proposal-groups">
                <section v-for="group in lineupProposalGroups" :key="group.code" class="lineup-proposal-group">
                  <div class="lineup-proposal-group-head">
                    <strong>{{ group.label }}</strong>
                    <span>{{ group.members.filter((member) => !selectedTeamLineupMembers.some((selected) => selected.id === member.id)).length }}</span>
                  </div>
                  <table class="lineup-proposal-table">
                  <thead>
                    <tr>
                      <th>{{ t('teamManagement.player') }}</th>
                      <th>{{ t('members.ageGroup') }}</th>
                      <th :title="t('teamManagement.qttr')">(Q)TTR</th>
                      <th>{{ t('teamManagement.eligibility') }}</th>
                      <th>{{ t('common.actions') }}</th>
                    </tr>
                  </thead>
                  <tbody>
                    <tr v-for="member in group.members.filter((entry) => !selectedTeamLineupMembers.some((selected) => selected.id === entry.id))" :key="member.id">
                      <td>{{ member.firstName }} {{ member.lastName }}</td>
                      <td>{{ member.memberAgeGroupLabel }}</td>
                      <td class="lineup-rating">{{ member.lineupRatingLabel }}</td>
                      <td>{{ member.eligibilityLabel }}</td>
                      <td class="lineup-actions-cell">
                        <button type="button" class="btn-secondary btn-upload-sm" @click="addMemberToLineup(member)" :disabled="savingTeamLineup">+</button>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </section>
              </div>
            </div>
          </div>

          <div v-if="!lineupProposalGroups.length" class="no-stats">
            {{ t('teamManagement.noPlayerStats') }}
          </div>
        </div>

        <div v-if="teamToEdit && activeEditorSection === 'documents'" class="workspace-section-panel advanced-settings">
            <div class="workspace-panel-header">
              <div>
                <span class="section-title">📋 {{ t('teamManagement.documents') }}</span>
                <p class="workspace-panel-copy">{{ t('teamManagement.documentsIntro') }}</p>
              </div>
            </div>
            <div class="document-status-grid">
              <div class="document-status-card">
                <div class="document-status-head">
                  <strong>{{ t('teamManagement.codeList') }}</strong>
                  <span class="document-status-badge" :class="getTeamDocuments(teamToEdit.id, 'code_list').length ? 'complete' : 'missing'">
                    {{ getTeamDocuments(teamToEdit.id, 'code_list').length ? t('teamManagement.documentAvailable') : t('teamManagement.documentMissing') }}
                  </span>
                </div>
                <p class="document-status-meta">
                  {{ getLatestTeamDocument(teamToEdit.id, 'code_list') ? t('teamManagement.latestUpload', { date: formatDate(getLatestTeamDocument(teamToEdit.id, 'code_list').createdAt) }) : t('teamManagement.noDocumentUploadYet') }}
                </p>
                <div class="document-status-actions">
                  <button @click="uploadCodeList" class="btn-upload-sm">
                    📋 {{ getTeamDocuments(teamToEdit.id, 'code_list').length ? t('teamManagement.uploadNewVersion') : t('teamManagement.codeList') }}
                  </button>
                  <button
                    v-if="getLatestTeamDocument(teamToEdit.id, 'code_list')"
                    type="button"
                    class="btn-secondary btn-upload-sm"
                    @click="showPDFDialog(teamToEdit.id, 'code_list')"
                  >
                    {{ t('teamManagement.openDocument') }}
                  </button>
                  <button
                    v-if="getLatestTeamDocument(teamToEdit.id, 'code_list')"
                    type="button"
                    class="btn-secondary btn-upload-sm"
                    @click="parsePDF(getLatestTeamDocument(teamToEdit.id, 'code_list'))"
                    :disabled="!!parsingDocuments[getLatestTeamDocument(teamToEdit.id, 'code_list').id]"
                  >
                    {{ t('teamManagement.parseDocument') }}
                  </button>
                </div>
              </div>
              <div class="document-status-card">
                <div class="document-status-head">
                  <strong>{{ t('teamManagement.pinList') }}</strong>
                  <span class="document-status-badge" :class="getTeamDocuments(teamToEdit.id, 'pin_list').length ? 'complete' : 'missing'">
                    {{ getTeamDocuments(teamToEdit.id, 'pin_list').length ? t('teamManagement.documentAvailable') : t('teamManagement.documentMissing') }}
                  </span>
                </div>
                <p class="document-status-meta">
                  {{ getLatestTeamDocument(teamToEdit.id, 'pin_list') ? t('teamManagement.latestUpload', { date: formatDate(getLatestTeamDocument(teamToEdit.id, 'pin_list').createdAt) }) : t('teamManagement.noDocumentUploadYet') }}
                </p>
                <div class="document-status-actions">
                  <button @click="uploadPinList" class="btn-upload-sm">
                    🔐 {{ getTeamDocuments(teamToEdit.id, 'pin_list').length ? t('teamManagement.uploadNewVersion') : t('teamManagement.pinList') }}
                  </button>
                  <button
                    v-if="getLatestTeamDocument(teamToEdit.id, 'pin_list')"
                    type="button"
                    class="btn-secondary btn-upload-sm"
                    @click="showPDFDialog(teamToEdit.id, 'pin_list')"
                  >
                    {{ t('teamManagement.openDocument') }}
                  </button>
                  <button
                    v-if="getLatestTeamDocument(teamToEdit.id, 'pin_list')"
                    type="button"
                    class="btn-secondary btn-upload-sm"
                    @click="parsePDF(getLatestTeamDocument(teamToEdit.id, 'pin_list'))"
                    :disabled="!!parsingDocuments[getLatestTeamDocument(teamToEdit.id, 'pin_list').id]"
                  >
                    {{ t('teamManagement.parseDocument') }}
                  </button>
                </div>
              </div>
            </div>
        </div>

        <div v-if="teamToEdit && activeEditorSection === 'jobs'" class="workspace-section-panel advanced-settings">
          <div v-if="getTeamJobInfo(teamToEdit)" class="team-job-info compact">
              <span class="section-title">🔄 {{ t('teamManagement.automaticJobs') }}</span>
            <div v-if="getTeamJobInfo(teamToEdit).lastRun" class="team-job-details">
              <div class="team-job-item">
                <span class="team-job-label">{{ t('teamManagement.lastUpdated') }}:</span>
                <span class="team-job-value">{{ formatJobDate(getTeamJobInfo(teamToEdit).lastRun) }}</span>
              </div>
              <div v-if="getTeamJobInfo(teamToEdit).success !== null" class="team-job-item">
                <span class="team-job-label">{{ t('teamManagement.status') }}:</span>
                <span v-if="getTeamJobInfo(teamToEdit).success" class="team-job-status success">✓ {{ t('teamManagement.successful') }}</span>
                <span v-else class="team-job-status error">✗ {{ t('teamManagement.error') }}</span>
              </div>
            </div>
            <div v-else class="team-job-details">
              <span class="team-job-no-data">{{ t('teamManagement.noAutomaticUpdate') }}</span>
            </div>
          </div>
        </div>

        <div v-if="teamToEdit && activeEditorSection === 'myTischtennis'" class="workspace-section-panel advanced-settings">
          <div class="mytischtennis-config compact">
            <div class="workspace-panel-header">
              <div>
                <span class="section-title">🏓 {{ t('teamManagement.myTischtennis') }}</span>
                <p class="workspace-panel-copy">{{ t('teamManagement.myTischtennisIntro') }}</p>
              </div>
              <button 
                v-if="getMyTischtennisStatus(teamToEdit).complete"
                @click="fetchTeamDataManually"
                :disabled="fetchingTeamData"
                class="btn-sm"
              >
                {{ fetchingTeamData ? '⏳' : '🔄' }} {{ t('teamManagement.manualFetch') }}
              </button>
            </div>
            <div class="mytt-status-card">
              <div class="mytt-status-line">
                <span class="mytt-status-label">{{ t('teamManagement.configurationStatus') }}</span>
                <span v-if="getMyTischtennisStatus(teamToEdit).complete" class="status-badge complete">✓ {{ t('teamManagement.fullyConfigured') }}</span>
                <span v-else-if="getMyTischtennisStatus(teamToEdit).partial" class="status-badge partial">⚠ {{ t('teamManagement.partiallyConfigured') }}</span>
                <span v-else class="status-badge missing">✗ {{ t('teamManagement.notConfigured') }}</span>
              </div>
              <ul
                v-if="!getMyTischtennisStatus(teamToEdit).complete && getMyTischtennisStatus(teamToEdit).missingItemLabels.length"
                class="mytt-missing-checklist"
              >
                <li v-for="(label, idx) in getMyTischtennisStatus(teamToEdit).missingItemLabels" :key="idx">{{ label }}</li>
              </ul>
              <p v-else-if="getMyTischtennisStatus(teamToEdit).complete" class="mytt-status-copy">
                {{ t('teamManagement.noIssues') }}
              </p>
            </div>
            
            <div
              class="compact-input-row"
              :class="{ 'field-needs-attention-wrap': getMyTischtennisStatus(teamToEdit).needsTeamId }"
            >
              <input 
                type="text" 
                v-model="myTischtennisUrl" 
                @keyup.enter="parseMyTischtennisUrl"
                :placeholder="t('teamManagement.myTischtennisUrlPlaceholder')"
                class="compact-url-input"
                :disabled="parsingUrl"
                :aria-invalid="getMyTischtennisStatus(teamToEdit).needsTeamId ? 'true' : 'false'"
              >
              <button type="button" class="btn-secondary btn-upload-sm" @click="parseMyTischtennisUrl" :disabled="parsingUrl || !myTischtennisUrl.trim()">
                {{ parsingUrl ? '⏳' : t('teamManagement.parseUrlAction') }}
              </button>
            </div>
            <p v-if="getMyTischtennisStatus(teamToEdit).needsTeamId" class="field-attention-hint field-attention-hint-block">{{ t('teamManagement.myTischtennisUrlRequired') }}</p>
            
            <div v-if="myTischtennisError" class="compact-message error">⚠️ {{ myTischtennisError }}</div>
            <div v-if="myTischtennisSuccess" class="compact-message success">✅ {{ myTischtennisSuccess }}</div>
          </div>
        </div>
      </div>
    </div>
    
    <!-- PDF-Dialog -->
    <div v-if="showPDFViewer" class="pdf-dialog-overlay" @click="closePDFDialog">
      <div class="pdf-dialog" @click.stop>
        <div class="pdf-dialog-header">
          <h3>{{ pdfDialogTitle }}</h3>
          <button @click="closePDFDialog" class="close-btn">✕</button>
        </div>
        <div class="pdf-dialog-content">
          <iframe 
            v-if="pdfUrl" 
            :src="pdfUrl" 
            class="pdf-viewer"
            frameborder="0"
          ></iframe>
          <div v-else class="no-pdf">
            <p>PDF konnte nicht geladen werden.</p>
          </div>
        </div>
      </div>
    </div>
    
    <!-- Info Dialog -->
    <InfoDialog
        v-model="infoDialog.isOpen"
        :title="infoDialog.title"
        :message="infoDialog.message"
        :details="infoDialog.details"
        :type="infoDialog.type"
    />
    
    <!-- Confirm Dialog -->
    <ConfirmDialog
        v-model="confirmDialog.isOpen"
        :title="confirmDialog.title"
        :message="confirmDialog.message"
        :details="confirmDialog.details"
        :type="confirmDialog.type"
        @confirm="handleConfirmResult(true)"
        @cancel="handleConfirmResult(false)"
    />
  </div>
</template>

<script>
import { ref, onMounted, computed, watch, nextTick, onBeforeUnmount } from 'vue';
import { useStore } from 'vuex';
import SeasonSelector from '../components/SeasonSelector.vue';
import apiClient from '../apiClient.js';
import { getSafeErrorMessage, getSafeMessage } from '../utils/errorMessages.js';
import i18n from '../i18n';
import Sortable from 'sortablejs';

import InfoDialog from '../components/InfoDialog.vue';
import ConfirmDialog from '../components/ConfirmDialog.vue';
import TeamListCard from '../components/team/TeamListCard.vue';
import TeamManagementOverview from '../components/team/TeamManagementOverview.vue';
import TeamPlanningBoard from '../components/team/TeamPlanningBoard.vue';
import { buildInfoConfig, buildConfirmConfig } from '../utils/dialogUtils.js';
import PDFGenerator from '../components/PDFGenerator.js';

export default {
  name: 'TeamManagementView',
  components: {
    SeasonSelector,
    InfoDialog,
    ConfirmDialog,
    TeamListCard,
    TeamManagementOverview,
    TeamPlanningBoard
  },
  setup() {
    const store = useStore();
    const t = (key, params) => i18n.global.t(key, params);
    
    // Dialog States
    const infoDialog = ref({
      isOpen: false,
      title: '',
      message: '',
      details: '',
      type: 'info',
      size: 'small',
      closeOnOverlay: true,
    });
    const confirmDialog = ref({
      isOpen: false,
      title: '',
      message: '',
      details: '',
      type: 'info',
      confirmText: t('common.confirm'),
      cancelText: t('common.cancel'),
      showCancel: true,
      resolveCallback: null
    });
    
    // Reactive data
    const teams = ref([]);
    const leagues = ref([]);
    const teamFormIsOpen = ref(false);
    const teamToEdit = ref(null);
    const newTeamName = ref('');
    const newLeagueId = ref('');
    const newTeamGender = ref('open');
    const newTeamAgeGroup = ref('adult');
    const newPlannedLeagueName = ref('');
    const selectedSeasonId = ref(null);
    const currentSeason = ref(null);
    const teamDocuments = ref([]);
    const parsingInProgress = ref(false);
    const parsingDocuments = ref({});
    const teamSearchQuery = ref('');
    const teamFilter = ref('all');
    const activeMainSection = ref('overview');
    const activeEditorSection = ref('basic');
    const showGlobalJobDetails = ref(false);
    const planningMemberSearchQuery = ref('');
    const planningDraggingMemberId = ref(null);
    const planningAssignments = ref([]);
    const planningLocalTeams = ref([]);
    const planningTeamSaveStatus = ref({});
    const markingPlanningInterest = ref(false);
    const planningInterestedMemberIds = ref(new Set());
    const planningAutosaveTimers = new Map();
    const planningStatusClearTimers = new Map();
    
    // PDF-Dialog Variablen
    const showPDFViewer = ref(false);
    const pdfUrl = ref('');
    const pdfDialogTitle = ref('');
    
    // MyTischtennis URL Parser
    const myTischtennisUrl = ref('');
    const parsedMyTischtennisData = ref(null);
    const parsingUrl = ref(false);
    const configuringTeam = ref(false);
    const fetchingTeamData = ref(false);
    const myTischtennisError = ref('');
    const myTischtennisSuccess = ref('');
    
    // Player Stats
    const playerStats = ref([]);
    const loadingStats = ref(false);
    const loadingLineupMembers = ref(false);
    const loadingTeamLineup = ref(false);
    const savingTeamLineup = ref(false);
    const memberById = ref({});
    const clubMembers = ref([]);
    const teamLineupAssignments = ref([]);
    const lineupSortableBody = ref(null);
    const lineupRenderKey = ref(0);
    const selectedLineupHalf = ref('first_half');
    let lineupSortableInstance = null;
    
    // Scheduler Jobs Info
    const schedulerJobs = ref({
      rating_updates: null,
      match_results: null
    });
    
    // Computed
    const selectedClub = computed(() => store.state.currentClub);
    const authToken = computed(() => store.state.token);
    const filteredLeagues = computed(() => {
      if (!selectedSeasonId.value) return [];
      return leagues.value.filter(league => league.seasonId == selectedSeasonId.value);
    });
    const isSecondHalf = computed(() => {
      const now = new Date();
      return now.getMonth() >= 0 && now.getMonth() <= 5; // Januar (0) bis Juni (5) = Rückrunde
    });
    const fullyConfiguredTeamsCount = computed(() => teams.value.filter(team => getMyTischtennisStatus(team).complete).length);
    const partiallyConfiguredTeamsCount = computed(() => teams.value.filter(team => getMyTischtennisStatus(team).partial).length);
    const teamsWithoutLeagueCount = computed(() => teams.value.filter(team => !team.leagueId).length);
    const totalSeasonAppearances = computed(() => playerStats.value.reduce((sum, stat) => sum + (Number(stat.totalSeason) || 0), 0));
    const totalHalfAppearances = computed(() => playerStats.value.reduce((sum, stat) => sum + (Number(isSecondHalf.value ? stat.totalSecondHalf : stat.totalFirstHalf) || 0), 0));
    const lineupHalfOptions = computed(() => ([
      { value: 'first_half', label: t('teamManagement.firstHalfFull') },
      { value: 'second_half', label: t('teamManagement.secondHalfFull') }
    ]));
    const teamAgeGroupOptions = computed(() => ['adult', 'J19', 'J17', 'J15', 'J13', 'J11'].map((value) => ({
      value,
      label: value === 'adult' ? t('members.adults') : t(`members.${value.toLowerCase()}`)
    })));
    const teamGenderOptions = computed(() => ([
      { value: 'open', label: t('teamManagement.teamGenderOpen') },
      { value: 'female', label: t('teamManagement.teamGenderFemale') }
    ]));
    const filteredTeams = computed(() => {
      const search = teamSearchQuery.value.trim().toLowerCase();
      return teams.value.filter(team => {
        const status = getMyTischtennisStatus(team);
        const matchesSearch = !search || [
          team.name,
          team.league?.name,
          team.season?.season
        ].filter(Boolean).some(value => String(value).toLowerCase().includes(search));

        if (!matchesSearch) return false;

        if (teamFilter.value === 'configured') return status.complete;
        if (teamFilter.value === 'attention') return !status.complete;
        if (teamFilter.value === 'noLeague') return !team.leagueId;
        return true;
      });
    });
    const planningMemberSearchNeedle = computed(() => planningMemberSearchQuery.value.trim().toLowerCase());
    const planningMemberKey = (id) => String(id ?? '');
    const planningMemberById = computed(() => {
      const map = new Map();
      (clubMembers.value || []).forEach((member) => {
        const ageCode = getMemberAgeGroupCode(member);
        map.set(planningMemberKey(member.id), {
          ...member,
          memberAgeGroupLabel: getMemberAgeGroupLabel(ageCode),
          lineupRatingLabel: getMemberLineupRatingLabel(member)
        });
      });
      return map;
    });
    const isMemberInterested = (member) => planningInterestedMemberIds.value.has(planningMemberKey(member?.id));

    const planningPoolMembersRaw = computed(() => {
      return (clubMembers.value || [])
        .filter((member) => {
          if (!member?.active || member?.testMembership) return false;
          return planningInterestedMemberIds.value.has(planningMemberKey(member?.id));
        })
        .map((member) => planningMemberById.value.get(planningMemberKey(member.id)))
        .filter(Boolean);
    });
    const planningPoolMembers = computed(() => {
      const needle = planningMemberSearchNeedle.value;
      return planningPoolMembersRaw.value.filter((member) => {
        if (!needle) return true;
        return `${member.firstName || ''} ${member.lastName || ''}`.toLowerCase().includes(needle);
      });
    });
    const planningSelectableMembers = computed(() => {
      const needle = planningMemberSearchNeedle.value;
      return (clubMembers.value || [])
        .filter((member) => member?.active && !member?.testMembership && !isMemberInterested(member))
        .map((member) => planningMemberById.value.get(planningMemberKey(member.id)))
        .filter(Boolean)
        .filter((member) => {
          if (!needle) return true;
          return `${member.firstName || ''} ${member.lastName || ''}`.toLowerCase().includes(needle);
        });
    });
    const planningTeamsWithMembers = computed(() => {
      return planningLocalTeams.value.map((team) => {
        const members = planningAssignments.value
          .filter((entry) => Number(entry.teamId) === Number(team.id))
          .sort((a, b) => a.position - b.position)
          .map((entry) => planningMemberById.value.get(planningMemberKey(entry.memberId)))
          .filter(Boolean);
        return {
          ...team,
          members
        };
      });
    });
    const planningUnassignedMembers = computed(() => {
      const assigned = new Set(planningAssignments.value.map((entry) => Number(entry.memberId)));
      return planningPoolMembers.value.filter((member) => !assigned.has(Number(member.id)));
    });

    const parseLeagueAgeGroupCode = (leagueName) => {
      const source = String(leagueName || '');
      const compactMatch = source.match(/([JM])(\d{1,2})/i);
      if (compactMatch) {
        return `J${compactMatch[2]}`;
      }

      const youthMatch = source.match(/(?:jugend|mädchen)\s*(\d{1,2})/i);
      if (youthMatch) {
        return `J${youthMatch[1]}`;
      }

      return 'adult';
    };

    const parseLeagueGenderCode = (leagueName) => {
      const source = String(leagueName || '').toLowerCase();
      return /(frauen|damen|mädchen|girls|women)/i.test(source) ? 'female' : 'open';
    };

    const getConfiguredTeamAgeGroup = (team) => team?.teamAgeGroup || parseLeagueAgeGroupCode(team?.league?.name);
    const getConfiguredTeamGender = (team) => team?.teamGender || parseLeagueGenderCode(team?.league?.name);

    const effectiveTeamAgeGroup = computed(() => getConfiguredTeamAgeGroup(teamToEdit.value));
    const effectiveTeamGender = computed(() => getConfiguredTeamGender(teamToEdit.value));

    const getSeasonReferenceYear = () => {
      const seasonLabel = String(currentSeason.value?.season || teamToEdit.value?.season?.season || '').trim();
      const fourDigitRange = seasonLabel.match(/(\d{4})\s*\/\s*(\d{4})/);
      if (fourDigitRange) {
        return Number(fourDigitRange[2]);
      }

      const mixedRange = seasonLabel.match(/(\d{4})\s*\/\s*(\d{2})/);
      if (mixedRange) {
        const startYear = Number(mixedRange[1]);
        const endTwoDigits = Number(mixedRange[2]);
        const century = Math.floor(startYear / 100) * 100;
        return century + endTwoDigits;
      }

      const singleYear = seasonLabel.match(/(\d{4})/);
      if (singleYear) {
        return Number(singleYear[1]);
      }

      return new Date().getFullYear();
    };

    const parseMemberBirthDate = (value) => {
      if (!value || typeof value !== 'string') {
        return null;
      }

      const trimmed = value.trim();
      const dotted = trimmed.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/);
      if (dotted) {
        const year = Number(dotted[3]);
        const month = Number(dotted[2]) - 1;
        const day = Number(dotted[1]);
        const parsed = new Date(year, month, day);
        return Number.isNaN(parsed.getTime()) ? null : parsed;
      }

      const isoDate = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/);
      if (isoDate) {
        const year = Number(isoDate[1]);
        const month = Number(isoDate[2]) - 1;
        const day = Number(isoDate[3]);
        const parsed = new Date(year, month, day);
        return Number.isNaN(parsed.getTime()) ? null : parsed;
      }

      const parsed = new Date(trimmed);
      return Number.isNaN(parsed.getTime()) ? null : parsed;
    };

    const getMemberActualAge = (member) => {
      const birthDate = parseMemberBirthDate(member?.birthDate);
      if (!birthDate) {
        return null;
      }

      const today = new Date();
      let age = today.getFullYear() - birthDate.getFullYear();
      const hadBirthdayThisYear =
        today.getMonth() > birthDate.getMonth() ||
        (today.getMonth() === birthDate.getMonth() && today.getDate() >= birthDate.getDate());
      if (!hadBirthdayThisYear) {
        age -= 1;
      }
      return age;
    };

    const getMemberSeasonAge = (member) => {
      const birthDate = parseMemberBirthDate(member?.birthDate);
      if (!birthDate) {
        return null;
      }

      return getSeasonReferenceYear() - birthDate.getFullYear();
    };

    const getMemberAgeGroupCode = (member) => {
      const seasonAge = getMemberSeasonAge(member);
      if (seasonAge === null) {
        return 'unknown';
      }

      if (seasonAge <= 11) return 'J11';
      if (seasonAge <= 13) return 'J13';
      if (seasonAge <= 15) return 'J15';
      if (seasonAge <= 17) return 'J17';
      if (seasonAge <= 19) return 'J19';
      return 'adult';
    };

    const getMemberAgeGroupLabel = (code) => {
      if (code === 'adult') return t('members.adults');
      if (code === 'unknown') return t('unknown');
      return code;
    };

    const labelAgeGroup = (code) => getMemberAgeGroupLabel(code);
    const labelTeamGender = (gender) => gender === 'female' ? t('teamManagement.teamGenderFemale') : t('teamManagement.teamGenderOpen');

    const isFemaleEligible = (member, teamGender) => {
      if (teamGender !== 'female') return true;
      return member?.gender === 'female';
    };

    const isEligibleForTeam = (member, teamAgeGroup, teamGender) => {
      if (!member?.active || !isFemaleEligible(member, teamGender)) {
        return false;
      }

      const memberAgeGroup = getMemberAgeGroupCode(member);
      const memberActualAge = getMemberActualAge(member);
      if (memberAgeGroup === 'unknown') {
        return false;
      }

      if (teamAgeGroup === 'adult') {
        if (memberActualAge !== null && memberActualAge >= 18) {
          return true;
        }
        return !!member?.adultReleaseApproved || !!member?.adultReserveApproved;
      }

      const order = ['J11', 'J13', 'J15', 'J17', 'J19'];
      const memberIndex = order.indexOf(memberAgeGroup);
      const teamIndex = order.indexOf(teamAgeGroup);
      if (memberIndex < 0 || teamIndex < 0) {
        return false;
      }
      return memberIndex <= teamIndex;
    };

    const getMemberEligibilityLabel = (member, teamAgeGroup) => {
      if (teamAgeGroup !== 'adult' || getMemberAgeGroupCode(member) === 'adult') {
        return t('teamManagement.eligibilityRegular');
      }
      if (member?.adultReleaseApproved && member?.adultReserveApproved) {
        return t('teamManagement.eligibilityAdultReleaseAndReserve');
      }
      if (member?.adultReleaseApproved) {
        return t('teamManagement.eligibilityAdultRelease');
      }
      if (member?.adultReserveApproved) {
        return t('teamManagement.eligibilityAdultReserve');
      }
      return t('teamManagement.eligibilityRegular');
    };

    /**
     * Nur für Meldungsreihenfolge / ±30-Punkte-Regel: zählt **QTTR > 0**.
     * Kein QTTR, 0 oder ungültig → wie „unbewertet“ (frei verschiebbar bzgl. der Regel).
     */
    const getMemberLineupRatingValue = (member) => {
      if (member == null) return Number.NEGATIVE_INFINITY;
      const raw = member.qttr;
      if (raw == null || raw === '') return Number.NEGATIVE_INFINITY;
      const qttr = Number(raw);
      if (!Number.isFinite(qttr) || qttr === 0) return Number.NEGATIVE_INFINITY;
      return qttr;
    };

    /** Anzeige Meldung/Statistik: nur QTTR (>0); TTR ist für die Einordnung nicht relevant */
    const getMemberLineupRatingLabel = (member) => {
      const rawQ = member?.qttr;
      if (rawQ == null || rawQ === '') return '–';
      const q = Number(rawQ);
      if (!Number.isFinite(q) || q === 0) return '–';
      return String(q);
    };

    const lineupProposalGroups = computed(() => {
      const teamAgeGroup = effectiveTeamAgeGroup.value;
      const teamGender = effectiveTeamGender.value;
      const members = (clubMembers.value || []).filter((member) => isEligibleForTeam(member, teamAgeGroup, teamGender));
      if (!members.length) {
        return [];
      }

      const preferredAgeGroup = teamAgeGroup;
      const defaultOrder = teamAgeGroup === 'adult'
        ? ['adult', 'J19', 'J17', 'J15', 'J13', 'J11', 'unknown']
        : [teamAgeGroup, ...['J19', 'J17', 'J15', 'J13', 'J11'].filter((entry) => entry !== teamAgeGroup), 'unknown'];
      const groupOrder = defaultOrder.includes(preferredAgeGroup)
        ? [preferredAgeGroup, ...defaultOrder.filter((entry) => entry !== preferredAgeGroup)]
        : defaultOrder;

      const groups = new Map();
      members.forEach((member) => {
        const code = getMemberAgeGroupCode(member);
        if (!groups.has(code)) {
          groups.set(code, {
            code,
            label: getMemberAgeGroupLabel(code),
            members: []
          });
        }
        groups.get(code).members.push({
          ...member,
          lineupRatingLabel: getMemberLineupRatingLabel(member),
          memberAgeGroupLabel: getMemberAgeGroupLabel(code),
          eligibilityLabel: getMemberEligibilityLabel(member, teamAgeGroup)
        });
      });

      return Array.from(groups.values())
        .map((group) => ({
          ...group,
          members: [...group.members].sort((a, b) => {
            const ratingDiff = getMemberLineupRatingValue(b) - getMemberLineupRatingValue(a);
            if (ratingDiff !== 0) return ratingDiff;
            const lastNameDiff = String(a.lastName || '').localeCompare(String(b.lastName || ''), 'de', { sensitivity: 'base' });
            if (lastNameDiff !== 0) return lastNameDiff;
            return String(a.firstName || '').localeCompare(String(b.firstName || ''), 'de', { sensitivity: 'base' });
          })
        }))
        .sort((a, b) => {
          const indexA = groupOrder.indexOf(a.code);
          const indexB = groupOrder.indexOf(b.code);
          const safeA = indexA >= 0 ? indexA : groupOrder.length;
          const safeB = indexB >= 0 ? indexB : groupOrder.length;
          return safeA - safeB;
        });
    });
    const lineupProposalMemberCount = computed(() => lineupProposalGroups.value.reduce((sum, group) => sum + group.members.length, 0));
    const eligibleLineupMembers = computed(() => lineupProposalGroups.value.flatMap((group) => group.members));
    const selectedTeamLineupMembers = computed(() => {
      const memberMap = new Map(eligibleLineupMembers.value.map((member) => [Number(member.id), member]));
      return [...teamLineupAssignments.value]
        .sort((a, b) => a.position - b.position)
        .map((entry) => {
          const member = memberMap.get(Number(entry.memberId));
          return member ? { ...member, position: entry.position } : null;
        })
        .filter(Boolean);
    });
    const availableLineupMembers = computed(() => {
      const selectedIds = new Set(teamLineupAssignments.value.map((entry) => Number(entry.memberId)));
      return eligibleLineupMembers.value.filter((member) => !selectedIds.has(Number(member.id)));
    });
    const teamLineupValidationMessage = computed(() => {
      const selected = selectedTeamLineupMembers.value;
      for (let index = 0; index < selected.length - 1; index += 1) {
        const currentMember = selected[index];
        const nextMember = selected[index + 1];
        const currentRating = getMemberLineupRatingValue(currentMember);
        const nextRating = getMemberLineupRatingValue(nextMember);
        if (!Number.isFinite(currentRating) || !Number.isFinite(nextRating)) {
          continue;
        }
        if (nextRating > currentRating + 30) {
          return t('teamManagement.lineupValidationTooLargeGap', {
            higher: `${nextMember.firstName} ${nextMember.lastName}`,
            lower: `${currentMember.firstName} ${currentMember.lastName}`
          });
        }
      }
      return '';
    });
    
    // Methods
    const normalizePlanningAssignments = (assignments) => assignments
      .filter((entry) => entry && entry.teamId && entry.memberId)
      .map((entry, index) => ({
        teamId: Number(entry.teamId),
        memberId: Number(entry.memberId),
        position: Number(entry.position) || (index + 1)
      }));

    const normalizePlanningTeamAssignments = () => {
      const byTeam = new Map();
      planningAssignments.value.forEach((entry) => {
        const key = Number(entry.teamId);
        if (!byTeam.has(key)) {
          byTeam.set(key, []);
        }
        byTeam.get(key).push(entry);
      });
      const normalized = [];
      byTeam.forEach((entries, teamId) => {
        entries
          .sort((a, b) => a.position - b.position)
          .forEach((entry, idx) => {
            normalized.push({
              teamId,
              memberId: Number(entry.memberId),
              position: idx + 1
            });
          });
      });
      planningAssignments.value = normalized;
    };

    const loadPlanningInterestedMemberIds = async () => {
      if (!selectedClub.value || !selectedSeasonId.value) {
        planningInterestedMemberIds.value = new Set();
        return;
      }
      const half = selectedLineupHalf.value || (isSecondHalf.value ? 'second_half' : 'first_half');
      try {
        const response = await apiClient.get(`/clubmembers/play-interest/${selectedClub.value}`, {
          params: {
            seasonId: selectedSeasonId.value,
            lineupHalf: half
          }
        });
        const rows = Array.isArray(response.data) ? response.data : [];
        planningInterestedMemberIds.value = new Set(
          rows
            .filter((entry) => entry?.interested !== false)
            .map((entry) => planningMemberKey(entry.memberId))
            .filter(Boolean)
        );
      } catch (error) {
        console.error('Fehler beim Laden der Spielinteressen:', error);
        planningInterestedMemberIds.value = new Set();
      }
    };

    const loadPlanningMembers = async () => {
      if (!selectedClub.value) return;
      try {
        const membersResp = await apiClient.get(`/clubmembers/get/${selectedClub.value}/true`);
        clubMembers.value = Array.isArray(membersResp.data) ? membersResp.data : [];
      } catch (error) {
        console.error('Fehler beim Laden der Planungs-Mitglieder:', error);
        clubMembers.value = [];
      }
    };

    const loadPlanningAssignments = async () => {
      if (!teams.value.length) {
        planningAssignments.value = [];
        planningLocalTeams.value = [];
        return;
      }
      const half = selectedLineupHalf.value || (isSecondHalf.value ? 'second_half' : 'first_half');
      const responses = await Promise.all(teams.value.map(async (team) => {
        try {
          const resp = await apiClient.get(`/club-teams/${team.id}/lineup`, { params: { half } });
          return {
            teamId: team.id,
            assignments: Array.isArray(resp.data) ? resp.data : []
          };
        } catch (error) {
          return { teamId: team.id, assignments: [] };
        }
      }));
      planningAssignments.value = normalizePlanningAssignments(
        responses.flatMap((entry) => entry.assignments.map((a, idx) => ({
          teamId: entry.teamId,
          memberId: a.memberId,
          position: a.position || (idx + 1)
        })))
      );
      planningLocalTeams.value = teams.value.map((team) => ({
        id: Number(team.id),
        name: team.name || '',
        plannedLeagueName: team.plannedLeagueName || '',
        teamGender: team.teamGender || 'open',
        teamAgeGroup: team.teamAgeGroup || 'adult',
        leagueId: team.leagueId || null
      }));
      removeAllIneligiblePlanningAssignments();
    };

    const onPlanningDragMember = (member) => {
      planningDraggingMemberId.value = Number(member?.id);
    };

    const isEligibleForPlanningTeam = (memberId, teamId) => {
      const team = planningLocalTeams.value.find((entry) => Number(entry.id) === Number(teamId));
      if (!team) return false;
      const member = planningMemberById.value.get(planningMemberKey(memberId));
      if (!member) return false;
      const teamAgeGroup = team.teamAgeGroup || 'adult';
      const teamGender = team.teamGender || 'open';
      return isEligibleForTeam(member, teamAgeGroup, teamGender);
    };

    const removeIneligibleAssignmentsForTeam = (teamId) => {
      const before = planningAssignments.value.length;
      planningAssignments.value = planningAssignments.value.filter((entry) => (
        Number(entry.teamId) !== Number(teamId) || isEligibleForPlanningTeam(entry.memberId, entry.teamId)
      ));
      if (planningAssignments.value.length !== before) {
        normalizePlanningTeamAssignments();
        schedulePlanningTeamAutosave(Number(teamId), 300);
      }
    };

    const removeAllIneligiblePlanningAssignments = () => {
      const allEntries = [...planningAssignments.value];
      const affectedTeamIds = new Set(
        allEntries
          .filter((entry) => !isEligibleForPlanningTeam(entry.memberId, entry.teamId))
          .map((entry) => Number(entry.teamId))
      );
      if (!affectedTeamIds.size) return;

      planningAssignments.value = allEntries.filter((entry) => (
        isEligibleForPlanningTeam(entry.memberId, entry.teamId)
      ));
      normalizePlanningTeamAssignments();
      affectedTeamIds.forEach((teamId) => schedulePlanningTeamAutosave(teamId, 300));
    };

    const setPlanningTeamStatus = (teamId, state) => {
      const key = String(teamId);
      planningTeamSaveStatus.value = {
        ...planningTeamSaveStatus.value,
        [key]: { state }
      };

      const existingClearTimer = planningStatusClearTimers.get(key);
      if (existingClearTimer) {
        clearTimeout(existingClearTimer);
        planningStatusClearTimers.delete(key);
      }

      if (state === 'saved') {
        const clearTimer = setTimeout(() => {
          const next = { ...planningTeamSaveStatus.value };
          delete next[key];
          planningTeamSaveStatus.value = next;
          planningStatusClearTimers.delete(key);
        }, 4500);
        planningStatusClearTimers.set(key, clearTimer);
      }
    };

    const getPlanningTeamLineupAssignments = (teamId) => (
      planningAssignments.value
        .filter((entry) => Number(entry.teamId) === Number(teamId))
        .sort((a, b) => a.position - b.position)
        .map((entry, idx) => ({
          memberId: Number(entry.memberId),
          position: idx + 1
        }))
    );

    const persistPlanningTeam = async (teamId) => {
      const localTeam = planningLocalTeams.value.find((entry) => Number(entry.id) === Number(teamId));
      if (!localTeam || !selectedClub.value || !selectedSeasonId.value) return;
      const sourceTeam = teams.value.find((entry) => Number(entry.id) === Number(teamId));
      if (!sourceTeam) return;

      setPlanningTeamStatus(teamId, 'saving');
      try {
        await apiClient.put(`/club-teams/${teamId}`, {
          name: (localTeam.name || sourceTeam.name || '').trim(),
          leagueId: sourceTeam.leagueId || null,
          seasonId: sourceTeam.seasonId || selectedSeasonId.value,
          teamGender: localTeam.teamGender || sourceTeam.teamGender || 'open',
          teamAgeGroup: localTeam.teamAgeGroup || sourceTeam.teamAgeGroup || 'adult',
          plannedLeagueName: (localTeam.plannedLeagueName || '').trim() || null
        });

        await apiClient.put(`/club-teams/${teamId}/lineup`, {
          assignments: getPlanningTeamLineupAssignments(teamId),
          lineupHalf: selectedLineupHalf.value
        });

        teams.value = teams.value.map((entry) => (
          Number(entry.id) === Number(teamId)
            ? {
              ...entry,
              name: (localTeam.name || sourceTeam.name || '').trim(),
              teamGender: localTeam.teamGender || sourceTeam.teamGender || 'open',
              teamAgeGroup: localTeam.teamAgeGroup || sourceTeam.teamAgeGroup || 'adult',
              plannedLeagueName: (localTeam.plannedLeagueName || '').trim() || null
            }
            : entry
        ));
        setPlanningTeamStatus(teamId, 'saved');
      } catch (error) {
        console.error('Fehler beim automatischen Speichern der Team-Planung:', error);
        setPlanningTeamStatus(teamId, 'error');
      }
    };

    const schedulePlanningTeamAutosave = (teamId, delayMs = 700) => {
      const key = String(teamId);
      const existingTimer = planningAutosaveTimers.get(key);
      if (existingTimer) {
        clearTimeout(existingTimer);
      }
      const timer = setTimeout(async () => {
        planningAutosaveTimers.delete(key);
        await persistPlanningTeam(teamId);
      }, delayMs);
      planningAutosaveTimers.set(key, timer);
    };

    const onPlanningDropToTeam = async (teamId) => {
      const memberId = Number(planningDraggingMemberId.value);
      const targetTeamId = Number(teamId);
      if (!memberId || !targetTeamId) return;
      if (!isEligibleForPlanningTeam(memberId, targetTeamId)) {
        await showInfo(
          t('messages.warning'),
          'Dieses Mitglied ist für die gewählte Mannschaft nicht spielberechtigt (Geschlecht/Altersklasse).',
          '',
          'warning'
        );
        planningDraggingMemberId.value = null;
        return;
      }
      const previousEntry = planningAssignments.value.find((entry) => Number(entry.memberId) === memberId);
      const sourceTeamId = previousEntry ? Number(previousEntry.teamId) : null;
      const withoutMember = planningAssignments.value.filter((entry) => Number(entry.memberId) !== memberId);
      const targetEntries = withoutMember.filter((entry) => Number(entry.teamId) === targetTeamId);
      withoutMember.push({ teamId: targetTeamId, memberId, position: targetEntries.length + 1 });
      planningAssignments.value = withoutMember;
      normalizePlanningTeamAssignments();
      schedulePlanningTeamAutosave(targetTeamId, 300);
      if (sourceTeamId && sourceTeamId !== targetTeamId) {
        schedulePlanningTeamAutosave(sourceTeamId, 300);
      }
      planningDraggingMemberId.value = null;
    };

    const onPlanningDropToUnassigned = () => {
      const memberId = Number(planningDraggingMemberId.value);
      if (!memberId) return;
      const sourceEntry = planningAssignments.value.find((entry) => Number(entry.memberId) === memberId);
      const sourceTeamId = sourceEntry ? Number(sourceEntry.teamId) : null;
      planningAssignments.value = planningAssignments.value.filter((entry) => Number(entry.memberId) !== memberId);
      normalizePlanningTeamAssignments();
      if (sourceTeamId) {
        schedulePlanningTeamAutosave(sourceTeamId, 300);
      }
      planningDraggingMemberId.value = null;
    };

    const onPlanningMoveInsideTeam = ({ laneId, memberId, direction }) => {
      const teamId = Number(laneId);
      const filtered = [...planningAssignments.value].sort((a, b) => a.position - b.position);
      const teamEntries = filtered.filter((entry) => Number(entry.teamId) === teamId);
      const index = teamEntries.findIndex((entry) => Number(entry.memberId) === Number(memberId));
      if (index < 0) return;
      const target = direction === 'up' ? index - 1 : index + 1;
      if (target < 0 || target >= teamEntries.length) return;
      const [entry] = teamEntries.splice(index, 1);
      teamEntries.splice(target, 0, entry);
      const others = filtered.filter((entry) => Number(entry.teamId) !== teamId);
      planningAssignments.value = [
        ...others,
        ...teamEntries.map((entry, idx) => ({ ...entry, position: idx + 1 }))
      ];
      normalizePlanningTeamAssignments();
      schedulePlanningTeamAutosave(teamId, 300);
    };

    const onPlanningRemoveFromTeam = ({ laneId, memberId }) => {
      planningAssignments.value = planningAssignments.value.filter((entry) => Number(entry.memberId) !== Number(memberId));
      normalizePlanningTeamAssignments();
      if (laneId) {
        schedulePlanningTeamAutosave(Number(laneId), 300);
      }
    };

    const onPlanningMarkMemberInterested = async ({ memberId }) => {
      const normalizedMemberId = planningMemberKey(memberId);
      if (!normalizedMemberId || markingPlanningInterest.value) return;
      if (!selectedClub.value) {
        await showInfo(t('messages.warning'), 'Bitte zuerst einen Verein auswählen.', '', 'warning');
        return;
      }
      if (!selectedSeasonId.value) {
        await showInfo(t('messages.warning'), 'Bitte zuerst eine Saison auswählen.', '', 'warning');
        return;
      }
      const member = (clubMembers.value || []).find((entry) => planningMemberKey(entry.id) === normalizedMemberId);
      if (!member) {
        await showInfo(t('messages.warning'), 'Bitte zuerst ein Mitglied auswählen.', '', 'warning');
        return;
      }

      markingPlanningInterest.value = true;
      try {
        const half = selectedLineupHalf.value || (isSecondHalf.value ? 'second_half' : 'first_half');
        await apiClient.post(`/clubmembers/play-interest/${selectedClub.value}`, {
          memberId: member.id,
          seasonId: selectedSeasonId.value,
          lineupHalf: half,
          interested: true
        });
        await loadPlanningInterestedMemberIds();
      } catch (error) {
        await showInfo(t('messages.error'), t('members.errorSavingMember'), '', 'error');
      } finally {
        markingPlanningInterest.value = false;
      }
    };

    const updatePlanningTeamField = (teamId, field, value) => {
      planningLocalTeams.value = planningLocalTeams.value.map((team) => (
        Number(team.id) === Number(teamId) ? { ...team, [field]: value } : team
      ));
      if (field === 'teamAgeGroup' || field === 'teamGender') {
        removeIneligibleAssignmentsForTeam(teamId);
      }
      schedulePlanningTeamAutosave(Number(teamId));
    };

    const addPlanningTeam = async () => {
      if (!selectedClub.value || !selectedSeasonId.value) return;
      const nextIndex = planningLocalTeams.value.length + 1;
      try {
        const payload = {
          name: `${t('teamManagement.teamName')} ${nextIndex}`,
          leagueId: null,
          seasonId: selectedSeasonId.value,
          teamGender: 'open',
          teamAgeGroup: 'adult',
          plannedLeagueName: ''
        };
        await apiClient.post(`/club-teams/club/${selectedClub.value}`, payload);
        await loadTeams();
      } catch (error) {
        await showInfo(t('messages.error'), t('teamManagement.lineupSaveError'), '', 'error');
      }
    };

    const removePlanningTeam = async (teamId) => {
      const team = teams.value.find((entry) => Number(entry.id) === Number(teamId));
      if (!team) return;
      const confirmed = await showConfirm(t('messages.warning'), t('teamManagement.reallyDeleteTeam', { teamName: team.name || '' }), '', 'warning');
      if (!confirmed) return;
      try {
        await apiClient.delete(`/club-teams/${teamId}`);
        planningAssignments.value = planningAssignments.value.filter((entry) => Number(entry.teamId) !== Number(teamId));
        await loadTeams();
      } catch (error) {
        await showInfo(t('messages.error'), t('teamManagement.deleteTeamError'), '', 'error');
      }
    };

    const convertPlanningTeamToRegular = async (teamId) => {
      const team = teams.value.find((entry) => Number(entry.id) === Number(teamId));
      if (!team) return;

      activeMainSection.value = 'overview';
      await editTeam(team);
      teamFormIsOpen.value = true;
      activeEditorSection.value = 'basic';

      if (!team.leagueId && !String(team?.plannedLeagueName || '').trim()) {
        await showInfo(
          t('teamManagement.editTeam'),
          t('teamManagement.leagueFieldRequired'),
          '',
          'info'
        );
      }
    };

    const toggleNewTeam = () => {
      teamFormIsOpen.value = !teamFormIsOpen.value;
      if (!teamFormIsOpen.value) {
        resetNewTeam();
      }
    };
    
    const resetToNewTeam = () => {
      teamToEdit.value = null;
      activeEditorSection.value = 'basic';
      resetNewTeam();
    };
    
    const resetNewTeam = () => {
      newTeamName.value = '';
      newLeagueId.value = '';
      newTeamGender.value = 'open';
      newTeamAgeGroup.value = 'adult';
      newPlannedLeagueName.value = '';
      activeEditorSection.value = 'basic';
    };
    
    const loadTeams = async () => {
      if (!selectedClub.value || !selectedSeasonId.value) {
        teams.value = [];
        planningLocalTeams.value = [];
        planningAssignments.value = [];
        return;
      }
      
      try {
        const response = await apiClient.get(`/club-teams/club/${selectedClub.value}?seasonid=${selectedSeasonId.value}`);
        teams.value = response.data;
        
        // Lade alle Team-Dokumente nach dem Laden der Teams
        await loadAllTeamDocuments();
        
        // Aktualisiere Job-Informationen, damit Team-Filterung korrekt funktioniert
        await loadSchedulerJobsInfo();
        await loadPlanningMembers();
        await loadPlanningInterestedMemberIds();
        await loadPlanningAssignments();
      } catch (error) {
        console.error('Fehler beim Laden der Club-Teams:', error);
      }
    };
    
    const loadLeagues = async () => {
      if (!selectedClub.value) return;
      
      try {
        const seasonParam = selectedSeasonId.value ? `?seasonid=${selectedSeasonId.value}` : '';
        const response = await apiClient.get(`/club-teams/leagues/${selectedClub.value}${seasonParam}`);
        leagues.value = response.data;
      } catch (error) {
        console.error('Fehler beim Laden der Spielklassen:', error);
      }
    };
    
    const addNewTeam = async () => {
      if (!newTeamName.value.trim() || !selectedClub.value || !selectedSeasonId.value) return;
      
      try {
        const normalizedLeagueId = newLeagueId.value || null;
        const shouldClearPlannedLeague =
          !!teamToEdit.value &&
          !!normalizedLeagueId &&
          !!String(teamToEdit.value?.plannedLeagueName || '').trim();
        const teamData = {
          name: newTeamName.value.trim(),
          leagueId: normalizedLeagueId,
          seasonId: selectedSeasonId.value,
          teamGender: newTeamGender.value,
          teamAgeGroup: newTeamAgeGroup.value,
          plannedLeagueName: shouldClearPlannedLeague ? null : (newPlannedLeagueName.value.trim() || null)
        };
        
        if (teamToEdit.value) {
          // Bearbeitung eines bestehenden Teams
          await apiClient.put(`/club-teams/${teamToEdit.value.id}`, teamData);
          await loadTeams();
          resetNewTeam();
          teamFormIsOpen.value = false;
          teamToEdit.value = null;
        } else {
          // Erstellen eines neuen Teams
          const response = await apiClient.post(`/club-teams/club/${selectedClub.value}`, teamData);
          const newTeam = response.data;
          
          await loadTeams();
          
          // Neues Team automatisch auswählen und bearbeiten
          teamToEdit.value = newTeam;
          newTeamName.value = newTeam.name;
          newLeagueId.value = newTeam.leagueId || '';
          newPlannedLeagueName.value = newTeam.plannedLeagueName || '';
          teamFormIsOpen.value = true; // Formular bleibt offen für weitere Bearbeitung
        }
      } catch (error) {
        console.error('Fehler beim Speichern des Club-Teams:', error);
      }
    };
    
    const editTeam = async (team) => {
      teamToEdit.value = team;
      newTeamName.value = team.name;
      newLeagueId.value = team.leagueId || '';
      newPlannedLeagueName.value = team.plannedLeagueName || '';
      newTeamGender.value = getConfiguredTeamGender(team);
      newTeamAgeGroup.value = getConfiguredTeamAgeGroup(team);
      teamFormIsOpen.value = true;
      activeEditorSection.value = 'basic';
      await loadTeamDocuments();
      // Aktualisiere Job-Informationen, damit Team-spezifische Daten aktuell sind
      await loadSchedulerJobsInfo();
    };
    
    const deleteTeam = async (team) => {
      const confirmed = await showConfirm(
        t('teamManagement.deleteTeamTitle'),
        t('teamManagement.deleteTeamConfirm', { name: team.name }),
        '',
        'danger'
      );
      if (!confirmed) {
        return;
      }
      
      try {
        await apiClient.delete(`/club-teams/${team.id}`);
        await loadTeams();
      } catch (error) {
        console.error('Fehler beim Löschen des Club-Teams:', error);
        await showInfo(t('messages.error'), getSafeErrorMessage(error, t('teamManagement.errorDeletingTeam')), '', 'error');
      }
    };

    const uploadCodeList = () => {
      if (!teamToEdit.value) return;
      if (parsingInProgress.value) {
        return;
      }
      
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = '.pdf,.doc,.docx,.txt,.csv';
      input.onchange = async (event) => {
        const file = event.target.files[0];
        if (!file) return;
        await uploadAndParseDocument(file, 'code_list');
      };
      input.click();
    };

    const uploadPinList = () => {
      if (!teamToEdit.value) return;
      if (parsingInProgress.value) {
        return;
      }
      
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = '.pdf,.doc,.docx,.txt,.csv';
      input.onchange = async (event) => {
        const file = event.target.files[0];
        if (!file) return;
        await uploadAndParseDocument(file, 'pin_list');
      };
      input.click();
    };
    
    const formatDate = (dateString) => {
      return new Date(dateString).toLocaleDateString('de-DE');
    };

    const formatFileSize = (bytes) => {
      if (bytes === 0) return '0 Bytes';
      const k = 1024;
      const sizes = ['Bytes', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    };
    
    const formatJobDate = (dateString) => {
      if (!dateString) return t('myTischtennisAccount.never');
      const date = new Date(dateString);
      const now = new Date();
      const diffMs = now - date;
      const diffMins = Math.floor(diffMs / 60000);
      const diffHours = Math.floor(diffMs / 3600000);
      const diffDays = Math.floor(diffMs / 86400000);
      
      if (diffMins < 1) return t('myTischtennisAccount.justNow');
      if (diffMins < 60) return t('myTischtennisAccount.minutesAgo', { count: diffMins });
      if (diffHours < 24) return t('myTischtennisAccount.hoursAgo', { count: diffHours });
      if (diffDays === 1) return t('myTischtennisAccount.yesterday');
      if (diffDays < 7) return t('myTischtennisAccount.daysAgo', { count: diffDays });
      
      // Format: DD.MM.YYYY HH:MM
      return date.toLocaleDateString('de-DE', {
        day: '2-digit',
        month: '2-digit',
        year: 'numeric',
        hour: '2-digit',
        minute: '2-digit'
      });
    };

    const loadTeamDocuments = async () => {
      if (!teamToEdit.value) return;
      
      try {
        const response = await apiClient.get(`/team-documents/club-team/${teamToEdit.value.id}`);
        teamDocuments.value = response.data;
      } catch (error) {
        console.error('Fehler beim Laden der Team-Dokumente:', error);
      }
    };
    
    const loadAllTeamDocuments = async () => {
      if (!selectedClub.value) return;
      
      try {
        // Lade alle Dokumente für alle Teams des Clubs
        const allDocuments = [];
        for (const team of teams.value) {
          try {
            const response = await apiClient.get(`/team-documents/club-team/${team.id}`);
            allDocuments.push(...response.data);
          } catch (error) {
            continue;
          }
        }
        teamDocuments.value = allDocuments;
      } catch (error) {
        console.error('Fehler beim Laden aller Team-Dokumente:', error);
      }
    };

    const uploadAndParseDocument = async (file, documentType) => {
      if (!teamToEdit.value?.leagueId) {
        await showInfo(
          t('messages.warning'),
          t('teamManagement.teamHasNoLeague'),
          t('teamManagement.assignLeagueBeforeDocuments'),
          'warning'
        );
        return;
      }

      if (parsingInProgress.value) {
        return;
      }

      parsingInProgress.value = true;

      try {
        const documentLabel = documentType === 'code_list' ? t('teamManagement.codeList') : t('teamManagement.pinList');
        const isValid = await validateTeamDocumentFile(file, documentLabel);
        if (!isValid) {
          return;
        }

        const formData = new FormData();
        formData.append('document', file);
        formData.append('documentType', documentType);

        const uploadResponse = await apiClient.post(`/team-documents/club-team/${teamToEdit.value.id}/upload`, formData, {
          headers: {
            'Content-Type': 'multipart/form-data'
          }
        });

        const fileExtension = file.name.toLowerCase().split('.').pop();

        if (fileExtension === 'pdf' || fileExtension === 'txt') {
          const parseResponse = await apiClient.post(`/team-documents/${uploadResponse.data.id}/parse?leagueid=${teamToEdit.value.leagueId}`);
          const { parseResult, saveResult } = parseResponse.data;

          let message = t('teamManagement.documentParsedSummary', {
            label: documentLabel,
            matchesFound: parseResult.matchesFound,
            created: saveResult.created,
            updated: saveResult.updated
          });

          if (saveResult.errors.length > 0) {
            message += `\n\n${t('teamManagement.errorsCount', { count: saveResult.errors.length })}\n`;
            message += saveResult.errors.slice(0, 3).join('\n');
            if (saveResult.errors.length > 3) {
              message += `\n${t('teamManagement.moreErrors', { count: saveResult.errors.length - 3 })}`;
            }
          }

          let dialogTitle = t('messages.success');
          let dialogType = 'success';

          if (parseResult.matchesFound === 0) {
            dialogTitle = t('teamManagement.noMatchesFoundTitle');
            dialogType = 'warning';
            if (parseResult.debugInfo) {
              message += `\n\n${t('teamManagement.noMatchesFoundDetails', { lines: parseResult.debugInfo.totalLines })}`;
            }
          } else if (saveResult.errors.length > 0) {
            dialogTitle = t('messages.warning');
            dialogType = 'warning';
          }

          await showInfo(dialogTitle, message, '', dialogType);
        } else {
          await showInfo(t('messages.info'), t('teamManagement.documentUploaded', { label: documentLabel, fileName: file.name }), '', 'info');
        }

        await loadTeamDocuments();
      } catch (error) {
        console.error('Fehler beim Hochladen und Parsen der Datei:', error);
        const message = getSafeErrorMessage(error, t('teamManagement.errorUploadingDocument'));
        await showInfo(t('messages.error'), message, '', 'error');
      } finally {
        parsingInProgress.value = false;
      }
    };

    const parsePDF = async (document) => {
      const team = teams.value.find(t => t.id === document.clubTeamId);
      if (!team || !team.leagueId) {
        await showInfo(
          t('messages.warning'),
          t('teamManagement.teamHasNoLeague'),
          t('teamManagement.assignLeagueBeforeParsing'),
          'warning'
        );
        return;
      }

      if (parsingDocuments.value[document.id]) {
        return;
      }

      parsingDocuments.value = {
        ...parsingDocuments.value,
        [document.id]: true
      };
      
      try {
        const response = await apiClient.post(`/team-documents/${document.id}/parse?leagueid=${team.leagueId}`);
        const { parseResult, saveResult } = response.data;

        let message = t('teamManagement.matchesSummary', {
          matchesFound: parseResult.matchesFound,
          created: saveResult.created,
          updated: saveResult.updated
        });

        let dialogTitle = t('messages.success');
        let dialogType = 'success';

        if (parseResult.matchesFound === 0) {
          dialogTitle = t('teamManagement.noMatchesFoundTitle');
          dialogType = 'warning';
          if (parseResult.debugInfo) {
            message += `\n\n${t('teamManagement.noMatchesFoundDetails', { lines: parseResult.debugInfo.totalLines })}`;
          }
        } else if (saveResult.errors.length > 0) {
          dialogTitle = t('messages.warning');
          dialogType = 'warning';
          message += `\n\n${t('teamManagement.errorsCount', { count: saveResult.errors.length })}\n`;
          message += saveResult.errors.slice(0, 3).join('\n');
          if (saveResult.errors.length > 3) {
            message += `\n${t('teamManagement.moreErrors', { count: saveResult.errors.length - 3 })}`;
          }
        }

        await showInfo(dialogTitle, message, '', dialogType);
        await loadTeamDocuments();
      } catch (error) {
        console.error('Fehler beim Parsen der PDF:', error);
        const responseData = error?.response?.data || {};
        const status = error?.response?.status;
        let errorMessage = getSafeErrorMessage(error, t('teamManagement.errorParsingPdf'));
        let details = '';

        if (status === 404 && responseData.error === 'documentnotfound') {
          errorMessage = t('teamManagement.documentNotFound');
        } else if (status === 400 && responseData.error === 'missingleagueid') {
          errorMessage = t('teamManagement.missingLeagueForTeam');
        } else if (error.code === 'ENOENT' || (typeof error?.message === 'string' && error.message.includes('ENOENT'))) {
          errorMessage = t('teamManagement.pdfFileNotFound');
          details = t('teamManagement.reuploadFile');
        }

        await showInfo(t('messages.error'), errorMessage, details, 'error');
      } finally {
        const { [document.id]: _ignored, ...rest } = parsingDocuments.value;
        parsingDocuments.value = rest;
      }
    };
    
    const onSeasonChange = async (season) => {
      if (season) {
        currentSeason.value = season;
        selectedSeasonId.value = season.id;
        await loadTeams();
        await loadLeagues();
      }
    };
    
    // Load scheduler jobs info
    const loadSchedulerJobsInfo = async () => {
      try {
        const clubIdParam = selectedClub.value ? `?clubId=${selectedClub.value}` : '';
        const response = await apiClient.get(`/logs/scheduler/last-executions${clubIdParam}`);
        if (response.data.success) {
          schedulerJobs.value = response.data.data;
        }
      } catch (error) {
        console.error('Fehler beim Laden der Job-Informationen:', error);
      }
    };
    
    // Filter team details to only show teams from current club
    const getFilteredTeamDetails = (teamDetails) => {
      if (!teamDetails || !selectedClub.value) return teamDetails;
      // Filter by checking if team exists in current club's teams
      return teamDetails.filter(team => {
        return teams.value.some(t => t.id === team.clubTeamId);
      });
    };
    
    // Get job info for a specific team
    const getTeamJobInfo = (team) => {
      if (!team || !team.id || !schedulerJobs.value.match_results?.teamDetails) {
        return null;
      }
      
      const teamDetail = schedulerJobs.value.match_results.teamDetails.find(
        td => td.clubTeamId === team.id
      );
      
      if (!teamDetail) {
        return null;
      }
      
      return {
        lastRun: teamDetail.lastRun,
        success: teamDetail.success
      };
    };
    
    // Lifecycle
    onMounted(async () => {
      // Lade Ligen beim ersten Laden der Seite (ohne Saison-Filter)
      await loadLeagues();
      // Lade Job-Informationen
      await loadSchedulerJobsInfo();
      await loadPlanningMembers();
      await loadPlanningInterestedMemberIds();
      
      // Warte kurz, damit SeasonSelector die Saison setzen kann
      // Dann lade Teams, falls eine Saison ausgewählt wurde
      setTimeout(() => {
        if (selectedSeasonId.value && selectedClub.value) {
          loadTeams();
        }
      }, 100);
    });
    
    // PDF-Dialog Funktionen
    const getTeamDocuments = (teamId, documentType) => {
      return teamDocuments.value.filter(doc => 
        doc.clubTeamId === teamId && doc.documentType === documentType
      );
    };

    const getLatestTeamDocument = (teamId, documentType) => {
      return getTeamDocuments(teamId, documentType)
        .slice()
        .sort((a, b) => new Date(b.createdAt || 0) - new Date(a.createdAt || 0))[0] || null;
    };
    
    const showPDFDialog = async (teamId, documentType) => {
      const documents = getTeamDocuments(teamId, documentType);
      if (documents.length === 0) return;
      
      const document = documents[0]; // Nehme das erste Dokument
      const team = teams.value.find(t => t.id === teamId);
      
      pdfDialogTitle.value = `${team?.name || t('teamManagement.team')} - ${documentType === 'code_list' ? t('teamManagement.codeList') : t('teamManagement.pinList')}`;
      
      try {
        // Lade das PDF über die API
        const response = await apiClient.get(`/team-documents/${document.id}/download`, {
          responseType: 'blob'
        });
        
        // Erstelle eine URL für das PDF
        const blob = new Blob([response.data], { type: 'application/pdf' });
        pdfUrl.value = URL.createObjectURL(blob);
        showPDFViewer.value = true;
      } catch (error) {
        console.error('Fehler beim Laden des PDFs:', error);
        await showInfo(t('messages.error'), t('teamManagement.errorLoadingPdf'), '', 'error');
      }
    };
    
    const closePDFDialog = () => {
      showPDFViewer.value = false;
      if (pdfUrl.value) {
        URL.revokeObjectURL(pdfUrl.value);
        pdfUrl.value = '';
      }
    };
    
    // Dialog Helper Methods
    const showInfo = async (title, message, details = '', type = 'info') => {
      infoDialog.value = buildInfoConfig({ title, message, details, type });
    };
    
    const showConfirm = async (title, message, details = '', type = 'info', options = {}) => {
      return new Promise((resolve) => {
        confirmDialog.value = buildConfirmConfig({
          title,
          message,
          details,
          type,
          resolveCallback: resolve,
          ...options,
        });
      });
    };
    
    const handleConfirmResult = (confirmed) => {
      if (confirmDialog.value.resolveCallback) {
        confirmDialog.value.resolveCallback(confirmed);
        confirmDialog.value.resolveCallback = null;
      }
      confirmDialog.value.isOpen = false;
    };
    
    // MyTischtennis URL Parser Methods
    const parseMyTischtennisUrl = async () => {
      if (!myTischtennisUrl.value || !myTischtennisUrl.value.trim()) {
        return;
      }
      
      parsingUrl.value = true;
      myTischtennisError.value = '';
      myTischtennisSuccess.value = '';
      parsedMyTischtennisData.value = null;
      
      try {
        const response = await apiClient.post('/mytischtennis/parse-url', {
          url: myTischtennisUrl.value.trim()
        });
        
        if (response.data.success) {
          parsedMyTischtennisData.value = response.data.data;
          
          // Automatisch konfigurieren
          await configureTeamFromUrl();
        }
      } catch (error) {
        console.error('Fehler beim Parsen der URL:', error);
        myTischtennisError.value = getSafeErrorMessage(error, t('teamManagement.errorParsingUrl'));
        await showInfo(t('messages.error'), myTischtennisError.value, '', 'error');
      } finally {
        parsingUrl.value = false;
      }
    };
    
    const configureTeamFromUrl = async () => {
      if (!parsedMyTischtennisData.value) {
        return;
      }
      
      // Für Tabellen-URLs: Biete Liga-Konfiguration an
      if (parsedMyTischtennisData.value.urlType === 'table') {
        const confirmed = await showConfirm(
          t('teamManagement.configureLeagueTitle'),
          t('teamManagement.tableUrlDetected'),
          t('teamManagement.configureLeagueDetails', {
            association: parsedMyTischtennisData.value.association,
            season: parsedMyTischtennisData.value.season,
            league: parsedMyTischtennisData.value.groupname,
            groupId: parsedMyTischtennisData.value.groupId
          }),
          'info'
        );
        
        if (confirmed) {
          await configureLeagueFromUrl();
        }
        return;
      }
      
      // Für Team-URLs: Normale Konfiguration
      if (!teamToEdit.value) {
        await showInfo(
          t('teamManagement.selectTeamTitle'),
          t('teamManagement.selectTeamFirst'),
          t('teamManagement.selectTeamForConfiguration'),
          'warning'
        );
        return;
      }
      
      configuringTeam.value = true;
      myTischtennisError.value = '';
      myTischtennisSuccess.value = '';
      
      try {
        const response = await apiClient.post('/mytischtennis/configure-team', {
          url: myTischtennisUrl.value.trim(),
          clubTeamId: teamToEdit.value.id,
          createLeague: true,
          createSeason: true
        });
        
        if (response.data.success) {
          myTischtennisSuccess.value = t('teamManagement.teamConfiguredSuccess');
          await showInfo(
            t('messages.success'),
            t('teamManagement.teamConfiguredSuccess'),
            t('teamManagement.teamConfiguredDetails', {
              league: response.data.data.league.name,
              season: response.data.data.season.name
            }),
            'success'
          );
          
          // Teams und Ligen neu laden
          await loadTeams();
          await loadLeagues();
          
          // Aktuelles Team mit neuen Daten aktualisieren
          if (teamToEdit.value) {
            const updatedTeam = teams.value.find(t => t.id === teamToEdit.value.id);
            if (updatedTeam) {
              teamToEdit.value = updatedTeam;
              // Team-Dokumente neu laden
              await loadTeamDocuments();
            }
          }
          
          // Parsed Data löschen
          clearParsedData();
        }
      } catch (error) {
        console.error('Fehler bei der Konfiguration:', error);
        myTischtennisError.value = getSafeErrorMessage(error, t('teamManagement.errorConfiguringTeam'));
        await showInfo(t('messages.error'), myTischtennisError.value, '', 'error');
      } finally {
        configuringTeam.value = false;
      }
    };
    
    const configureLeagueFromUrl = async () => {
      if (!parsedMyTischtennisData.value || parsedMyTischtennisData.value.urlType !== 'table' || !selectedClub.value) {
        return;
      }
      
      configuringTeam.value = true;
      myTischtennisError.value = '';
      myTischtennisSuccess.value = '';
      
      try {
        const response = await apiClient.post('/mytischtennis/configure-league', {
          url: myTischtennisUrl.value.trim(),
          clubId: selectedClub.value,
          createSeason: true
        });
        
        if (response.data.success) {
          myTischtennisSuccess.value = t('teamManagement.leagueConfiguredSuccess');
          await showInfo(
            t('messages.success'),
            t('teamManagement.leagueConfiguredSuccess'),
            t('teamManagement.leagueConfiguredDetails', {
              league: response.data.data.league.name,
              season: response.data.data.season?.name || t('teamManagement.notCreated'),
              association: response.data.data.league.association,
              groupId: response.data.data.league.myTischtennisGroupId
            }),
            'success'
          );
          
          // Teams und Ligen neu laden
          await loadTeams();
          await loadLeagues();
          
          // Parsed Data löschen
          clearParsedData();
        }
      } catch (error) {
        console.error('Fehler bei der Liga-Konfiguration:', error);
        myTischtennisError.value = getSafeErrorMessage(error, t('teamManagement.errorConfiguringLeague'));
        await showInfo(t('messages.error'), myTischtennisError.value, '', 'error');
      } finally {
        configuringTeam.value = false;
      }
    };
    
    const clearParsedData = () => {
      parsedMyTischtennisData.value = null;
      myTischtennisUrl.value = '';
      myTischtennisError.value = '';
      myTischtennisSuccess.value = '';
    };
    
    const getMyTischtennisStatus = (team) => {
      const empty = {
        complete: false,
        partial: false,
        missing: '',
        missingItemLabels: [],
        tooltip: '',
        needsLeague: false,
        needsTeamId: false,
        needsLeagueGroupId: false,
        needsLeagueAssociation: false,
        needsLeagueGroupName: false
      };
      if (!team) {
        return { ...empty };
      }

      const hasTeamId = !!team.myTischtennisTeamId;
      const hasLeague = !!team.league;
      const hasPlannedLeague = !!String(team?.plannedLeagueName || '').trim();
      // Für den Planungs-Workflow gilt ein geplanter Liga-Text als valider Zwischenstand.
      const hasLeagueForWorkflow = hasLeague || hasPlannedLeague;
      const hasLeagueConfig = hasLeague &&
        !!team.league.myTischtennisGroupId &&
        !!team.league.association &&
        !!team.league.groupname;

      const missingItems = [];
      if (!hasTeamId) missingItems.push(t('teamManagement.teamId'));
      if (!hasLeagueForWorkflow) missingItems.push(t('teamManagement.league'));
      if (hasLeague && !team.league.myTischtennisGroupId) missingItems.push(t('teamManagement.groupId'));
      if (hasLeague && !team.league.association) missingItems.push(t('teamManagement.association'));
      if (hasLeague && !team.league.groupname) missingItems.push(t('teamManagement.groupName'));

      const complete = hasTeamId && hasLeagueConfig;
      const partial = (hasTeamId || hasLeagueConfig || hasPlannedLeague) && !complete;

      let tooltip = '';
      if (complete) {
        tooltip = t('teamManagement.autoFetchEnabled');
      } else if (partial) {
        tooltip = t('teamManagement.missingItems', { items: missingItems.join(', ') });
      } else {
        tooltip = missingItems.length > 0
          ? t('teamManagement.missingItems', { items: missingItems.join(', ') })
          : t('teamManagement.enterUrlForAutoConfig');
      }

      return {
        complete,
        partial,
        missing: missingItems.length > 0 ? t('teamManagement.missingItems', { items: missingItems.join(', ') }) : '',
        missingItemLabels: [...missingItems],
        tooltip,
        needsLeague: !hasLeagueForWorkflow,
        needsTeamId: !hasTeamId,
        needsLeagueGroupId: !!(hasLeague && !team.league.myTischtennisGroupId),
        needsLeagueAssociation: !!(hasLeague && !team.league.association),
        needsLeagueGroupName: !!(hasLeague && !team.league.groupname)
      };
    };

    const editorTabNeedsAttention = (section) => {
      const team = teamToEdit.value;
      if (!team) return false;
      const s = getMyTischtennisStatus(team);
      if (s.complete) return false;
      if (section === 'basic') return s.needsLeague;
      if (section === 'myTischtennis') {
        if (s.needsLeague) return false;
        return !s.complete;
      }
      return false;
    };
    
    const loadClubMembers = async () => {
      if (!teamToEdit.value) {
        return;
      }

      loadingLineupMembers.value = true;
      try {
        const membersResp = await apiClient.get(`/clubmembers/get/${selectedClub.value}/true`);
        const map = {};
        clubMembers.value = membersResp.data || [];
        for (const m of clubMembers.value) {
          map[m.id] = { qttr: m.qttr ?? null };
        }
        memberById.value = map;
      } catch (e) {
        clubMembers.value = [];
        memberById.value = {};
      } finally {
        loadingLineupMembers.value = false;
      }
    };

    const normalizeTeamLineupAssignments = (assignments) => assignments
      .filter((entry) => entry && entry.memberId)
      .map((entry, index) => ({
        memberId: Number(entry.memberId),
        position: index + 1
      }));

    const getLineupValidationMessageForAssignments = (assignments) => {
      const memberMap = new Map(eligibleLineupMembers.value.map((member) => [Number(member.id), member]));
      const selected = normalizeTeamLineupAssignments(assignments)
        .map((entry) => memberMap.get(Number(entry.memberId)))
        .filter(Boolean);

      for (let index = 0; index < selected.length - 1; index += 1) {
        const currentMember = selected[index];
        const nextMember = selected[index + 1];
        const currentRating = getMemberLineupRatingValue(currentMember);
        const nextRating = getMemberLineupRatingValue(nextMember);
        if (!Number.isFinite(currentRating) || !Number.isFinite(nextRating)) {
          continue;
        }
        if (nextRating > currentRating + 30) {
          return t('teamManagement.lineupValidationTooLargeGap', {
            higher: `${nextMember.firstName} ${nextMember.lastName}`,
            lower: `${currentMember.firstName} ${currentMember.lastName}`
          });
        }
      }

      return '';
    };

    const getLineupMoveValidationMessage = (currentAssignments, oldIndex, newIndex) => {
      const normalizedAssignments = normalizeTeamLineupAssignments(currentAssignments);
      const memberMap = new Map(eligibleLineupMembers.value.map((member) => [Number(member.id), member]));
      const movedEntry = normalizedAssignments[oldIndex];
      if (!movedEntry) {
        return '';
      }

      const movedMember = memberMap.get(Number(movedEntry.memberId));
      const movedRating = getMemberLineupRatingValue(movedMember);
      if (!Number.isFinite(movedRating)) {
        return '';
      }

      const rangeStart = Math.min(oldIndex, newIndex);
      const rangeEnd = Math.max(oldIndex, newIndex);
      const crossedEntries = normalizedAssignments.slice(rangeStart, rangeEnd + 1)
        .filter((_, index) => (rangeStart + index) !== oldIndex);

      for (const crossedEntry of crossedEntries) {
        const crossedMember = memberMap.get(Number(crossedEntry.memberId));
        const crossedRating = getMemberLineupRatingValue(crossedMember);
        if (!Number.isFinite(crossedRating)) {
          continue;
        }

        if (newIndex < oldIndex) {
          if (crossedRating > movedRating + 30) {
            return t('teamManagement.lineupValidationTooLargeGap', {
              higher: `${crossedMember.firstName} ${crossedMember.lastName}`,
              lower: `${movedMember.firstName} ${movedMember.lastName}`
            });
          }
        } else if (movedRating > crossedRating + 30) {
          return t('teamManagement.lineupValidationTooLargeGap', {
            higher: `${movedMember.firstName} ${movedMember.lastName}`,
            lower: `${crossedMember.firstName} ${crossedMember.lastName}`
          });
        }
      }

      return '';
    };

    const loadTeamLineup = async () => {
      if (!teamToEdit.value?.id) {
        teamLineupAssignments.value = [];
        lineupRenderKey.value += 1;
        return;
      }

      loadingTeamLineup.value = true;
      try {
        const response = await apiClient.get(`/club-teams/${teamToEdit.value.id}/lineup`, {
          params: { half: selectedLineupHalf.value }
        });
        if (response.status >= 400) {
          throw new Error('lineuploadfailed');
        }
        const assignments = Array.isArray(response.data)
          ? response.data.map((entry) => ({
            memberId: Number(entry.memberId),
            position: Number(entry.position)
          }))
          : [];
        teamLineupAssignments.value = normalizeTeamLineupAssignments(assignments);
        lineupRenderKey.value += 1;
      } catch (error) {
        console.error('Fehler beim Laden der Mannschaftsmeldung:', error);
        teamLineupAssignments.value = [];
        lineupRenderKey.value += 1;
      } finally {
        loadingTeamLineup.value = false;
      }
    };

    const downloadLineupPdf = () => {
      const members = selectedTeamLineupMembers.value;
      if (!members.length) {
        showInfo(t('messages.info'), t('teamManagement.lineupPdfEmpty'), '', 'info');
        return;
      }
      const team = teamToEdit.value;
      const clubName = store.getters.currentClubName || '';
      const seasonLabel = team?.season?.season || currentSeason.value?.season || t('teamManagement.seasonUnknown');
      const leagueName = team?.league?.name || t('teamManagement.noLeague');
      const halfLabel = lineupHalfOptions.value.find((o) => o.value === selectedLineupHalf.value)?.label || '';
      const introLines = [
        `${t('pdfGenerator.clubLabel')} ${clubName}`,
        `${t('pdfGenerator.teamNameLabel')} ${team?.name || ''}`,
        `${t('pdfGenerator.leagueLabel')} ${leagueName}`
      ];
      if (team?.plannedLeagueName?.trim()) {
        introLines.push(`${t('pdfGenerator.plannedLeagueLabel')} ${team.plannedLeagueName.trim()}`);
      }
      introLines.push(
        `${t('pdfGenerator.seasonLabel')} ${seasonLabel}`,
        `${t('pdfGenerator.periodLabel')} ${halfLabel}`,
        `${t('pdfGenerator.teamGenderLabel')} ${labelTeamGender(effectiveTeamGender.value)}`,
        `${t('pdfGenerator.teamAgeGroupLabel')} ${labelAgeGroup(effectiveTeamAgeGroup.value)}`,
        `${t('pdfGenerator.generatedAt')} ${new Date().toLocaleString()}`
      );
      const tableHead = [[
        '#',
        t('teamManagement.player'),
        t('members.ageGroup'),
        t('pdfGenerator.lineupQttr'),
        t('teamManagement.eligibility')
      ]];
      const tableBody = members.map((member, index) => [
        String(index + 1),
        `${member.firstName || ''} ${member.lastName || ''}`.trim(),
        String(member.memberAgeGroupLabel || ''),
        String(member.lineupRatingLabel || ''),
        String(member.eligibilityLabel || '')
      ]);
      const pdfGenerator = new PDFGenerator(20, 10, t);
      pdfGenerator.addTeamLineup({
        title: t('pdfGenerator.teamLineupTitle'),
        introLines,
        tableHead,
        tableBody
      });
      const safe = (s) => String(s || '')
        .replace(/[<>:"/\\|?*\u0000-\u001f]/g, '_')
        .replace(/\s+/g, '_')
        .slice(0, 80);
      const filename = `${t('teamManagement.lineupPdfFilePrefix')}_${safe(team?.name)}_${new Date().toISOString().slice(0, 10)}.pdf`;
      pdfGenerator.save(filename);
    };

    const persistTeamLineupAssignments = async (nextAssignments, validationMessage = '') => {
      if (!teamToEdit.value?.id || savingTeamLineup.value) {
        return false;
      }

      const normalizedAssignments = normalizeTeamLineupAssignments(nextAssignments);
      if (validationMessage) {
        lineupRenderKey.value += 1;
        await nextTick();
        initializeLineupSortable();
        await showInfo(t('messages.warning'), validationMessage, '', 'warning');
        return false;
      }

      const previousAssignments = [...teamLineupAssignments.value];
      teamLineupAssignments.value = normalizedAssignments;
      savingTeamLineup.value = true;
      try {
        const response = await apiClient.put(`/club-teams/${teamToEdit.value.id}/lineup`, {
          assignments: normalizedAssignments,
          lineupHalf: selectedLineupHalf.value
        });
        if (response.status >= 400) {
          throw new Error('lineupsavefailed');
        }
        teamLineupAssignments.value = normalizedAssignments;
        lineupRenderKey.value += 1;
        await nextTick();
        initializeLineupSortable();
        return true;
      } catch (error) {
        console.error('Fehler beim Speichern der Mannschaftsmeldung:', error);
        teamLineupAssignments.value = previousAssignments;
        lineupRenderKey.value += 1;
        await nextTick();
        initializeLineupSortable();
        await showInfo(t('messages.error'), t('teamManagement.lineupSaveError'), '', 'error');
        return false;
      } finally {
        savingTeamLineup.value = false;
      }
    };

    const addMemberToLineup = async (member) => {
      if (!member?.id || savingTeamLineup.value) {
        return;
      }
      if (teamLineupAssignments.value.some((entry) => Number(entry.memberId) === Number(member.id))) {
        return;
      }
      const nextAssignments = [
        ...teamLineupAssignments.value,
        { memberId: Number(member.id) }
      ];
      const validationMessage = getLineupValidationMessageForAssignments(nextAssignments);
      await persistTeamLineupAssignments(nextAssignments, validationMessage);
    };

    const removeMemberFromLineup = async (memberId) => {
      if (savingTeamLineup.value) {
        return;
      }
      await persistTeamLineupAssignments(
        teamLineupAssignments.value.filter((entry) => Number(entry.memberId) !== Number(memberId))
      );
    };

    const moveLineupMember = async (memberId, direction) => {
      if (savingTeamLineup.value) {
        return;
      }

      const currentAssignments = [...teamLineupAssignments.value].sort((a, b) => a.position - b.position);
      const currentIndex = currentAssignments.findIndex((entry) => Number(entry.memberId) === Number(memberId));
      if (currentIndex < 0) {
        return;
      }

      const targetIndex = direction === 'up' ? currentIndex - 1 : currentIndex + 1;
      if (targetIndex < 0 || targetIndex >= currentAssignments.length) {
        return;
      }

      const [entry] = currentAssignments.splice(currentIndex, 1);
      currentAssignments.splice(targetIndex, 0, entry);
      const validationMessage = getLineupMoveValidationMessage(
        [...teamLineupAssignments.value].sort((a, b) => a.position - b.position),
        currentIndex,
        targetIndex
      );
      await persistTeamLineupAssignments(currentAssignments, validationMessage);
    };

    const destroyLineupSortable = () => {
      if (lineupSortableInstance) {
        lineupSortableInstance.destroy();
        lineupSortableInstance = null;
      }
    };

    const initializeLineupSortable = async () => {
      await nextTick();
      destroyLineupSortable();

      if (activeEditorSection.value !== 'lineup' || !lineupSortableBody.value || selectedTeamLineupMembers.value.length < 2) {
        return;
      }

      lineupSortableInstance = Sortable.create(lineupSortableBody.value, {
        draggable: '.team-lineup-row',
        handle: '.drag-handle',
        animation: 150,
        onEnd: async (event) => {
          if (savingTeamLineup.value || event.oldIndex == null || event.newIndex == null || event.oldIndex === event.newIndex) {
            return;
          }

          const currentAssignments = [...teamLineupAssignments.value].sort((a, b) => a.position - b.position);
          const [movedEntry] = currentAssignments.splice(event.oldIndex, 1);
          currentAssignments.splice(event.newIndex, 0, movedEntry);
          const validationMessage = getLineupMoveValidationMessage(
            [...teamLineupAssignments.value].sort((a, b) => a.position - b.position),
            event.oldIndex,
            event.newIndex
          );
          const success = await persistTeamLineupAssignments(currentAssignments, validationMessage);
          if (!success) {
            await nextTick();
            initializeLineupSortable();
          }
        }
      });
    };

    const refreshPlayerStats = async () => {
      if (!teamToEdit.value || !teamToEdit.value.leagueId) {
        return;
      }
      
      loadingStats.value = true;
      
      try {
        await loadClubMembers();
        const response = await apiClient.get(
          `/matches/leagues/${selectedClub.value}/stats/${teamToEdit.value.leagueId}`,
          {
            params: { seasonid: currentSeason.value?.id }
          }
        );
        
        playerStats.value = response.data;
      } catch (error) {
        console.error('Error loading player stats:', error);
        await showInfo(t('messages.error'), t('teamManagement.errorLoadingStats'), '', 'danger');
      } finally {
        loadingStats.value = false;
      }
    };
    
    const fetchTeamDataManually = async () => {
      if (!teamToEdit.value || !teamToEdit.value.id) {
        return;
      }
      
      fetchingTeamData.value = true;
      myTischtennisError.value = '';
      myTischtennisSuccess.value = '';
      
      try {

        const startResponse = await apiClient.post('/mytischtennis/fetch-team-data/async', {
          clubTeamId: teamToEdit.value.id
        });

        const jobId = startResponse?.data?.jobId;
        if (!jobId) {
          throw new Error(t('teamManagement.asyncJobStartFailed'));
        }

        const maxPollAttempts = 120; // ~4 Minuten bei 2s Intervall
        let completedResponse = null;
        for (let attempt = 0; attempt < maxPollAttempts; attempt++) {
          await new Promise((resolve) => setTimeout(resolve, 2000));
          const pollResponse = await apiClient.get(`/mytischtennis/fetch-team-data/jobs/${jobId}`, {
            timeout: 15000
          });
          const job = pollResponse?.data?.job;
          if (!job) continue;

          if (job.status === 'completed') {
            completedResponse = job.result;
            break;
          }

          if (job.status === 'failed') {
            const failedMsg = getSafeMessage(job.error, t('teamManagement.dataFetchFailed'));
            throw new Error(failedMsg);
          }
        }

        if (!completedResponse) {
          throw new Error(t('teamManagement.fetchTimedOut'));
        }

        if (completedResponse && completedResponse.success) {
          const successMessage = getSafeMessage(completedResponse.message, t('teamManagement.teamDataFetched'));
          myTischtennisSuccess.value = successMessage;
          
          const teamName = getSafeMessage(completedResponse.data?.teamName, teamToEdit.value?.name || t('teamManagement.unknownTeam'));
          const fetchedCount = getSafeMessage(String(completedResponse.data?.fetchedCount ?? ''), '0');
          let detailsMessage = t('teamManagement.teamDataFetchedDetails', { team: teamName, count: fetchedCount });
          
          if (completedResponse.data?.tableUpdate) {
            const tableUpdate = getSafeMessage(completedResponse.data.tableUpdate);
            if (tableUpdate) {
              detailsMessage += `\n\n${t('teamManagement.tableUpdateLabel')}\n${tableUpdate}`;
            }
          }
          
          await showInfo(
            t('messages.success'),
            successMessage,
            detailsMessage,
            'success'
          );
        } else if (completedResponse && completedResponse.success === false) {
          const errorTitle = completedResponse.needsMyTischtennisReauth ? t('teamManagement.mytischtennisLoginRequired') : t('messages.error');
          const errorMessage = getSafeMessage(completedResponse.error, t('teamManagement.dataFetchFailed'));
          const details = completedResponse.debug ? getSafeMessage(JSON.stringify(completedResponse.debug, null, 2)) : '';
          await showInfo(
            errorTitle,
            errorMessage,
            details,
            completedResponse.needsMyTischtennisReauth ? 'warning' : 'error'
          );
          myTischtennisError.value = errorMessage;
        }
      } catch (error) {
        console.error('Fehler beim Abrufen der Team-Daten:', error);
        const isTimeout = error?.code === 'ECONNABORTED' || /Zeitüberschreitung/i.test(String(error?.message || ''));
        const errData = error?.response?.data || {};
        const errorMsg = isTimeout
          ? t('teamManagement.fetchTimeoutShort')
          : getSafeMessage(error?.message || errData.message || errData.error, t('teamManagement.dataFetchFailed'));
        const details = errData.debug ? getSafeMessage(JSON.stringify(errData.debug, null, 2)) : '';
        myTischtennisError.value = errorMsg;
        await showInfo(t('messages.error'), errorMsg, details, isTimeout ? 'warning' : 'error');
      } finally {
        fetchingTeamData.value = false;
      }
    };
    
    // Watch teamToEdit to load stats when a team is selected
    watch(teamToEdit, (newTeam) => {
      if (newTeam) {
        selectedLineupHalf.value = isSecondHalf.value ? 'second_half' : 'first_half';
        loadClubMembers();
        loadTeamLineup();
      } else {
        clubMembers.value = [];
        memberById.value = {};
        teamLineupAssignments.value = [];
      }

      if (newTeam && newTeam.leagueId) {
        refreshPlayerStats();
      } else {
        playerStats.value = [];
      }
    });

    watch(selectedLineupHalf, () => {
      if (teamToEdit.value?.id) {
        loadTeamLineup();
      }
      if (activeMainSection.value === 'planning' && planningLocalTeams.value.length) {
        loadPlanningAssignments();
      }
      if (selectedClub.value && selectedSeasonId.value) {
        loadPlanningInterestedMemberIds();
      }
    });

    watch(activeEditorSection, () => {
      initializeLineupSortable();
    });

    watch(selectedTeamLineupMembers, () => {
      initializeLineupSortable();
    }, { deep: true });

    onBeforeUnmount(() => {
      destroyLineupSortable();
      planningAutosaveTimers.forEach((timer) => clearTimeout(timer));
      planningStatusClearTimers.forEach((timer) => clearTimeout(timer));
      planningAutosaveTimers.clear();
      planningStatusClearTimers.clear();
    });
    
    // Watch selectedSeasonId to load teams when season changes
    watch(selectedSeasonId, (newSeasonId) => {
      if (newSeasonId && selectedClub.value) {
        loadTeams();
        loadLeagues();
        loadPlanningInterestedMemberIds();
      }
    });
    watch(selectedClub, () => {
      loadPlanningMembers();
      loadPlanningInterestedMemberIds();
    });
    
    const validateTeamDocumentFile = async (file, label) => {
      if (!file) {
        return false;
      }
      const maxSize = 10 * 1024 * 1024; // 10 MB, entspricht Backend-Limit
      const allowedExtensions = ['pdf', 'doc', 'docx', 'txt', 'csv'];
      const allowedMimeTypes = [
        'application/pdf',
        'application/msword',
        'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
        'text/plain',
        'text/csv',
        'application/octet-stream'
      ];

      const extension = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
      if (!allowedExtensions.includes(extension)) {
        await showInfo(t('teamManagement.invalidFileTypeTitle'), t('teamManagement.invalidFileExtension', { label, extensions: allowedExtensions.join(', ') }), '', 'warning');
        return false;
      }

      if (file.type && !allowedMimeTypes.includes(file.type)) {
        await showInfo(t('teamManagement.invalidFileTypeTitle'), t('teamManagement.invalidMimeType', { label, type: file.type }), '', 'warning');
        return false;
      }

      if (file.size > maxSize) {
        await showInfo(t('teamManagement.fileTooLargeTitle'), t('teamManagement.fileTooLarge', { label }), '', 'warning');
        return false;
      }

      return true;
    };
    
    return {
      t,
      infoDialog,
      confirmDialog,
      showInfo,
      showConfirm,
      handleConfirmResult,
      teams,
      leagues,
      teamFormIsOpen,
      teamToEdit,
      newTeamName,
      newLeagueId,
      newTeamGender,
      newTeamAgeGroup,
      newPlannedLeagueName,
      selectedSeasonId,
      currentSeason,
      teamDocuments,
      teamSearchQuery,
      teamFilter,
      activeMainSection,
      activeEditorSection,
      showGlobalJobDetails,
      filteredTeams,
      parsingInProgress,
      parsingDocuments,
      showPDFViewer,
      pdfUrl,
      pdfDialogTitle,
      myTischtennisUrl,
      parsedMyTischtennisData,
      parsingUrl,
      configuringTeam,
      fetchingTeamData,
      myTischtennisError,
      myTischtennisSuccess,
      playerStats,
      loadingStats,
      loadingLineupMembers,
      loadingTeamLineup,
      savingTeamLineup,
      lineupSortableBody,
      lineupRenderKey,
      selectedLineupHalf,
      filteredLeagues,
      isSecondHalf,
      fullyConfiguredTeamsCount,
      partiallyConfiguredTeamsCount,
      teamsWithoutLeagueCount,
      totalSeasonAppearances,
      totalHalfAppearances,
      lineupHalfOptions,
      teamAgeGroupOptions,
      teamGenderOptions,
      effectiveTeamAgeGroup,
      effectiveTeamGender,
      lineupProposalGroups,
      lineupProposalMemberCount,
      eligibleLineupMembers,
      selectedTeamLineupMembers,
      availableLineupMembers,
      planningPoolMembers,
      planningSelectableMembers,
      planningTeamsWithMembers,
      planningUnassignedMembers,
      planningMemberSearchQuery,
      planningTeamSaveStatus,
      markingPlanningInterest,
      teamLineupValidationMessage,
      labelAgeGroup,
      labelTeamGender,
      toggleNewTeam,
      resetToNewTeam,
      resetNewTeam,
      addNewTeam,
      addPlanningTeam,
      editTeam,
      deleteTeam,
      removePlanningTeam,
      convertPlanningTeamToRegular,
      uploadCodeList,
      uploadPinList,
      loadTeamDocuments,
      loadAllTeamDocuments,
      parsePDF,
      getTeamDocuments,
      getLatestTeamDocument,
      showPDFDialog,
      closePDFDialog,
      formatDate,
      formatFileSize,
      onSeasonChange,
      parseMyTischtennisUrl,
      configureTeamFromUrl,
      configureLeagueFromUrl,
      clearParsedData,
      getMyTischtennisStatus,
      editorTabNeedsAttention,
      fetchTeamDataManually,
      refreshPlayerStats,
      loadClubMembers,
      loadTeamLineup,
      onPlanningDragMember,
      onPlanningDropToTeam,
      onPlanningDropToUnassigned,
      onPlanningMoveInsideTeam,
      onPlanningRemoveFromTeam,
      onPlanningMarkMemberInterested,
      updatePlanningTeamField,
      downloadLineupPdf,
      addMemberToLineup,
      removeMemberFromLineup,
      moveLineupMember,
      memberById,
      getMemberLineupRatingLabel,
      schedulerJobs,
      formatJobDate,
      loadSchedulerJobsInfo,
      getFilteredTeamDetails,
      getTeamJobInfo
    };
  }
};
</script>

<style scoped>
.team-management-view {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.team-management-hero {
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background: var(--background-light);
  padding: 0.9rem;
}

.team-management-hero-header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
  flex-wrap: wrap;
  margin-bottom: 0.85rem;
}

.team-management-hero-copy h2 {
  margin: 0 0 0.25rem;
}

.team-management-hero-copy p {
  margin: 0;
  color: var(--text-muted);
}

.team-management-hero-actions {
  display: flex;
  gap: 0.75rem;
  align-items: flex-start;
}

.team-management-season-selector {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.team-management-hero-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.scheduler-jobs-summary {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 0.65rem 0.85rem;
  margin: 0.85rem 0 0.75rem;
  background: var(--background-light);
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
}

.scheduler-jobs-summary-copy {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
}

.scheduler-jobs-summary-text {
  font-size: 0.85rem;
  color: var(--text-muted);
}

.scheduler-jobs-toggle {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-width: 96px;
  padding: 0.4rem 0.75rem;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: white;
  cursor: pointer;
  font-weight: 600;
  color: var(--text-color);
  line-height: 1;
  white-space: nowrap;
}

.scheduler-jobs-toggle-icon {
  color: var(--text-muted);
}

.scheduler-jobs-toggle-text {
  color: inherit;
}

.team-management-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 0.75rem;
  margin: 1rem 0;
}

.team-summary-card {
  background: white;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 0.8rem 0.9rem;
}

.team-summary-label {
  display: block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.team-summary-value {
  display: block;
  margin-top: 0.2rem;
  font-size: 1.15rem;
  color: var(--text-color);
}

.newteam {
  margin-bottom: 1rem;
}

.team-management-mode-switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.toggle-new-team {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1rem;
}

.toggle-new-team span {
  cursor: pointer;
  font-weight: 600;
  color: var(--primary-color);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.add {
  background: var(--primary-color);
  color: white;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: bold;
}

.new-team-form {
  background: var(--background-light);
  padding: 0.75rem;
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
  margin-bottom: 0.5rem;
}

.workspace-header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
  margin-bottom: 1rem;
}

.workspace-label {
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.workspace-section-switcher {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.workspace-section-button {
  padding: 0.45rem 0.8rem;
  border: 1px solid var(--border-color);
  border-radius: 999px;
  background: white;
  color: var(--text-color);
  font-size: 0.9rem;
  font-weight: 600;
  cursor: pointer;
}

.workspace-section-button.active {
  background: var(--primary-light);
  border-color: var(--primary-color);
  color: var(--primary-dark);
}

.workspace-section-button.needs-attention:not(.active) {
  border-color: #c45c4a;
  background: #fff5f2;
  color: #8a2e22;
  box-shadow: 0 0 0 1px rgba(196, 92, 74, 0.35);
}

.workspace-section-button.needs-attention.active {
  box-shadow: 0 0 0 2px rgba(196, 92, 74, 0.45);
}

.workspace-missing-banner {
  margin: 0 0 1rem;
  padding: 0.65rem 0.85rem;
  border-radius: var(--border-radius);
  border: 1px solid #e8c9c4;
  background: #fff8f6;
  color: #5c2e28;
  font-size: 0.9rem;
}

.workspace-missing-banner-label {
  display: block;
  font-weight: 700;
  margin-bottom: 0.35rem;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: #8a3d34;
}

.workspace-missing-list {
  margin: 0;
  padding-left: 1.15rem;
}

.workspace-missing-list li {
  margin: 0.15rem 0;
}

.settings-summary-card--warning {
  border-color: #e8a598;
  background: #fff8f6;
}

label.field-needs-attention span:first-of-type,
.settings-field-league.field-needs-attention > span:first-of-type {
  color: #8a2e22;
  font-weight: 600;
}

.field-needs-attention select,
.field-needs-attention-wrap .compact-url-input {
  border-color: #d97b6a !important;
  box-shadow: 0 0 0 1px rgba(217, 123, 106, 0.35);
}

.field-attention-hint {
  display: block;
  font-size: 0.8rem;
  color: #9d3b32;
  margin-top: 0.35rem;
}

.field-attention-hint-block {
  margin-top: 0.25rem;
  margin-bottom: 0.35rem;
}

.mytt-missing-checklist {
  margin: 0.4rem 0 0;
  padding-left: 1.2rem;
  color: var(--text-color);
  font-size: 0.9rem;
}

.mytt-missing-checklist li {
  margin: 0.2rem 0;
}

.workspace-section-panel {
  background: white;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  padding: 0.9rem;
}

.workspace-header h3 {
  margin: 0.2rem 0 0;
}

.workspace-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35rem;
}

.workspace-badge {
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.2rem 0.55rem;
  border-radius: 999px;
}

.workspace-badge.complete {
  background: #e8f6ed;
  color: #1b6f3a;
}

.workspace-badge.partial {
  background: #fff5e6;
  color: #a85d00;
}

.workspace-badge.missing {
  background: #fdeceb;
  color: #9d2b28;
}

.workspace-subtle {
  font-size: 0.85rem;
  color: var(--text-muted);
}

.form-layout-two-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
}

.basic-settings {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.advanced-settings {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

@media (max-width: 900px) {
  .form-layout-two-columns {
    grid-template-columns: 1fr;
  }
}

.new-team-form label {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

.new-team-form label span {
  font-weight: 600;
  color: var(--text-color);
  min-width: 120px;
  flex-shrink: 0;
}

.new-team-form input,
.new-team-form select {
  flex: 1;
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-small);
  font-size: 0.9rem;
}

.new-team-form input:focus,
.new-team-form select:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 1px var(--primary-light);
}

.form-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.5rem;
}

.form-actions button {
  padding: 0.4rem 1rem;
  border: none;
  border-radius: var(--border-radius-small);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
}

.form-actions button:not(.cancel-action) {
  background: var(--primary-color);
  color: white;
}

.form-actions button:not(.cancel-action):hover {
  background: var(--primary-dark);
}

.form-actions button:not(.cancel-action):disabled {
  background: var(--border-color);
  color: var(--text-color);
  opacity: 0.8;
  cursor: not-allowed;
}

.cancel-action {
  background: var(--background-light);
  color: var(--text-color);
  border: 1px solid var(--border-color);
}

.cancel-action:hover {
  background: var(--border-color);
}

.teams-list h3 {
  margin-bottom: 0.5rem;
  color: var(--text-color);
  font-size: 1.1rem;
}

.teams-list-header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
  margin-bottom: 0.75rem;
}

.teams-list-tools {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  min-width: min(420px, 100%);
}

.team-search-input {
  width: 100%;
  padding: 0.65rem 0.8rem;
  border: 1px solid var(--border-color);
  border-radius: 10px;
  background: var(--surface-color);
  color: var(--text-color);
}

.team-search-input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 3px rgba(47, 122, 95, 0.14);
}

.team-filter-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

.team-filter-chip {
  border: 1px solid var(--border-color);
  background: var(--surface-muted);
  color: var(--text-color) !important;
  border-radius: 999px;
  padding: 0.35rem 0.75rem;
  font-size: 0.85rem;
  cursor: pointer;
  font-weight: 600;
}

.team-filter-chip.active {
  background: rgba(47, 122, 95, 0.12);
  border-color: var(--primary-soft);
  color: var(--primary-strong) !important;
  font-weight: 600;
}

.no-teams {
  text-align: center;
  padding: 2rem;
  color: var(--text-muted);
  background: var(--background-light);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
}

.workspace-panel-header {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  align-items: flex-start;
  margin-bottom: 0.9rem;
}

.workspace-panel-copy {
  margin: 0.25rem 0 0;
  color: var(--text-muted);
  font-size: 0.92rem;
}

.document-status-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 0.9rem;
}

.document-status-card,
.mytt-status-card {
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background: var(--background-light);
  padding: 0.9rem;
}

.document-status-head,
.mytt-status-line {
  display: flex;
  justify-content: space-between;
  gap: 0.75rem;
  align-items: flex-start;
}

.document-status-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.8rem;
  font-weight: 600;
  padding: 0.22rem 0.55rem;
  border-radius: 999px;
}

.document-status-badge.complete {
  background: #d9f5e3;
  color: #1f7a3f;
}

.document-status-badge.missing {
  background: #fdeaea;
  color: #b23b3b;
}

.document-status-meta,
.mytt-status-copy {
  margin: 0.55rem 0 0;
  color: var(--text-muted);
  line-height: 1.4;
}

.document-status-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.55rem;
  margin-top: 0.8rem;
}

.settings-summary-grid,
.stats-summary-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 0.75rem;
  margin-bottom: 0.9rem;
}

.settings-summary-card {
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background: var(--background-light);
  padding: 0.8rem 0.9rem;
}

.settings-summary-label {
  display: block;
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-bottom: 0.25rem;
}

.settings-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: 0.9rem;
}

.settings-form-grid label.settings-field-span-2 {
  grid-column: 1 / -1;
}

.settings-form-grid .field-hint {
  display: block;
  font-size: 0.8rem;
  color: var(--text-muted);
  margin-top: 0.25rem;
  line-height: 1.35;
}

.settings-form-grid label.settings-field-span-2 input {
  width: 100%;
  min-width: 0;
}

.mytt-status-label {
  font-weight: 600;
  color: var(--text-color);
}

@media (max-width: 768px) {
  .team-management-hero-header {
    flex-direction: column;
  }

  .scheduler-jobs-summary {
    flex-direction: column;
    align-items: flex-start;
  }

  .teams-list-header,
  .workspace-header {
    flex-direction: column;
  }

  .teams-list-tools {
    min-width: 100%;
  }

}

/* Upload-Buttons Styles */
.upload-actions.compact {
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--background-light);
  border-radius: 4px;
  border: 1px solid var(--border-color);
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.upload-actions.compact .section-title {
  flex-shrink: 0;
}

.upload-buttons-compact {
  display: flex;
  gap: 0.5rem;
  flex: 1;
  justify-content: flex-start;
}

.btn-upload-sm {
  padding: 4px 10px;
  font-size: 0.85rem;
  border: 1px solid transparent;
  border-radius: 8px;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
  background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
  color: var(--text-on-primary);
}

.btn-upload-sm:hover {
  transform: translateY(-1px);
}

/* Legacy styles */
.upload-actions {
  margin-top: 1rem;
  padding: 0.75rem;
  background: var(--background-light);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
}

.upload-actions h4 {
  margin: 0 0 0.4rem 0;
  color: var(--text-color);
  font-size: 0.95rem;
}

.upload-buttons {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.upload-btn {
  padding: 0.4rem 1rem;
  border: 1px solid transparent;
  border-radius: 10px;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.875rem;
}

.code-list-btn {
  background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
  color: var(--text-on-primary);
}

.code-list-btn:hover {
  transform: translateY(-1px);
}

.pin-list-btn {
  background: rgba(181, 110, 65, 0.14);
  border-color: rgba(181, 110, 65, 0.24);
  color: #8a4f28;
}

.pin-list-btn:hover {
  background: rgba(181, 110, 65, 0.2);
}

@media (max-width: 768px) {
  .upload-buttons {
    flex-direction: column;
  }
  
  .upload-btn {
    justify-content: center;
  }
}

/* Upload-Bestätigung */
.upload-confirmation {
  margin-top: 0.75rem;
  padding: 0.75rem;
  background: var(--surface-muted);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
}

.selected-file-info {
  background: rgba(15, 23, 42, 0.05);
  padding: 0.5rem;
  border-radius: var(--border-radius-small);
  margin-bottom: 0.5rem;
  font-size: 0.85rem;
  line-height: 1.4;
}

.action-buttons {
  display: flex;
  gap: 0.5rem;
  align-items: center;
}


.confirm-parse-btn {
  background: linear-gradient(135deg, var(--primary-color), var(--primary-hover));
  color: var(--text-on-primary);
  border: 1px solid transparent;
  padding: 0.4rem 1rem;
  border-radius: 10px;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
}

.confirm-parse-btn:hover:not(:disabled) {
  transform: translateY(-1px);
}

.confirm-parse-btn:disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

.cancel-parse-btn {
  background: var(--surface-color);
  color: var(--text-color);
  border: 1px solid var(--border-color);
  padding: 0.4rem 1rem;
  border-radius: 10px;
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
}

.cancel-parse-btn:hover {
  background: var(--surface-muted);
  border-color: var(--primary-soft);
}

.selected-file {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.875rem;
  color: #2e7d32;
  font-weight: 600;
}

.parse-options {
  display: flex;
  gap: 1rem;
  align-items: end;
  flex-wrap: wrap;
}

.parse-options label {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  min-width: 200px;
}

.parse-options label span {
  font-weight: 600;
  color: var(--text-color);
}

/* PDF-Parsing Styles */
.pdf-parsing-section.compact {
  margin-top: 0.5rem;
  padding: 0.5rem;
  background: var(--background-light);
  border-radius: 4px;
  border: 1px solid var(--border-color);
}

.pdf-parsing-section.compact .section-title {
  display: block;
  margin-bottom: 0.5rem;
}

.document-table.compact {
  width: 100%;
  border-collapse: collapse;
  background: white;
  border-radius: 3px;
  overflow: hidden;
  font-size: 0.85rem;
}

.document-table.compact th {
  background: var(--background-light);
  padding: 4px 8px;
  text-align: left;
  font-weight: 600;
  font-size: 0.8rem;
  border-bottom: 1px solid var(--border-color);
  color: var(--text-color);
}

.document-table.compact td {
  padding: 4px 8px;
  border-bottom: 1px solid var(--border-color);
  font-size: 0.8rem;
}

/* Legacy styles */
.pdf-parsing-section {
  margin-top: 1rem;
  padding: 0.75rem;
  background: var(--background-light);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
}

.pdf-parsing-section h4 {
  margin: 0 0 0.4rem 0;
  color: var(--text-color);
  font-size: 0.95rem;
}

.document-table {
  width: 100%;
  border-collapse: collapse;
  background: white;
  border-radius: var(--border-radius-small);
  overflow: hidden;
}

.document-table th {
  background: var(--background-light);
  padding: 0.25rem 0.4rem;
  text-align: left;
  font-weight: 600;
  font-size: 0.85rem;
  border-bottom: 2px solid var(--border-color);
  color: var(--text-color);
}

.document-table td {
  padding: 0.25rem 0.4rem;
  border-bottom: 1px solid var(--border-color);
  font-size: 0.85rem;
}

.document-row:hover {
  background: var(--background-light);
}

.document-row:last-child td {
  border-bottom: none;
}

.document-name {
  font-weight: 500;
  color: var(--text-color);
}

.document-type {
  font-size: 0.875rem;
  color: var(--text-muted);
  background: var(--primary-light);
  padding: 0.25rem 0.5rem;
  border-radius: var(--border-radius-small);
  display: inline-block;
  width: fit-content;
}

.document-size {
  font-size: 0.75rem;
  color: var(--text-muted);
}

.document-actions {
  display: flex;
  gap: 1rem;
  align-items: center;
}

.type-badge {
  font-size: 0.75rem;
  padding: 0.2rem 0.4rem;
  border-radius: var(--border-radius-small);
  display: inline-block;
  white-space: nowrap;
  font-weight: 600;
}

.type-badge.code_list {
  background: #e3f2fd;
  color: #1976d2;
}

.type-badge.pin_list {
  background: #fff3e0;
  color: #f57c00;
}

.parse-btn {
  padding: 0.3rem 0.6rem;
  background: #2196F3;
  color: white;
  border: none;
  border-radius: var(--border-radius-small);
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: var(--transition);
  white-space: nowrap;
}

.parse-btn:hover:not(:disabled) {
  background: #1976D2;
}

.parse-btn:disabled {
  background: var(--text-muted);
  cursor: not-allowed;
  opacity: 0.5;
}

/* PDF-Dialog Styles */
.pdf-dialog-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.8);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}

.pdf-dialog {
  background: white;
  border-radius: var(--border-radius-medium);
  width: 90%;
  height: 90%;
  max-width: 1200px;
  max-height: 800px;
  display: flex;
  flex-direction: column;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
}

.pdf-dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--border-color);
  background: var(--background-light);
  border-radius: var(--border-radius-medium) var(--border-radius-medium) 0 0;
}

.pdf-dialog-header h3 {
  margin: 0;
  color: var(--text-primary);
  font-size: 1.25rem;
}

.close-btn {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
  color: var(--text-muted);
  padding: 0.25rem;
  border-radius: var(--border-radius-small);
  transition: var(--transition);
}

.close-btn:hover {
  background: var(--background-light);
  color: var(--text-primary);
}

.pdf-dialog-content {
  flex: 1;
  padding: 0;
  overflow: hidden;
}

.pdf-viewer {
  width: 100%;
  height: 100%;
  border: none;
}

.no-pdf {
  display: flex;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--text-muted);
}

@media (max-width: 768px) {
  .document-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 1rem;
  }
  
  .document-actions {
    width: 100%;
    justify-content: space-between;
  }
  
  .parse-options {
    flex-direction: column;
    align-items: stretch;
  }
  
  .parse-options label {
    min-width: auto;
  }
}

/* MyTischtennis URL Parser Styles */
.mytischtennis-config.compact {
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--background-light);
  border-radius: 4px;
  border: 1px solid var(--border-color);
}

.mytischtennis-header-compact {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.5rem;
}

.section-title {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--primary-color);
  flex-shrink: 0;
}

.status-inline {
  flex: 1;
  display: flex;
  justify-content: flex-end;
}

.badge-sm {
  display: inline-block;
  padding: 2px 6px;
  font-size: 0.75rem;
  border-radius: 3px;
  font-weight: 500;
}

.badge-sm.complete {
  background-color: #d4edda;
  color: #155724;
}

.badge-sm.partial {
  background-color: #fff3cd;
  color: #856404;
}

.badge-sm.missing {
  background-color: #f8d7da;
  color: #721c24;
}

.btn-sm {
  padding: 4px 8px;
  font-size: 0.85rem;
  background-color: var(--primary-color);
  color: white;
  border: none;
  border-radius: 3px;
  cursor: pointer;
  transition: background-color 0.2s;
}

.btn-sm:hover:not(:disabled) {
  background-color: var(--primary-color-dark);
}

.btn-sm:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.compact-input-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.compact-url-input {
  flex: 1;
  padding: 6px 10px;
  font-size: 0.85rem;
  border: 1px solid var(--border-color);
  border-radius: 3px;
}

.inline-status {
  font-size: 1rem;
}

.compact-message {
  margin-top: 0.5rem;
  padding: 6px 10px;
  font-size: 0.85rem;
  border-radius: 3px;
}

.compact-message.error {
  background-color: #f8d7da;
  color: #721c24;
  border: 1px solid #f5c6cb;
}

.compact-message.success {
  background-color: #d4edda;
  color: #155724;
  border: 1px solid #c3e6cb;
}

/* Player Stats */
.player-stats {
  margin-top: 1rem;
  padding: 0.5rem;
  background: var(--background-light);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
}

.stats-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

.loading-stats, .no-stats {
  text-align: center;
  padding: 1rem;
  color: var(--text-muted);
  font-size: 0.9rem;
}

.lineup-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.lineup-period-meta {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.lineup-period-chip {
  display: inline-flex;
  align-items: center;
  padding: 0.4rem 0.7rem;
  border-radius: 999px;
  background: rgba(47, 122, 95, 0.08);
  color: var(--primary-color);
  font-weight: 600;
}

.lineup-period-select {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  color: var(--text-color);
  font-weight: 600;
}

.lineup-period-select select {
  min-width: 220px;
}

.stats-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.85rem;
}

.stats-table thead th {
  background: var(--primary-color);
  color: white;
  padding: 4px 6px;
  text-align: left;
  font-weight: 600;
  font-size: 0.8rem;
}

.stats-table tbody tr {
  border-bottom: 1px solid var(--border-color);
}

.stats-table tbody tr:hover {
  background: var(--background-hover);
}

.stats-table td {
  padding: 4px 6px;
}

.stats-table .player-name {
  font-weight: 500;
}

.stats-table .stat-value {
  text-align: center;
  font-weight: 600;
  color: var(--primary-color);
}

.lineup-proposal-card {
  margin-top: 1rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius);
  background: #fff;
}

.lineup-proposal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.85rem 1rem;
  border-bottom: 1px solid var(--border-color);
}

.lineup-proposal-groups {
  display: grid;
  gap: 0.9rem;
  padding: 1rem;
}

.lineup-proposal-group {
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-small);
  overflow: hidden;
}

.lineup-proposal-group-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.7rem 0.9rem;
  background: rgba(47, 122, 95, 0.08);
  color: var(--primary-color);
}

.lineup-proposal-table {
  width: 100%;
  border-collapse: collapse;
}

.lineup-proposal-table th,
.lineup-proposal-table td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--border-color);
}

.lineup-proposal-table tbody tr:last-child td {
  border-bottom: none;
}

.lineup-rank,
.lineup-rating {
  width: 90px;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.lineup-drag-cell {
  width: 2.4rem;
  text-align: center;
}

.drag-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: grab;
  color: #66788a;
  font-weight: 700;
  user-select: none;
}

.team-lineup-row.sortable-chosen .drag-handle,
.team-lineup-row.sortable-ghost .drag-handle {
  cursor: grabbing;
}

/* Legacy styles (can be removed if not used elsewhere) */
.mytischtennis-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1rem;
  gap: 1rem;
}

.mytischtennis-config h4 {
  margin: 0;
  color: var(--primary-color);
  font-size: 1.1rem;
}

.header-actions {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
}

.current-status {
  flex-shrink: 0;
}

.fetch-btn {
  padding: 0.5rem 1rem;
  background: #2196F3;
  color: white;
  border: none;
  border-radius: var(--border-radius-small);
  cursor: pointer;
  font-weight: 600;
  font-size: 0.85rem;
  transition: var(--transition);
  white-space: nowrap;
}

.fetch-btn:hover:not(:disabled) {
  background: #1976D2;
  transform: translateY(-1px);
  box-shadow: 0 2px 8px rgba(33, 150, 243, 0.3);
}

.fetch-btn:disabled {
  background: var(--text-muted);
  cursor: not-allowed;
  opacity: 0.5;
}

.status-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  padding: 0.28rem 0.78rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 600;
  white-space: nowrap;
  border: 1px solid transparent;
}

.status-badge.complete {
  background: rgba(47, 122, 95, 0.12);
  color: #1f5f49;
  border-color: rgba(47, 122, 95, 0.24);
}

.status-badge.partial {
  background: rgba(181, 110, 65, 0.14);
  color: #8a4f28;
  border-color: rgba(181, 110, 65, 0.28);
}

.status-badge.missing {
  background: rgba(200, 74, 56, 0.12);
  color: #8b3327;
  border-color: rgba(200, 74, 56, 0.24);
}

.mytischtennis-status {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border-color);
}

.mytischtennis-status .status-badge {
  font-size: 0.8rem;
  padding: 0.2rem 0.6rem;
}

.mytischtennis-url-input {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.mytischtennis-url-input label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.mytischtennis-url-input span {
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--text-secondary);
}

.url-input {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid var(--border-color);
  border-radius: var(--border-radius-small);
  font-size: 0.9rem;
}

.url-input:focus {
  outline: none;
  border-color: var(--primary-color);
  box-shadow: 0 0 0 2px rgba(33, 150, 243, 0.1);
}

.url-input:disabled {
  background: var(--background-light);
  cursor: wait;
}

.parsing-indicator {
  padding: 0.5rem;
  background: #e3f2fd;
  border: 1px solid #2196F3;
  border-radius: var(--border-radius-small);
  color: #1976D2;
  font-size: 0.9rem;
  text-align: center;
  font-weight: 600;
}

.error-message {
  margin-top: 1rem;
  padding: 0.75rem;
  background: #ffebee;
  border: 1px solid #ef5350;
  border-radius: var(--border-radius-small);
  color: #c62828;
  font-size: 0.9rem;
}

.success-message {
  margin-top: 1rem;
  padding: 0.75rem;
  background: #e8f5e9;
  border: 1px solid #66bb6a;
  border-radius: var(--border-radius-small);
  color: #2e7d32;
  font-size: 0.9rem;
}

/* Scheduler Jobs Info */
.scheduler-jobs-info {
  margin-bottom: 1rem;
  padding: 0.75rem;
  background: var(--background-light);
  border-radius: var(--border-radius);
  border: 1px solid var(--border-color);
  font-size: 0.875rem;
}

.job-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.5rem;
}

.job-info:last-child {
  margin-bottom: 0;
}

.job-label {
  font-weight: 600;
  color: var(--text-color);
  min-width: 150px;
}

.job-details {
  color: var(--text-muted);
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.job-count {
  color: var(--primary-color);
  font-weight: 500;
}

.job-error {
  color: #dc3545;
  font-weight: 500;
}

.job-header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.team-details {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--border-color);
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.team-detail-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.8rem;
  padding: 0.25rem 0.5rem;
  background: white;
  border-radius: 3px;
}

.team-name {
  flex: 1;
  color: var(--text-color);
}

.team-status {
  font-weight: bold;
  font-size: 0.9rem;
}

.team-status.success {
  color: #28a745;
}

.team-status.error {
  color: #dc3545;
}

/* Team Job Info */
.team-job-info.compact {
  margin-top: 0.5rem;
  padding: 0.5rem 0.75rem;
  background: var(--background-light);
  border-radius: 4px;
  border: 1px solid var(--border-color);
}

.team-job-details {
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.team-job-item {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-size: 0.85rem;
}

.team-job-label {
  font-weight: 600;
  color: var(--text-muted);
  min-width: 120px;
}

.team-job-value {
  color: var(--text-color);
}

.team-job-status {
  font-weight: 500;
}

.team-job-status.success {
  color: #28a745;
}

.team-job-status.error {
  color: #dc3545;
}

.team-job-no-data {
  color: var(--text-muted);
  font-size: 0.85rem;
  font-style: italic;
}

.team-job-status-row {
  margin-top: 0.3rem;
  padding-top: 0.3rem;
  border-top: 1px solid var(--border-color);
}

.team-job-status-value {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.8rem;
}

.team-job-status-icon {
  font-weight: bold;
  font-size: 0.9rem;
}

.team-job-status-icon.success {
  color: #28a745;
}

.team-job-status-icon.error {
  color: #dc3545;
}
</style>
