<template>
  <div class="receipts-list-view" :class="{ 'mobile-android-layout': isMobile }">
    <!-- Drag & Drop Overlay for bulk upload -->
    <DragDropOverlay
      ref="dragDropOverlayRef"
      @upload-complete="onBulkUploadComplete"
    />

    <!-- Hidden file input for button-triggered uploads -->
    <input
      ref="bulkFileInputRef"
      type="file"
      multiple
      accept=".pdf,.png,.jpg,.jpeg,application/pdf,image/png,image/jpeg"
      class="hidden-file-input"
      @change="onBulkFileInputChange"
    />

    <!-- Rejection Modal -->
    <Dialog
      v-model:visible="rejectDialogVisible"
      header="Respingere bon"
      :modal="true"
      :closable="true"
      :style="{ width: '400px' }"
    >
      <div class="reject-dialog-content">
        <p>Motivul respingerii (minim 5 caractere):</p>
        <Textarea
          v-model="rejectReason"
          rows="3"
          class="w-full"
          placeholder="Introduceți motivul respingerii..."
        />
      </div>
      <template #footer>
        <Button label="Anulează" severity="secondary" @click="rejectDialogVisible = false" />
        <Button
          label="Respinge"
          severity="danger"
          icon="pi pi-times"
          :disabled="rejectReason.length < 5"
          @click="executeReject"
        />
      </template>
    </Dialog>

    <!-- US-103: Mobile Android-Native Top Bar (using shared component) -->
    <MobileTopBar
      v-if="isMobile"
      :title="mobileSelectionMode ? `${selectedReceipts.length} selectate` : 'Bonuri'"
      :show-back="mobileSelectionMode"
      :show-menu="!mobileSelectionMode"
      :selection-active="mobileSelectionMode"
      :actions="mobileTopBarActions"
      @back-click="exitMobileSelectionMode"
      @menu-click="showDrawer = true"
      @action-click="handleTopBarAction"
    />

    <!-- US-040: Mobile Filter Chips (horizontal scrollable) -->
    <div v-if="isMobile && stats && !mobileSelectionMode" class="mobile-filter-chips-container">
      <div class="mobile-filter-chips">
        <span
          class="mobile-filter-chip"
          :class="{ active: !filters.status && !filters.processingStatus }"
          @click="filterByStatus(null)"
        >
          Toate
          <Badge :value="stats.total?.count || 0" class="chip-badge" />
        </span>
        <span
          class="mobile-filter-chip chip-draft"
          :class="{ active: filters.status === 'draft' }"
          @click="filterByStatus('draft')"
        >
          Ciorne
          <Badge :value="stats.draft?.count || 0" severity="info" class="chip-badge" />
        </span>
        <span
          class="mobile-filter-chip chip-pending"
          :class="{ active: filters.status === 'pending_review' }"
          @click="filterByStatus('pending_review')"
        >
          În așteptare
          <Badge :value="stats.pending_review?.count || 0" severity="warning" class="chip-badge" />
        </span>
        <span
          class="mobile-filter-chip chip-approved"
          :class="{ active: filters.status === 'approved' }"
          @click="filterByStatus('approved')"
        >
          Validate
          <Badge :value="stats.approved?.count || 0" severity="success" class="chip-badge" />
        </span>
        <span
          class="mobile-filter-chip chip-rejected"
          :class="{ active: filters.status === 'rejected' }"
          @click="filterByStatus('rejected')"
        >
          Respinse
          <Badge :value="stats.rejected?.count || 0" severity="danger" class="chip-badge" />
        </span>
        <!-- Processing status chips -->
        <span
          v-if="inProcessingCount > 0"
          class="mobile-filter-chip chip-processing"
          :class="{ active: filters.processingStatus === 'in_processing' }"
          @click="filterByProcessingStatus('in_processing')"
        >
          <i class="pi pi-spin pi-spinner"></i>
          În procesare
          <Badge :value="inProcessingCount" class="chip-badge" />
        </span>
        <span
          v-if="failedCount > 0"
          class="mobile-filter-chip chip-failed"
          :class="{ active: filters.processingStatus === 'failed' }"
          @click="filterByProcessingStatus('failed')"
        >
          <i class="pi pi-exclamation-triangle"></i>
          Cu erori
          <Badge :value="failedCount" class="chip-badge" />
        </span>
      </div>
    </div>

    <!-- Mobile More Menu -->
    <Menu ref="moreMenuRef" :model="moreMenuItems" :popup="true" />

    <!-- Mobile Drawer Menu (replaces old Sidebar) -->
    <MobileDrawerMenu
      v-model="showDrawer"
      :user="authStore.user"
      :companies-store="companyStore"
      :period-store="periodStore"
      @logout="handleLogout"
    />

    <!-- US-503: Filter BottomSheet for mobile -->
    <BottomSheet v-model="showFilters">
      <h3 class="bottom-sheet-title">Filtre</h3>
      <div class="bottom-sheet-filters">
        <!-- Status Filter -->
        <div class="form-group">
          <label class="form-label">Status</label>
          <Dropdown
            v-model="filters.status"
            :options="statusOptions"
            optionLabel="label"
            optionValue="value"
            placeholder="Toate statusurile"
            class="w-full"
          />
        </div>

        <!-- Search -->
        <div class="form-group">
          <label class="form-label">Căutare</label>
          <InputText
            v-model="filters.search"
            placeholder="Caută furnizor, CUI, nr. bon..."
            class="w-full"
          />
        </div>

        <!-- Direction Filter -->
        <div class="form-group">
          <label class="form-label">Tip</label>
          <Dropdown
            v-model="filters.direction"
            :options="directionOptions"
            optionLabel="label"
            optionValue="value"
            placeholder="Toate tipurile"
            class="w-full"
          />
        </div>

        <!-- Date From -->
        <div class="form-group">
          <label class="form-label">De la data</label>
          <Calendar
            v-model="filters.dateFrom"
            dateFormat="dd.mm.yy"
            placeholder="Selectează data"
            showIcon
            class="w-full"
          />
        </div>

        <!-- Date To -->
        <div class="form-group">
          <label class="form-label">Până la data</label>
          <Calendar
            v-model="filters.dateTo"
            dateFormat="dd.mm.yy"
            placeholder="Selectează data"
            showIcon
            class="w-full"
          />
        </div>

        <!-- Bottom sheet actions -->
        <div class="bottom-sheet-actions">
          <Button
            icon="pi pi-filter-slash"
            label="Resetează"
            class="p-button-outlined p-button-secondary"
            @click="clearFiltersAndClose"
          />
          <Button
            icon="pi pi-check"
            label="Aplică"
            @click="applyFiltersAndClose"
          />
        </div>
      </div>
    </BottomSheet>

    <!-- Main Card -->
    <!-- Page Header (centered, no icon) - Desktop only -->
    <div class="page-header" v-if="!isMobile">
      <h1 class="page-title">Lista Bonuri Fiscale</h1>
    </div>

    <!-- Main Card -->
    <div class="roa-card">
      <!-- Status Filters Row (desktop only - mobile uses dropdown) -->
      <div class="status-actions-row" v-if="stats && !isMobile">
        <div class="status-chips">
          <span
            class="status-chip"
            :class="{ active: !filters.status && !filters.processingStatus }"
            @click="filterByStatus(null)"
          >
            Toate <Badge :value="stats.total?.count || 0" />
          </span>
          <span
            class="status-chip status-draft"
            :class="{ active: filters.status === 'draft' }"
            @click="filterByStatus('draft')"
          >
            Ciorne <Badge :value="stats.draft?.count || 0" severity="info" />
          </span>
          <span
            class="status-chip status-pending"
            :class="{ active: filters.status === 'pending_review' }"
            @click="filterByStatus('pending_review')"
          >
            În așteptare <Badge :value="stats.pending_review?.count || 0" severity="warning" />
          </span>
          <span
            class="status-chip status-approved"
            :class="{ active: filters.status === 'approved' }"
            @click="filterByStatus('approved')"
          >
            Validate <Badge :value="stats.approved?.count || 0" severity="success" />
          </span>
          <span
            class="status-chip status-rejected"
            :class="{ active: filters.status === 'rejected' }"
            @click="filterByStatus('rejected')"
          >
            Respinse <Badge :value="stats.rejected?.count || 0" severity="danger" />
          </span>

          <!-- US-005: Processing Status Filter Chips -->
          <span
            v-if="inProcessingCount > 0"
            class="status-chip processing-chip-in-progress"
            :class="{ active: filters.processingStatus === 'in_processing' }"
            @click="filterByProcessingStatus('in_processing')"
          >
            <i class="pi pi-spin pi-spinner" style="font-size: 0.75rem;"></i>
            În procesare <Badge :value="inProcessingCount" />
          </span>
          <span
            v-if="failedCount > 0"
            class="status-chip processing-chip-failed"
            :class="{ active: filters.processingStatus === 'failed' }"
            @click="filterByProcessingStatus('failed')"
          >
            <i class="pi pi-exclamation-triangle" style="font-size: 0.75rem;"></i>
            Cu erori <Badge :value="failedCount" />
          </span>
        </div>

        <!-- Desktop Action Buttons -->
        <div v-if="!isMobile" class="desktop-action-buttons">
          <Button
            label="Upload Bonuri"
            icon="pi pi-cloud-upload"
            severity="info"
            outlined
            @click="openBulkFileInput"
          />
          <Button
            label="Bon Nou"
            icon="pi pi-plus"
            severity="success"
            raised
            @click="goToCreate"
          />
        </div>
      </div>

      <!-- Mobile Toolbar (action buttons + Upload + Bon Nou) -->
      <div v-if="isMobile" class="mobile-toolbar-container">
        <div class="mobile-toolbar-buttons">
          <Button
            icon="pi pi-filter"
            label="Filtre"
            :class="{ 'filter-active': hasActiveFilters }"
            severity="secondary"
            outlined
            size="small"
            @click="showFilters = !showFilters"
          />
          <Button
            icon="pi pi-refresh"
            severity="secondary"
            outlined
            size="small"
            :loading="loading"
            @click="clearFilters"
            v-tooltip.bottom="'Resetează filtrele'"
          />
        </div>
        <div class="mobile-toolbar-buttons mobile-toolbar-actions">
          <Button
            icon="pi pi-cloud-upload"
            label="Upload"
            severity="info"
            size="small"
            outlined
            @click="openBulkFileInput"
          />
          <Button
            label="Bon Nou"
            icon="pi pi-plus"
            severity="success"
            size="small"
            raised
            @click="goToCreate"
          />
        </div>
      </div>

      <!-- Search and Filters Row (desktop only - mobile uses BottomSheet US-503) -->
      <div v-if="!isMobile" class="filters-row">

        <InputText
          v-model="filters.search"
          placeholder="Caută furnizor, CUI, nr. bon..."
          class="filter-search"
          @keyup.enter="onFilterChange"
        >
          <template #prefix>
            <i class="pi pi-search" />
          </template>
        </InputText>

        <Dropdown
          v-model="filters.direction"
          :options="directionOptions"
          optionLabel="label"
          optionValue="value"
          placeholder="Tip"
          class="filter-direction"
          @change="onFilterChange"
        />
        <Calendar
          v-model="filters.dateFrom"
          dateFormat="dd.mm.yy"
          placeholder="De la"
          showIcon
          class="filter-date"
          @date-select="onFilterChange"
        />
        <Calendar
          v-model="filters.dateTo"
          dateFormat="dd.mm.yy"
          placeholder="Până la"
          showIcon
          class="filter-date"
          @date-select="onFilterChange"
        />

        <!-- US-502: Desktop header actions bar (Filter, Reset, Export dropdown) -->
        <div v-if="!isMobile" class="header-actions-bar">
          <Button
            icon="pi pi-filter"
            :class="{ 'filter-active': hasActiveFilters }"
            severity="secondary"
            outlined
            size="small"
            v-tooltip.bottom="'Filtrează'"
            @click="onFilterChange"
          />
          <Button
            icon="pi pi-filter-slash"
            severity="secondary"
            outlined
            size="small"
            v-tooltip.bottom="'Resetează filtrele'"
            @click="clearFilters"
          />
          <Button
            icon="pi pi-download"
            severity="secondary"
            outlined
            size="small"
            v-tooltip.bottom="'Export'"
            @click="toggleExportMenu"
            aria-haspopup="true"
            aria-controls="export_menu"
          />
          <Menu
            ref="exportMenuRef"
            id="export_menu"
            :model="exportMenuItems"
            :popup="true"
          />
        </div>
      </div>

      <!-- Loading -->
      <div v-if="loading" class="loading-container">
        <ProgressSpinner />
      </div>

      <!-- Empty State (US-017: checks unified items including jobs) -->
      <div v-else-if="!unifiedItems.length" class="empty-state">
        <i class="pi pi-inbox"></i>
        <h3>Niciun bon găsit</h3>
        <p>Creează primul bon fiscal folosind butonul "Bon Nou"</p>
      </div>

      <!-- Mobile: Compact Cards (US-017: uses unifiedItems to include jobs) -->
      <div v-else-if="isMobile" class="receipt-cards">

        <!-- US-038: Mobile Selection Header -->
        <div v-if="mobileSelectionMode" class="mobile-selection-header">
          <div class="selection-header-left">
            <Button
              icon="pi pi-times"
              text
              rounded
              severity="secondary"
              @click="exitMobileSelectionMode"
              class="exit-selection-btn"
            />
            <span class="selection-count">{{ selectedReceipts.length }} selectate</span>
          </div>
          <div class="selection-header-right">
            <Button
              label="Toate"
              icon="pi pi-check-square"
              text
              size="small"
              @click="selectAllMobile"
            />
          </div>
        </div>

        <div
          v-for="item in unifiedItems"
          :key="getItemKey(item)"
          class="receipt-card"
          :class="[getMobileCardClass(item), { 'card-selected': mobileSelectionMode && isRowSelected(item) }]"
          @click="handleMobileCardClick($event, item)"
          @touchstart="handleMobileCardTouchStart($event, item)"
          @touchmove="handleMobileCardTouchMove"
          @touchend="handleMobileCardTouchEnd"
        >
          <!-- US-017: Job card layout -->
          <template v-if="isJobItem(item)">
            <!-- Row 1: Filename + Status indicator -->
            <div class="card-row-1">
              <div class="partner-info">
                <span class="partner job-filename-mobile">
                  <i class="pi pi-file"></i>
                  {{ item.filename }}
                </span>
                <!-- US-406: Different subtitle for failed vs processing jobs -->
                <span v-if="item.processing_status === 'failed'" class="cui text-error">
                  Eroare OCR
                </span>
                <span v-else class="cui text-muted">Se procesează...</span>
              </div>
              <div class="amount-block">
                <span class="amount text-muted">-</span>
              </div>
              <!-- US-406: Show error icon for failed, spinner for processing -->
              <span v-if="item.processing_status === 'failed'" class="job-failed-indicator-mobile">
                <i class="pi pi-exclamation-triangle"></i>
              </span>
              <span v-else class="job-processing-indicator-mobile">
                <i class="pi pi-spin pi-spinner"></i>
              </span>
            </div>
            <!-- Row 2: Job info -->
            <div class="card-row-2">
              <!-- US-406: Show truncated error message for failed jobs -->
              <span
                v-if="item.processing_status === 'failed' && item.processing_error"
                class="text-error job-error-message"
                v-tooltip.top="item.processing_error"
              >
                {{ truncateErrorMessage(item.processing_error) }}
              </span>
              <span v-else-if="item.processing_status === 'failed'" class="text-error">
                Procesare eșuată
              </span>
              <span v-else class="text-muted">În procesare</span>
            </div>
            <!-- Row 3: Processing status -->
            <div class="card-row-3">
              <!-- US-406: Show error badge for failed, processing badge for others -->
              <span
                v-if="item.processing_status === 'failed'"
                class="processing-badge processing-failed"
                v-tooltip.top="item.processing_error || 'Eroare la procesare'"
              >
                <i class="pi pi-exclamation-triangle"></i>
                Eroare
              </span>
              <span v-else class="processing-badge processing-active">
                <i class="pi pi-spin pi-spinner"></i>
                {{ item.processing_status === 'pending' ? 'În așteptare' : 'Procesare' }}
              </span>
              <span v-if="item.batch_id" class="batch-badge">
                <i class="pi pi-folder"></i> Lot
              </span>
            </div>
          </template>

          <!-- Receipt card layout (existing) -->
          <template v-else>
            <!-- US-038: Selection checkmark indicator -->
            <div v-if="mobileSelectionMode && isRowSelected(item)" class="selection-checkmark">
              <i class="pi pi-check"></i>
            </div>

            <!-- Row 1: Partner + CUI + Filename | Amount block + Menu -->
            <div class="card-row-1">
              <div class="partner-info">
                <span class="partner">{{ item.partner_name || '-' }}</span>
                <span v-if="item.cui" class="cui">{{ item.cui }}</span>
                <!-- US-036: Show filename under partner on mobile -->
                <span v-if="getReceiptFilename(item)" class="filename-mobile">
                  <i class="pi pi-file"></i>
                  {{ truncateFilename(getReceiptFilename(item)) }}
                </span>
              </div>
              <div class="amount-block">
                <span class="amount">{{ formatAmount(item.amount) }}</span>
                <span v-if="item.tva_total" class="amount-detail tva">TVA {{ formatAmount(item.tva_total) }}</span>
                <span v-if="getPaymentMethodLabel(item)" class="amount-detail payment">{{ getPaymentMethodLabel(item) }}</span>
              </div>
              <Button
                icon="pi pi-ellipsis-v"
                text
                rounded
                size="small"
                :disabled="isProcessing(item)"
                @click.stop="toggleMenu($event, item)"
              />
            </div>
            <!-- Row 2: Date + Nr + Doc type + Direction -->
            <div class="card-row-2">
              <span>{{ formatDateShort(item.receipt_date) }}</span>
              <span v-if="item.receipt_number" class="sep">•</span>
              <span v-if="item.receipt_number" class="receipt-nr">Nr. {{ item.receipt_number }}</span>
              <span class="sep">•</span>
              <span>{{ item.receipt_type === 'bon_fiscal' ? 'Bon' : 'Chit' }}</span>
              <span class="sep">•</span>
              <span :class="item.direction === 'cheltuiala' ? 'direction-out' : 'direction-in'">
                {{ item.direction === 'cheltuiala' ? 'Plată' : 'Încasare' }}
              </span>
            </div>
            <!-- Row 3: Status + Processing + Batch + Created by + Attachments -->
            <div class="card-row-3">
              <span :class="['status-badge-small', getStatusClass(item.status)]">{{ getStatusLabel(item.status) }}</span>

              <!-- Processing Status Badge -->
              <span
                v-if="item.processing_status === 'pending' || item.processing_status === 'processing'"
                class="processing-badge processing-active"
              >
                <i class="pi pi-spin pi-spinner"></i> Procesare
              </span>
              <span
                v-else-if="item.processing_status === 'failed'"
                class="processing-badge processing-failed"
                v-tooltip.top="item.processing_error"
              >
                <i class="pi pi-exclamation-triangle"></i> Eroare
              </span>

              <!-- Batch Badge -->
              <span v-if="item.batch_id" class="batch-badge">
                <i class="pi pi-folder"></i> Lot
              </span>

              <span class="created-by">{{ item.created_by }}</span>
              <span v-if="item.attachments?.length" class="attachments">
                <i class="pi pi-paperclip"></i>{{ item.attachments.length }}
              </span>

              <!-- US-035: Edit + Retry buttons for failed receipts -->
              <template v-if="item.processing_status === 'failed'">
                <Button
                  icon="pi pi-pencil"
                  severity="secondary"
                  text
                  rounded
                  size="small"
                  class="edit-btn-mobile"
                  v-tooltip.top="'Editează manual'"
                  @click.stop="editReceipt(item.id)"
                />
                <Button
                  icon="pi pi-refresh"
                  severity="warning"
                  text
                  rounded
                  size="small"
                  class="retry-btn-mobile"
                  :loading="retryingReceipts[item.id]"
                  @click.stop="retryReceipt(item)"
                />
              </template>
            </div>
          </template>
        </div>

        <!-- Mobile Pagination -->
        <div class="mobile-pagination" v-if="pagination.total > pagination.pageSize">
          <Button
            icon="pi pi-chevron-left"
            size="small"
            severity="secondary"
            :disabled="pagination.page <= 1"
            @click="prevPage"
          />
          <span class="page-info">{{ pagination.page }} / {{ Math.ceil(pagination.total / pagination.pageSize) }}</span>
          <Button
            icon="pi pi-chevron-right"
            size="small"
            severity="secondary"
            :disabled="pagination.page >= Math.ceil(pagination.total / pagination.pageSize)"
            @click="nextPage"
          />
        </div>

        <!-- US-103: Mobile Selection Bottom Bar (using shared component) -->
        <MobileSelectionFooter
          :visible="mobileSelectionMode && selectedReceipts.length > 0"
          :actions="mobileSelectionActions"
        />
      </div>

      <!-- US-608: Mobile FAB SpeedDial with animated options -->
      <SpeedDial
        v-if="isMobile && !mobileSelectionMode && fabVisible"
        :model="speedDialItems"
        :radius="80"
        direction="up"
        type="linear"
        showIcon="pi pi-plus"
        hideIcon="pi pi-times"
        :mask="true"
        class="mobile-speed-dial"
        :transitionDelay="40"
        :pt="{
          root: { class: 'mobile-speed-dial-root' },
          button: { class: 'mobile-speed-dial-button' },
          menu: { class: 'mobile-speed-dial-menu' },
          menuitem: { class: 'mobile-speed-dial-item' },
          action: { class: 'mobile-speed-dial-action' },
          mask: { class: 'mobile-speed-dial-mask' }
        }"
      />

      <!-- US-103: Mobile Bottom Navigation (using shared component) -->
      <!-- US-307: Using default nav items (Dashboard, Bonuri, Facturi, Setări) -->
      <MobileBottomNav v-if="isMobile && !mobileSelectionMode" />

      <!-- Desktop: Compact Data Table with Batch Grouping (US-002) -->
      <!-- US-104: Explicit !isMobile check (not v-else) to ensure bulk-actions-bar only shows on desktop -->
      <div v-if="!isMobile && !loading && unifiedItems.length" class="data-table-container">
        <!-- Bulk Actions Bar -->
        <div v-if="selectedReceipts.length > 0" class="bulk-actions-bar">
          <span class="selection-info">
            <i class="pi pi-check-square"></i>
            {{ selectedReceipts.length }} selectate
          </span>
          <div class="bulk-buttons">
            <Button
              v-if="selectedReceipts.every(r => r.status === 'pending_review')"
              label="Validează selectate"
              icon="pi pi-check"
              severity="success"
              size="small"
              :loading="bulkApproving"
              @click="approveSelected"
            />
            <Button
              label="Șterge"
              icon="pi pi-trash"
              severity="danger"
              size="small"
              @click="confirmBulkDelete"
            />
            <Button
              label="Deselectează"
              icon="pi pi-times"
              severity="secondary"
              size="small"
              @click="selectedReceipts = []"
            />
          </div>
        </div>

        <!-- Grouped receipts by batch (US-002) -->
        <div class="batch-groups-container">
          <div
            v-for="group in groupedReceipts"
            :key="group.batchId || '__manual__'"
            class="batch-group"
          >
            <!-- Batch Group Header -->
            <BatchGroupHeader
              :batch-id="group.batchId"
              :processing-started-at="group.processingStartedAt"
              :items="group.items"
              :is-expanded="isGroupExpanded(group.batchId)"
              :retrying="retryingBatches[group.batchId]"
              @toggle="toggleGroup(group.batchId)"
              @retry-all="handleRetryBatchFailed"
              @cancel-all="handleCancelBatchAll"
            />

            <!-- Group Content (DataTable) - shown when expanded -->
            <div
              v-show="isGroupExpanded(group.batchId)"
              class="batch-group-content"
            >
              <DataTable
                v-model:selection="selectedReceipts"
                :value="group.items"
                responsiveLayout="scroll"
                stripedRows
                class="compact-table grouped-table"
                :dataKey="(item) => getItemKey(item)"
                :rowClass="getRowClass"
              >
                <!-- US-010: Custom selection column with disabled checkboxes for processing rows -->
                <Column headerStyle="width: 3rem">
                  <template #header>
                    <Checkbox
                      :modelValue="isAllSelectableSelected(group.items)"
                      :disabled="!hasSelectableItems(group.items)"
                      @update:modelValue="toggleSelectAll(group.items, $event)"
                      binary
                    />
                  </template>
                  <template #body="{ data }">
                    <Checkbox
                      v-if="!isProcessing(data)"
                      :modelValue="isRowSelected(data)"
                      @update:modelValue="toggleRowSelection(data, $event)"
                      binary
                    />
                    <Checkbox
                      v-else
                      :modelValue="false"
                      :disabled="true"
                      binary
                      v-tooltip.top="processingTooltip"
                    />
                  </template>
                </Column>

                <!-- US-017/US-036: Fișier column - shows filename for jobs and receipts -->
                <Column field="filename" header="Fișier" style="min-width: 140px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="job-filename" v-tooltip.top="data.filename">
                        <i class="pi pi-file"></i>
                        {{ truncateFilename(data.filename) }}
                      </span>
                    </template>
                    <template v-else>
                      <!-- US-036: Show original filename for receipts from attachments -->
                      <span
                        v-if="getReceiptFilename(data)"
                        class="receipt-filename"
                        v-tooltip.top="getReceiptFilename(data)"
                      >
                        <i class="pi pi-file"></i>
                        {{ truncateFilename(getReceiptFilename(data)) }}
                      </span>
                      <span v-else class="text-muted">-</span>
                    </template>
                  </template>
                </Column>

                <Column field="receipt_date" header="Data" style="width: 90px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      {{ formatDate(data.receipt_date) }}
                    </template>
                  </template>
                </Column>

                <Column field="receipt_number" header="Nr." style="width: 80px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      {{ data.receipt_number || '-' }}
                    </template>
                  </template>
                </Column>

                <Column field="receipt_type" header="Doc" style="width: 60px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <Tag :value="data.receipt_type === 'bon_fiscal' ? 'Bon' : 'Chit'" size="small" />
                    </template>
                  </template>
                </Column>

                <Column field="direction" header="Tip" style="width: 70px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <Tag
                        :value="data.direction === 'cheltuiala' ? 'Plată' : 'Încas.'"
                        :severity="data.direction === 'cheltuiala' ? 'danger' : 'success'"
                        size="small"
                      />
                    </template>
                  </template>
                </Column>

                <Column field="partner_name" header="Furnizor/Client" style="min-width: 120px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      {{ data.partner_name || '-' }}
                    </template>
                  </template>
                </Column>

                <Column field="cui" header="CUI" style="width: 100px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      {{ data.cui || '-' }}
                    </template>
                  </template>
                </Column>

                <Column field="amount" header="Suma" style="width: 90px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <strong>{{ formatAmount(data.amount) }}</strong>
                    </template>
                  </template>
                </Column>

                <Column field="tva_total" header="TVA" style="width: 70px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <span v-if="data.tva_total">{{ formatAmount(data.tva_total) }}</span>
                      <span v-else class="text-muted">-</span>
                    </template>
                  </template>
                </Column>

                <Column field="payment_methods" header="Plată" style="width: 100px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <span v-if="getPaymentMethodLabel(data)">{{ getPaymentMethodLabel(data) }}</span>
                      <span v-else class="text-muted">-</span>
                    </template>
                  </template>
                </Column>

                <Column field="created_by" header="Creat de" style="width: 100px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      {{ data.created_by || '-' }}
                    </template>
                  </template>
                </Column>

                <Column field="created_at" header="Creat la" style="width: 120px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      {{ formatDateTime(data.created_at) }}
                    </template>
                  </template>
                </Column>

                <Column field="status" header="Status" style="width: 100px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <span :class="['status-badge', getStatusClass(data.status)]">
                        {{ getStatusLabel(data.status) }}
                      </span>
                    </template>
                  </template>
                </Column>

                <Column field="processing_status" header="Procesare" style="width: 120px">
                  <template #body="{ data }">
                    <ProcessingStatusCell
                      :status="data.processing_status"
                      :batch-id="data.batch_id"
                      :processing-error="data.processing_error"
                    />
                  </template>
                </Column>

                <Column field="attachments" header="Ataș." style="width: 60px">
                  <template #body="{ data }">
                    <template v-if="isJobItem(data)">
                      <span class="text-muted">-</span>
                    </template>
                    <template v-else>
                      <Badge :value="data.attachments?.length || 0" />
                    </template>
                  </template>
                </Column>

                <Column header="Acțiuni" style="width: 120px">
                  <template #body="{ data }">
                    <div class="button-group">
                      <!-- View - only for receipts (not jobs) -->
                      <Button
                        v-if="!isJobItem(data)"
                        icon="pi pi-eye"
                        severity="info"
                        text
                        rounded
                        size="small"
                        @click="viewReceipt(data.id)"
                      />
                      <!-- US-006: Retry button for failed receipts -->
                      <Button
                        v-if="!isJobItem(data) && data.processing_status === 'failed'"
                        icon="pi pi-refresh"
                        severity="warning"
                        text
                        rounded
                        size="small"
                        :loading="retryingReceipts[data.id]"
                        v-tooltip.top="'Reîncercă procesarea'"
                        @click="retryReceipt(data)"
                      />
                      <!-- US-035: Edit button for failed receipts - quick access to manual editing -->
                      <Button
                        v-if="!isJobItem(data) && data.processing_status === 'failed'"
                        icon="pi pi-pencil"
                        severity="secondary"
                        text
                        rounded
                        size="small"
                        v-tooltip.top="'Editează manual'"
                        @click="editReceipt(data.id)"
                      />
                      <!-- US-010: Menu button disabled for processing rows -->
                      <Button
                        v-if="!isJobItem(data)"
                        icon="pi pi-ellipsis-v"
                        text
                        rounded
                        size="small"
                        :disabled="isProcessing(data)"
                        @click="toggleMenu($event, data)"
                        v-tooltip.top="isProcessing(data) ? processingTooltip : null"
                      />
                      <!-- US-017/US-018/US-020: Job row actions -->
                      <template v-if="isJobItem(data)">
                        <!-- US-018: Failed job - show error indicator (no Cancel, has Retry) -->
                        <span
                          v-if="data.processing_status === 'failed'"
                          class="job-failed-indicator"
                          v-tooltip.top="data.processing_error || 'Eroare la procesare'"
                        >
                          <i class="pi pi-exclamation-triangle"></i>
                        </span>
                        <!-- US-020: Pending/Processing job - show Cancel button + spinner -->
                        <template v-else>
                          <Button
                            icon="pi pi-times"
                            severity="danger"
                            text
                            rounded
                            size="small"
                            class="cancel-job-btn"
                            :loading="cancellingJobs[data.job_id]"
                            v-tooltip.top="'Anulează procesarea'"
                            @click="confirmCancelJob(data)"
                          />
                          <span
                            class="job-processing-indicator"
                            v-tooltip.top="'Fișierul se procesează...'"
                          >
                            <i class="pi pi-spin pi-spinner"></i>
                          </span>
                        </template>
                      </template>
                    </div>
                  </template>
                </Column>
              </DataTable>
            </div>
          </div>
        </div>

        <!-- Pagination (separate from grouped tables) -->
        <div class="grouped-pagination" v-if="pagination.total > pagination.pageSize">
          <Paginator
            :rows="pagination.pageSize"
            :totalRecords="pagination.total"
            :first="(pagination.page - 1) * pagination.pageSize"
            @page="onPageChange"
            template="FirstPageLink PrevPageLink PageLinks NextPageLink LastPageLink"
          />
        </div>
      </div>

      <!-- Shared Context Menu (for both mobile and desktop) - US-516: MD3 styled -->
      <Menu ref="menuRef" :model="menuItems" popup class="receipt-action-menu" />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted, watch, watchEffect } from 'vue'
import { useRouter } from 'vue-router'
import { useToast } from 'primevue/usetoast'
import { useConfirm } from 'primevue/useconfirm'
import { useReceiptsStore } from '@data-entry/stores/receiptsStore'
import { useCompanyStore, useAuthStore, useAccountingPeriodStore } from '@data-entry/stores/sharedStores'
import { useBatchProgressStore } from '@data-entry/stores/batchProgressStore'
import Menu from 'primevue/menu'
import Dialog from 'primevue/dialog'
import Textarea from 'primevue/textarea'
import Dropdown from 'primevue/dropdown'
import Checkbox from 'primevue/checkbox'
import MobileDrawerMenu from '@shared/components/mobile/MobileDrawerMenu.vue'
import BottomSheet from '@shared/components/mobile/BottomSheet.vue'
import DragDropOverlay from '@data-entry/components/bulk/DragDropOverlay.vue'
// US-103: Mobile Material Design common components
import MobileTopBar from '@shared/components/mobile/MobileTopBar.vue'
import MobileBottomNav from '@shared/components/mobile/MobileBottomNav.vue'
import MobileSelectionFooter from '@shared/components/mobile/MobileSelectionFooter.vue'
import { exportToExcel, exportToPDF } from '@reports/utils/exportUtils'
import BatchGroupHeader from '@data-entry/components/bulk/BatchGroupHeader.vue'
import ProcessingStatusCell from '@data-entry/components/bulk/ProcessingStatusCell.vue'
import Paginator from 'primevue/paginator'
import SpeedDial from 'primevue/speeddial'
import { sseService } from '@data-entry/services/sseService'

const router = useRouter()
const toast = useToast()
const confirm = useConfirm()
const store = useReceiptsStore()
const companyStore = useCompanyStore()
const periodStore = useAccountingPeriodStore()
const batchProgressStore = useBatchProgressStore()
const authStore = useAuthStore()

// Rejection dialog state
const rejectDialogVisible = ref(false)
const rejectReason = ref('')
const receiptToReject = ref(null)

// Bulk selection state
const selectedReceipts = ref([])
const bulkApproving = ref(false)

// US-038: Mobile long-press selection mode state
const mobileSelectionMode = ref(false)
let longPressTimer = null
let longPressStartPos = { x: 0, y: 0 }
const LONG_PRESS_DURATION = 500 // ms
const LONG_PRESS_MOVE_THRESHOLD = 10 // px - tolerance for finger movement

// Batch grouping state (US-002)
const expandedGroups = ref(new Set())

// US-006: Retry state - tracks which receipts/batches are currently being retried
const retryingReceipts = ref({})
const retryingBatches = ref({})

// US-020: Cancel state - tracks which jobs are currently being cancelled
const cancellingJobs = ref({})
// US-020: Animation state - tracks jobs in fade-out animation before removal
const cancellingJobIds = ref(new Set())

// US-019: Animation state - tracks which items have recently transitioned status
// Items in highlightCompleted will get the green highlight animation
// Items in highlightFailed will get the red highlight animation
const highlightCompleted = ref(new Set())
const highlightFailed = ref(new Set())

// US-019: Animation duration - matches CSS animation (2s)
const HIGHLIGHT_ANIMATION_DURATION_MS = 2000

// Refs for bulk upload via button
const dragDropOverlayRef = ref(null)
const bulkFileInputRef = ref(null)

// US-009: Auto-resume polling state (legacy fallback, see US-032 for SSE)
let autoRefreshInterval = null
const AUTO_REFRESH_INTERVAL_MS = 5000 // 5 seconds between refreshes when processing active

// US-032: SSE connection state - replaces polling for real-time updates
let isSSEConnected = false

// Mobile detection
const isMobile = ref(window.innerWidth < 768)
const showFilters = ref(false)
const handleResize = () => {
  isMobile.value = window.innerWidth < 768
}

// US-040: Mobile Android-Native Layout State
const showDrawer = ref(false)
const moreMenuRef = ref(null)
const fabVisible = ref(true)

// US-502: Export Menu ref and items for desktop header actions
const exportMenuRef = ref(null)

// US-608: FAB scroll hide/show state
let lastScrollY = 0
let scrollTimeout = null

// Handle logout from drawer menu
const handleLogout = async () => {
  await authStore.logout()
  router.push('/login')
}

// US-040: Toggle more menu (3-dot menu)
const toggleMoreMenu = (event) => {
  moreMenuRef.value?.toggle(event)
}

// US-040: More menu items
const moreMenuItems = computed(() => [
  {
    label: 'Actualizează',
    icon: 'pi pi-refresh',
    command: () => {
      store.fetchReceipts()
      store.fetchStats()
    }
  },
  {
    label: 'Resetează filtre',
    icon: 'pi pi-filter-slash',
    command: () => clearFilters()
  },
  { separator: true },
  {
    label: 'Setări',
    icon: 'pi pi-cog',
    command: () => router.push('/settings')
  }
])

// US-502: Toggle export menu
const toggleExportMenu = (event) => {
  exportMenuRef.value?.toggle(event)
}

// US-502: Export menu items (PDF and XLSX)
const exportMenuItems = computed(() => [
  {
    label: 'Export PDF',
    icon: 'pi pi-file-pdf',
    command: () => exportAllReceiptsPDF()
  },
  {
    label: 'Export XLSX',
    icon: 'pi pi-file-excel',
    command: () => exportAllReceipts()
  }
])

// US-608: SpeedDial Items - Bon Nou Manual, Scanare Bon OCR, Upload în Masă
const speedDialItems = [
  {
    label: 'Bon Nou Manual',
    icon: 'pi pi-pencil',
    command: () => goToCreate()
  },
  {
    label: 'Scanare Bon OCR',
    icon: 'pi pi-camera',
    command: () => goToOCRScan()
  },
  {
    label: 'Upload în Masă',
    icon: 'pi pi-upload',
    command: () => openBulkFileInput()
  }
]

// US-608: Navigate to OCR scan page
const goToOCRScan = () => {
  console.log('[ReceiptsList] Navigating to OCR scan...')
  router.push({ path: '/data-entry/create', query: { mode: 'scan' } }).catch(err => {
    console.error('[ReceiptsList] Navigation error:', err)
  })
}

// US-103/US-306/US-609: Top bar actions for MobileTopBar component
const mobileTopBarActions = computed(() => {
  if (mobileSelectionMode.value) {
    // Selection mode - show select all action
    return [
      { id: 'select-all', icon: 'pi pi-check-square', label: 'Selectează tot', tooltip: 'Selectează tot' }
    ]
  }
  // Normal mode - show filter, reset, export, more menu (US-306, US-609)
  return [
    { id: 'filter', icon: 'pi pi-filter', active: hasActiveFilters.value, tooltip: 'Filtre' },
    { id: 'reset', icon: 'pi pi-filter-slash', tooltip: 'Resetează Filtrele' },
    { id: 'export', icon: 'pi pi-download', tooltip: 'Export Excel' },
    { id: 'more', icon: 'pi pi-ellipsis-v', tooltip: 'Mai multe' }
  ]
})

// US-103/US-306/US-609: Handle top bar action clicks
const handleTopBarAction = (action) => {
  switch (action.id) {
    case 'select-all':
      selectAllMobile()
      break
    case 'filter':
      showFilters.value = !showFilters.value
      break
    case 'reset':
      // US-609: Reset all filters to default values
      clearFilters()
      break
    case 'export':
      // US-502: Show export menu (PDF/XLSX dropdown) on mobile too
      if (exportMenuRef.value) {
        const btn = document.querySelector('.mobile-top-bar .top-bar-btn:nth-child(3)')
        if (btn) {
          exportMenuRef.value.toggle({ currentTarget: btn })
        }
      }
      break
    case 'more':
      // The more menu needs to be toggled with the event, but we don't have the event here
      // So we need to use a different approach - we'll keep using toggleMoreMenu directly
      // by storing a ref to trigger it
      if (moreMenuRef.value) {
        // Create a synthetic event at the more button position
        const btn = document.querySelector('.mobile-top-bar .top-bar-btn:last-child')
        if (btn) {
          moreMenuRef.value.toggle({ currentTarget: btn })
        }
      }
      break
  }
}

// US-307: Removed custom mobileBottomNavItems - using MobileBottomNav defaults
// Upload functionality moved to FAB (US-303)

// US-103/US-113: Selection footer actions for MobileSelectionFooter component
// Bonuri batch actions: Delete + Export
const mobileSelectionActions = computed(() => [
  {
    label: 'Șterge',
    icon: 'pi pi-trash',
    severity: 'danger',
    handler: () => confirmBulkDelete()
  },
  {
    label: 'Export',
    icon: 'pi pi-download',
    severity: 'secondary',
    handler: () => exportSelectedReceipts()
  }
])

// US-040: Handle scroll to show/hide FAB
const handleScroll = () => {
  if (!isMobile.value) return

  const currentScrollY = window.scrollY
  const scrollDelta = currentScrollY - lastScrollY

  // Clear any pending timeout
  if (scrollTimeout) {
    clearTimeout(scrollTimeout)
  }

  // Hide FAB when scrolling down, show when scrolling up
  if (scrollDelta > 10) {
    // Scrolling down - hide FAB
    fabVisible.value = false
  } else if (scrollDelta < -10) {
    // Scrolling up - show FAB
    fabVisible.value = true
  }

  lastScrollY = currentScrollY

  // Show FAB after scroll stops (300ms delay)
  scrollTimeout = setTimeout(() => {
    fabVisible.value = true
  }, 300)
}

onMounted(async () => {
  window.addEventListener('resize', handleResize)
  // US-040: Add scroll listener for FAB visibility
  window.addEventListener('scroll', handleScroll, { passive: true })

  await store.fetchStats()
  await store.fetchReceipts()

  // US-018: Set up callback for when jobs transition to completed/failed
  // This triggers a receipt refresh so the row can transition from job to receipt
  batchProgressStore.setOnJobsTransitionCallback(handleJobsTransition)

  // US-032: Set up SSE callback for real-time status updates
  sseService.onStatusChange(handleSSEStatusChange)

  // US-033: Set up polling callback for graceful degradation
  // When SSE fails, sseService will use this callback for fallback polling
  sseService.setPollingCallback(async () => {
    console.log('[ReceiptsList] Polling fallback: refreshing data')
    await store.fetchReceipts()
    await store.fetchStats()

    // Check if processing is done
    if (inProcessingCount.value === 0) {
      console.log('[ReceiptsList] Processing complete during polling, disconnecting')
      disconnectSSE()
      batchProgressStore.clearAllStoredBatches()
    }
  })

  // US-009/US-032: Check for active processing and start SSE/polling if needed
  await checkAndResumeProcessing()

  // US-038: Add listener for tap outside to exit selection mode
  document.addEventListener('click', handleOutsideTap)
})

onUnmounted(() => {
  window.removeEventListener('resize', handleResize)
  // US-040: Clean up scroll listener
  window.removeEventListener('scroll', handleScroll)
  if (scrollTimeout) {
    clearTimeout(scrollTimeout)
  }

  // US-032: Clean up SSE connection (also cleans up polling fallback via US-033)
  disconnectSSE()

  // US-009: Clean up auto-refresh interval (legacy fallback)
  stopAutoRefresh()

  // US-018: Clean up transition callback
  batchProgressStore.setOnJobsTransitionCallback(null)

  // US-032: Unregister SSE callback
  sseService.onStatusChange(null)

  // US-033: Unregister polling callback
  sseService.setPollingCallback(null)

  // US-038: Clean up outside tap listener
  document.removeEventListener('click', handleOutsideTap)
})

// Watch for company changes and reload data
watch(
  () => companyStore.selectedCompany,
  async (newCompany, oldCompany) => {
    // Skip initial load (handled by onMounted) and same company
    if (!oldCompany || !newCompany) return
    if (newCompany.id_firma === oldCompany.id_firma) return

    console.log('[ReceiptsList] Company changed, reloading data...')
    // Reset filters and pagination when company changes
    filters.value = {
      status: null,
      search: '',
      direction: null,
      dateFrom: null,
      dateTo: null,
      processingStatus: null, // US-005
    }
    store.clearFilters()
    await store.fetchStats()
    await store.fetchReceipts()
  }
)

// Mobile context menu
const menuRef = ref()
const selectedReceipt = ref(null)

const toggleMenu = (event, receipt) => {
  selectedReceipt.value = receipt
  menuRef.value.toggle(event)
}

// US-516: Receipt action menu items with MD3 styling classes
const menuItems = computed(() => {
  const r = selectedReceipt.value
  if (!r) return []
  const items = [
    { label: 'Vizualizează', icon: 'pi pi-eye', command: () => viewReceipt(r.id) }
  ]

  // DRAFT: Edit + Submit + Delete
  if (r.status === 'draft') {
    items.push({ label: 'Editează', icon: 'pi pi-pencil', command: () => editReceipt(r.id) })
    items.push({ label: 'Spre aprobare', icon: 'pi pi-send', command: () => confirmSubmit(r) })
    items.push({ separator: true })
    items.push({ label: 'Șterge', icon: 'pi pi-trash', class: 'menu-item-danger', command: () => confirmDelete(r) })
  }

  // PENDING: Approve + Reject
  if (r.status === 'pending_review') {
    items.push({ separator: true })
    items.push({ label: 'Validează', icon: 'pi pi-check', class: 'menu-item-success', command: () => confirmApprove(r) })
    items.push({ label: 'Respinge', icon: 'pi pi-times', class: 'menu-item-danger', command: () => openRejectDialog(r) })
  }

  // APPROVED: Unapprove
  if (r.status === 'approved') {
    items.push({ separator: true })
    items.push({ label: 'Anulare Validare', icon: 'pi pi-undo', class: 'menu-item-warning', command: () => confirmUnapprove(r) })
  }

  // REJECTED: Edit + Resubmit + Delete
  if (r.status === 'rejected') {
    items.push({ label: 'Editează', icon: 'pi pi-pencil', command: () => editReceipt(r.id) })
    items.push({ label: 'Retrimite', icon: 'pi pi-replay', command: () => confirmResubmit(r) })
    items.push({ separator: true })
    items.push({ label: 'Șterge', icon: 'pi pi-trash', class: 'menu-item-danger', command: () => confirmDelete(r) })
  }

  return items
})

const filters = ref({
  status: null,
  search: '',
  direction: null,
  dateFrom: null,
  dateTo: null,
  processingStatus: null, // US-005: processing_status filter
})

// Direction filter options
const directionOptions = [
  { value: null, label: 'Toate' },
  { value: 'cheltuiala', label: 'Plăți' },
  { value: 'incasare', label: 'Încasări' },
]

// Status filter options (for mobile dropdown)
const statusOptions = [
  { value: null, label: 'Toate' },
  { value: 'draft', label: 'Ciorne' },
  { value: 'pending_review', label: 'În așteptare' },
  { value: 'approved', label: 'Validate' },
  { value: 'rejected', label: 'Respinse' },
]

const receipts = computed(() => store.receipts)
const loading = computed(() => store.loading)
const pagination = computed(() => store.pagination)
const stats = computed(() => store.stats)
// US-005: Processing stats from store
const processingStats = computed(() => store.processingStats)

// ============ US-017: Unified Items (Receipts + Pending Jobs) ============

/**
 * Check if an item is a pending job (not yet a receipt).
 * Jobs have job_id but no receipt id.
 *
 * @param {Object} item - Receipt or Job object
 * @returns {boolean} True if item is a job, false if receipt
 */
const isJobItem = (item) => {
  return item._isJob === true
}

/**
 * Get a unique key for an item (used as Vue :key).
 * Jobs use job_id, receipts use id.
 *
 * @param {Object} item - Receipt or Job object
 * @returns {string|number} Unique key
 */
const getItemKey = (item) => {
  return isJobItem(item) ? `job_${item.job_id}` : item.id
}

/**
 * Unified list of items: receipts from store + active jobs from batchProgressStore.
 * Jobs are converted to a receipt-like structure for consistent rendering.
 *
 * This computed merges two data sources:
 * 1. receipts from receiptsStore (existing receipts from backend)
 * 2. jobs from batchProgressStore (pending/processing/failed jobs)
 *
 * US-018: When a job completes and becomes a receipt:
 * - The receipts list is refreshed (via handleJobsTransition callback)
 * - The completed job is filtered out because its receipt_id now exists in receipts
 * - The row smoothly transitions from job display to receipt display (same position)
 *
 * Failed jobs remain as job rows with error_message displayed.
 * Completed jobs with receipt_id matching an existing receipt are filtered out.
 */
const unifiedItems = computed(() => {
  const receiptsList = receipts.value || []
  const jobsMap = batchProgressStore.jobs || new Map()

  // Get all receipt_ids from receipts to filter out completed jobs
  const existingReceiptIds = new Set()
  for (const r of receiptsList) {
    existingReceiptIds.add(r.id)
  }

  // Convert jobs to receipt-like objects for unified rendering
  // Include: pending, processing, and failed jobs
  // Exclude: completed jobs whose receipt_id is already in receipts list
  const jobItems = []

  for (const [jobId, job] of jobsMap.entries()) {
    // US-018: Skip completed jobs that have a receipt_id matching an existing receipt
    // This is the "transition" - the job row is replaced by the receipt row
    if (job.status === 'completed' && job.receipt_id && existingReceiptIds.has(job.receipt_id)) {
      continue
    }

    // Include pending, processing, and failed jobs
    // - pending/processing: active jobs waiting to be processed
    // - failed: jobs that failed OCR, show with error message
    if (job.status !== 'pending' && job.status !== 'processing' && job.status !== 'failed') {
      // Skip 'completed' jobs without matching receipt (edge case - receipt not yet fetched)
      // They'll appear once the receipts list is refreshed
      continue
    }

    // Convert job to receipt-like structure
    jobItems.push({
      // Mark as job for identification
      _isJob: true,
      job_id: jobId,
      // Use job_id as temporary id for grouping/keying
      id: `job_${jobId}`,
      // Display filename in the "Fișier" column
      filename: job.filename,
      // Batch info from batchProgressStore
      batch_id: batchProgressStore.batchId,
      // Processing status maps directly
      processing_status: job.status, // 'pending', 'processing', or 'failed'
      processing_error: job.error_message,
      processing_started_at: null, // Will be set from batch info
      // Empty/placeholder values for receipt columns
      receipt_date: null,
      receipt_number: null,
      receipt_type: null,
      direction: null,
      partner_name: null,
      cui: null,
      amount: null,
      tva_total: null,
      payment_methods: null,
      status: null, // No workflow status yet
      created_by: null,
      created_at: null,
      attachments: []
    })
  }

  // Combine receipts and jobs
  // Jobs should appear at the top since they're most recent uploads
  return [...jobItems, ...receiptsList]
})

// US-005: Count for "În procesare" chip (pending + processing)
const inProcessingCount = computed(() => {
  return (processingStats.value?.pending_count || 0) + (processingStats.value?.processing_count || 0)
})

// US-005: Count for "Cu erori" chip
const failedCount = computed(() => processingStats.value?.failed_count || 0)

// Check if any filters are active (for mobile filter indicator)
const hasActiveFilters = computed(() => {
  return (
    filters.value.status !== null ||
    filters.value.search !== '' ||
    filters.value.direction !== null ||
    filters.value.dateFrom !== null ||
    filters.value.dateTo !== null ||
    filters.value.processingStatus !== null // US-005
  )
})

// ============ Batch Grouping (US-002) ============

/**
 * Group items (receipts + pending jobs) by batch_id for visual grouping in DataTable.
 * - Groups with batch_id are sorted by processing_started_at descending
 * - Items without batch_id (manual) go into a special "null" group at the end
 *
 * US-017: Now uses unifiedItems which includes both receipts and pending jobs
 */
const groupedReceipts = computed(() => {
  const groups = new Map()

  // Group items by batch_id (includes both receipts and jobs)
  for (const item of unifiedItems.value) {
    const batchId = item.batch_id || '__manual__' // Use special key for manual items
    if (!groups.has(batchId)) {
      groups.set(batchId, {
        batchId: item.batch_id, // null for manual
        processingStartedAt: item.processing_started_at,
        items: []
      })
    }
    groups.get(batchId).items.push(item)
  }

  // Convert to array and sort
  const groupsArray = Array.from(groups.values())

  // Sort groups: batches with processing_started_at descending, manual group at end
  groupsArray.sort((a, b) => {
    // Manual group (no batch_id) always at the end
    if (!a.batchId && b.batchId) return 1
    if (a.batchId && !b.batchId) return -1
    if (!a.batchId && !b.batchId) return 0

    // Both have batch_id: sort by processing_started_at descending (most recent first)
    const dateA = a.processingStartedAt ? new Date(a.processingStartedAt).getTime() : 0
    const dateB = b.processingStartedAt ? new Date(b.processingStartedAt).getTime() : 0
    return dateB - dateA
  })

  return groupsArray
})

/**
 * Check if a group has active processing (pending or processing status)
 */
const hasActiveProcessing = (group) => {
  return group.items.some(
    item => item.processing_status === 'pending' || item.processing_status === 'processing'
  )
}

// ============ US-010: Row Lock for Processing Receipts ============

/**
 * Check if an item (receipt or job) is currently being processed.
 * These items should be read-only with disabled actions.
 *
 * US-017: Job items with pending/processing status are in processing state
 * US-018: Failed jobs are NOT in processing state - they can have actions (retry)
 */
const isProcessing = (item) => {
  // Job items: check status (failed jobs are not "processing")
  if (isJobItem(item)) {
    return item.processing_status === 'pending' || item.processing_status === 'processing'
  }
  // Receipt items check processing_status
  return item.processing_status === 'pending' || item.processing_status === 'processing'
}

/**
 * Tooltip message for disabled buttons on processing rows
 */
const processingTooltip = 'Fișierul se procesează'

/**
 * Get row CSS class based on data.
 * Adds .row-processing class for receipts/jobs in pending/processing state.
 * Adds .row-failed class for failed jobs AND failed receipts.
 * US-019: Adds highlight animation classes for status transitions.
 *
 * US-017: Also applies to job items (which are always processing)
 * US-018: Failed jobs get different styling
 * US-019: Items that recently transitioned get highlight animation classes
 * US-035: Failed receipts also get row-failed styling for visual identification
 */
const getRowClass = (data) => {
  const classes = []

  // US-019: Check for highlight animation classes (status transitions)
  const itemKey = getItemKey(data)
  if (highlightCompleted.value.has(itemKey)) {
    classes.push('row-highlight-completed')
  } else if (highlightFailed.value.has(itemKey)) {
    classes.push('row-highlight-failed')
  }

  // US-020: Check for cancel fade-out animation
  if (isJobItem(data) && cancellingJobIds.value.has(data.job_id)) {
    classes.push('row-cancelling')
  }

  if (isJobItem(data)) {
    // US-018: Failed jobs get different styling than processing jobs
    if (data.processing_status === 'failed') {
      classes.push('row-failed')
    } else {
      // Pending/processing jobs
      classes.push('row-processing')
    }
  } else {
    // Receipt items check processing_status
    if (isProcessing(data)) {
      classes.push('row-processing')
    } else if (data.processing_status === 'failed') {
      // US-035: Failed receipts get red highlight for easy identification
      classes.push('row-failed')
    }
  }

  return classes.join(' ')
}

/**
 * US-019: Get CSS classes for mobile card including highlight animations.
 * Similar to getRowClass but for mobile cards.
 * US-035: Adds card-failed class for failed receipts.
 *
 * @param {Object} data - Receipt or Job object
 * @returns {Object} Class object for Vue :class binding
 */
const getMobileCardClass = (data) => {
  const itemKey = getItemKey(data)
  // US-035: Check if this is a failed receipt (not job, and has processing_status='failed')
  const isFailedReceipt = !isJobItem(data) && data.processing_status === 'failed'
  return {
    'card-processing': isProcessing(data),
    'card-job': isJobItem(data),
    'card-failed': isFailedReceipt,
    'card-highlight-completed': highlightCompleted.value.has(itemKey),
    'card-highlight-failed': highlightFailed.value.has(itemKey)
  }
}

/**
 * Check if a row is currently selected
 */
const isRowSelected = (data) => {
  return selectedReceipts.value.some(r => r.id === data.id)
}

/**
 * Toggle selection for a single row
 */
const toggleRowSelection = (data, selected) => {
  if (selected) {
    // Add to selection if not already selected
    if (!isRowSelected(data)) {
      selectedReceipts.value = [...selectedReceipts.value, data]
    }
  } else {
    // Remove from selection
    selectedReceipts.value = selectedReceipts.value.filter(r => r.id !== data.id)
  }
}

/**
 * Check if group has selectable items (non-processing)
 */
const hasSelectableItems = (items) => {
  return items.some(item => !isProcessing(item))
}

/**
 * Check if all selectable items in group are selected
 */
const isAllSelectableSelected = (items) => {
  const selectableItems = items.filter(item => !isProcessing(item))
  if (selectableItems.length === 0) return false
  return selectableItems.every(item => isRowSelected(item))
}

/**
 * Toggle select all for group (only selectable items)
 */
const toggleSelectAll = (items, selected) => {
  const selectableItems = items.filter(item => !isProcessing(item))
  if (selected) {
    // Add all selectable items that aren't already selected
    const currentIds = new Set(selectedReceipts.value.map(r => r.id))
    const toAdd = selectableItems.filter(item => !currentIds.has(item.id))
    selectedReceipts.value = [...selectedReceipts.value, ...toAdd]
  } else {
    // Remove all items from this group from selection
    const groupIds = new Set(items.map(item => item.id))
    selectedReceipts.value = selectedReceipts.value.filter(r => !groupIds.has(r.id))
  }
}

// ============================================================================
// US-038: Mobile Long-Press Selection Mode
// ============================================================================

/**
 * Handle touch start on mobile card - initiates long-press timer
 */
const handleMobileCardTouchStart = (event, item) => {
  // Don't start long-press for items in processing
  if (isJobItem(item) || isProcessing(item)) return

  const touch = event.touches[0]
  longPressStartPos = { x: touch.clientX, y: touch.clientY }

  longPressTimer = setTimeout(() => {
    // Activate selection mode and select this item
    mobileSelectionMode.value = true
    if (!isRowSelected(item)) {
      toggleRowSelection(item, true)
    }
    // Provide haptic feedback if available
    if (navigator.vibrate) {
      navigator.vibrate(50)
    }
  }, LONG_PRESS_DURATION)
}

/**
 * Handle touch move - cancel long-press if finger moves too much
 */
const handleMobileCardTouchMove = (event) => {
  if (!longPressTimer) return

  const touch = event.touches[0]
  const deltaX = Math.abs(touch.clientX - longPressStartPos.x)
  const deltaY = Math.abs(touch.clientY - longPressStartPos.y)

  // Cancel if moved beyond threshold (user is scrolling)
  if (deltaX > LONG_PRESS_MOVE_THRESHOLD || deltaY > LONG_PRESS_MOVE_THRESHOLD) {
    clearTimeout(longPressTimer)
    longPressTimer = null
  }
}

/**
 * Handle touch end - cancel long-press timer
 */
const handleMobileCardTouchEnd = () => {
  if (longPressTimer) {
    clearTimeout(longPressTimer)
    longPressTimer = null
  }
}

/**
 * Handle mobile card click/tap
 * - In selection mode: toggle selection
 * - Normal mode: navigate to receipt
 */
const handleMobileCardClick = (event, item) => {
  // Ignore if this is a job item
  if (isJobItem(item)) return

  if (mobileSelectionMode.value) {
    // In selection mode: toggle selection (don't navigate)
    event.preventDefault()
    event.stopPropagation()

    // Don't allow selecting items in processing
    if (isProcessing(item)) return

    toggleRowSelection(item, !isRowSelected(item))
  } else {
    // Normal mode: navigate to view receipt
    viewReceipt(item.id)
  }
}

/**
 * Exit mobile selection mode and clear selection
 */
const exitMobileSelectionMode = () => {
  mobileSelectionMode.value = false
  selectedReceipts.value = []
}

/**
 * Handle tap outside cards to exit selection mode
 */
const handleOutsideTap = (event) => {
  if (!mobileSelectionMode.value) return

  // Check if tap is outside any receipt card or selection UI elements
  const target = event.target
  const isInsideCard = target.closest('.receipt-card')
  const isInsideSelectionHeader = target.closest('.mobile-selection-header')
  const isInsideBottomBar = target.closest('.mobile-selection-bottom-bar')

  if (!isInsideCard && !isInsideSelectionHeader && !isInsideBottomBar) {
    exitMobileSelectionMode()
  }
}

/**
 * Select all visible items on mobile
 */
const selectAllMobile = () => {
  const selectableItems = unifiedItems.value.filter(item => !isJobItem(item) && !isProcessing(item))
  const currentIds = new Set(selectedReceipts.value.map(r => r.id))
  const toAdd = selectableItems.filter(item => !currentIds.has(item.id))
  selectedReceipts.value = [...selectedReceipts.value, ...toAdd]
}

/**
 * Toggle group expansion
 */
const toggleGroup = (groupKey) => {
  const key = groupKey || '__manual__'
  if (expandedGroups.value.has(key)) {
    expandedGroups.value.delete(key)
  } else {
    expandedGroups.value.add(key)
  }
  // Trigger reactivity
  expandedGroups.value = new Set(expandedGroups.value)
}

/**
 * Check if group is expanded
 */
const isGroupExpanded = (groupKey) => {
  const key = groupKey || '__manual__'
  return expandedGroups.value.has(key)
}

/**
 * Auto-expand groups with active processing on data change
 */
watchEffect(() => {
  const newExpandedGroups = new Set(expandedGroups.value)

  for (const group of groupedReceipts.value) {
    const key = group.batchId || '__manual__'
    if (hasActiveProcessing(group)) {
      newExpandedGroups.add(key)
    }
  }

  // Only update if there are changes
  if (newExpandedGroups.size !== expandedGroups.value.size) {
    expandedGroups.value = newExpandedGroups
  }
})

const formatDate = (dateStr) => {
  if (!dateStr) return '-'
  const date = new Date(dateStr)
  return date.toLocaleDateString('ro-RO')
}

/**
 * US-017: Truncate filename for display in table column.
 * Shows first 20 characters with ellipsis if longer.
 *
 * @param {string} filename - Original filename
 * @returns {string} Truncated filename
 */
const truncateFilename = (filename) => {
  if (!filename) return '-'
  const maxLength = 20
  if (filename.length <= maxLength) return filename
  return filename.substring(0, maxLength) + '...'
}

/**
 * US-406: Truncate error message for display on mobile card row 2.
 * Shows first 40 characters with ellipsis if longer.
 * Full error available via tooltip on hover/click.
 *
 * @param {string} error - Error message from OCR processing
 * @returns {string} Truncated error message
 */
const truncateErrorMessage = (error) => {
  if (!error) return 'Eroare necunoscută'
  const maxLength = 40
  if (error.length <= maxLength) return error
  return error.substring(0, maxLength) + '...'
}

/**
 * US-036: Get original filename from a receipt's attachments.
 * For receipts, the original_filename is stored in attachments[0].filename.
 * For jobs, the filename is directly on the job object.
 *
 * @param {Object} item - Receipt or Job object
 * @returns {string|null} Original filename or null if not available
 */
const getReceiptFilename = (item) => {
  // For job items, filename is directly available
  if (isJobItem(item)) {
    return item.filename || null
  }
  // For receipts, get from first attachment
  if (item.attachments && item.attachments.length > 0) {
    return item.attachments[0].filename || null
  }
  return null
}

const formatDateShort = (dateStr) => {
  if (!dateStr) return '-'
  const date = new Date(dateStr)
  return date.toLocaleDateString('ro-RO', { day: '2-digit', month: '2-digit' })
}

const formatAmount = (amount) => {
  return new Intl.NumberFormat('ro-RO', {
    minimumFractionDigits: 2,
    maximumFractionDigits: 2,
  }).format(amount)
}

const getStatusClass = (status) => {
  const classes = {
    draft: 'status-draft',
    pending_review: 'status-pending',
    approved: 'status-approved',
    rejected: 'status-rejected',
    synced: 'status-synced',
  }
  return classes[status] || ''
}

const getStatusLabel = (status) => {
  const labels = {
    draft: 'Ciornă',
    pending_review: 'În așteptare',
    approved: 'Validat',
    rejected: 'Respins',
    synced: 'Sincronizat',
  }
  return labels[status] || status
}

const formatDateTime = (dateStr) => {
  if (!dateStr) return '-'
  const date = new Date(dateStr)
  return date.toLocaleDateString('ro-RO', {
    day: '2-digit',
    month: '2-digit',
    hour: '2-digit',
    minute: '2-digit',
  })
}

const getPaymentMethodLabel = (receipt) => {
  if (!receipt.payment_methods || receipt.payment_methods.length === 0) return null
  // Combine payment methods: "Card 50 / Num 30"
  return receipt.payment_methods.map(pm => {
    const method = pm.method === 'CARD' ? 'Card' : 'Num'
    return `${method} ${formatAmount(pm.amount)}`
  }).join(' / ')
}

const filterByStatus = async (status) => {
  if (filters.value.status === status) return // Do nothing if already selected
  filters.value.status = status
  await onFilterChange()
}

// US-005: Filter by processing status
const filterByProcessingStatus = async (processingStatus) => {
  // Toggle off if already selected
  if (filters.value.processingStatus === processingStatus) {
    filters.value.processingStatus = null
  } else {
    filters.value.processingStatus = processingStatus
  }
  await onFilterChange()
}

const onFilterChange = async () => {
  selectedReceipts.value = [] // Clear selection on filter change
  store.setFilters({
    status: filters.value.status,
    search: filters.value.search,
    direction: filters.value.direction,
    dateFrom: filters.value.dateFrom
      ? filters.value.dateFrom.toISOString().split('T')[0]
      : null,
    dateTo: filters.value.dateTo
      ? filters.value.dateTo.toISOString().split('T')[0]
      : null,
    processingStatus: filters.value.processingStatus, // US-005
  })
  await store.fetchReceipts()
}

const clearFilters = async () => {
  selectedReceipts.value = [] // Clear selection on filter reset
  filters.value = {
    status: null,
    search: '',
    direction: null,
    dateFrom: null,
    dateTo: null,
    processingStatus: null, // US-005
  }
  store.clearFilters()
  await store.fetchReceipts()
}

// US-503: Clear filters and close BottomSheet (for mobile)
const clearFiltersAndClose = async () => {
  await clearFilters()
  showFilters.value = false
}

// US-503: Apply filters and close BottomSheet (for mobile)
const applyFiltersAndClose = async () => {
  await onFilterChange()
  showFilters.value = false
}

// Refresh data (for mobile toolbar)
const refreshData = async () => {
  await store.fetchReceipts()
  await store.fetchStats()
  toast.add({
    severity: 'success',
    summary: 'Actualizare reușită',
    detail: 'Datele au fost actualizate',
    life: 3000
  })
}

// ============ Bulk Upload Handler ============

/**
 * Open the hidden file input for bulk upload (triggered by button click).
 * This is the alternative to drag & drop for mobile devices.
 */
const openBulkFileInput = () => {
  if (bulkFileInputRef.value) {
    bulkFileInputRef.value.click()
  }
}

/**
 * Handle file selection from the hidden file input.
 * Passes the files to DragDropOverlay's handleFiles for validation and upload.
 *
 * US-037 FIX: On mobile browsers (Chrome Android/iOS), File objects can be invalidated
 * by the browser's SnapshotState mechanism after the event handler completes.
 * We MUST:
 * 1. Convert FileList to Array immediately (Array.from doesn't access file content)
 * 2. Pass to handleFiles which clones files to memory BEFORE accessing properties
 * 3. Only reset the input AFTER handleFiles completes (awaited)
 *
 * See: https://issues.chromium.org/40703873
 */
const onBulkFileInputChange = async (event) => {
  const files = event.target?.files
  if (!files || files.length === 0) return

  // Convert FileList to Array immediately - this is safe, doesn't read file content
  const filesArray = Array.from(files)

  // Use the DragDropOverlay's handleFiles method for consistent behavior
  // CRITICAL: handleFiles clones files to memory before accessing properties
  if (dragDropOverlayRef.value?.handleFiles) {
    await dragDropOverlayRef.value.handleFiles(filesArray)
  }

  // Reset the file input so the same files can be selected again
  // CRITICAL: Only reset AFTER handleFiles completes to avoid invalidating files
  if (bulkFileInputRef.value) {
    bulkFileInputRef.value.value = ''
  }
}

/**
 * Handle bulk upload completion from DragDropOverlay.
 * Refreshes the receipts list to show new items.
 * US-009: Stores batch ID for auto-resume.
 * US-017: Pre-populates jobs for instant display in table.
 * US-032: Starts SSE connection instead of polling.
 */
const onBulkUploadComplete = async (uploadResult) => {
  console.log('[ReceiptsList] Bulk upload complete:', uploadResult)

  // US-009/US-017: Register the batch ID and start polling with initial jobs
  if (uploadResult.batchId) {
    // US-017: Pass initial jobs with filenames for instant display
    // Jobs will appear immediately in the table while polling fetches real status
    batchProgressStore.startPolling(uploadResult.batchId, uploadResult.jobs)
  }

  // Refresh data after a short delay to allow backend processing to start
  // The new receipts will appear with processing_status='pending'
  setTimeout(async () => {
    await store.fetchReceipts()
    await store.fetchStats()

    // US-032: Connect to SSE for real-time updates instead of polling
    connectSSE(uploadResult.batchId)
  }, 500)
}

const onPageChange = async (event) => {
  selectedReceipts.value = [] // Clear selection on page change
  store.setPage(event.page + 1)
  await store.fetchReceipts()
}

const prevPage = async () => {
  if (pagination.value.page > 1) {
    store.setPage(pagination.value.page - 1)
    await store.fetchReceipts()
  }
}

const nextPage = async () => {
  const totalPages = Math.ceil(pagination.value.total / pagination.value.pageSize)
  if (pagination.value.page < totalPages) {
    store.setPage(pagination.value.page + 1)
    await store.fetchReceipts()
  }
}

const goToCreate = () => {
  console.log('[ReceiptsList] Navigating to create...')
  router.push('/data-entry/create').catch(err => {
    console.error('[ReceiptsList] Navigation error:', err)
  })
}

const viewReceipt = (id) => {
  router.push(`/data-entry/${id}`)
}

const editReceipt = (id) => {
  router.push(`/data-entry/${id}/edit`)
}

const confirmDelete = (receipt) => {
  // US-517: MD3 styled delete dialog with receipt details
  const storeName = receipt.store_name || 'Magazin necunoscut'
  const amount = receipt.total
    ? new Intl.NumberFormat('ro-RO', { style: 'currency', currency: 'RON' }).format(receipt.total)
    : 'sumă necunoscută'

  confirm.require({
    message: `Ești sigur că vrei să ștergi bonul de la "${storeName}" în valoare de ${amount}?`,
    header: 'Șterge bonul?',
    icon: 'pi pi-trash',
    acceptClass: 'p-button-danger',
    rejectClass: 'p-button-text',
    acceptLabel: 'Șterge',
    rejectLabel: 'Anulează',
    accept: async () => {
      try {
        await store.deleteReceipt(receipt.id)
        toast.add({
          severity: 'success',
          summary: 'Succes',
          detail: 'Bonul a fost șters',
          life: 3000,
        })
        await store.fetchReceipts()
        await store.fetchStats()
      } catch (error) {
        toast.add({
          severity: 'error',
          summary: 'Eroare',
          detail: error.message || 'Nu s-a putut șterge bonul',
          life: 5000,
        })
      }
    },
  })
}

// ============ Workflow Actions ============

const confirmSubmit = (receipt) => {
  confirm.require({
    message: 'Sigur doriți să trimiteți bonul spre aprobare? Statusul va deveni "În așteptare".',
    header: 'Confirmare trimitere',
    icon: 'pi pi-send',
    acceptClass: 'p-button-success',
    acceptLabel: 'Trimite',
    rejectLabel: 'Anulează',
    accept: async () => {
      try {
        await store.submitReceipt(receipt.id)
        toast.add({
          severity: 'success',
          summary: 'Succes',
          detail: 'Bonul a fost trimis spre aprobare',
          life: 3000,
        })
        await store.fetchReceipts()
        await store.fetchStats()
      } catch (error) {
        toast.add({
          severity: 'error',
          summary: 'Eroare',
          detail: error.message || 'Nu s-a putut trimite bonul',
          life: 5000,
        })
      }
    },
  })
}

const confirmApprove = (receipt) => {
  confirm.require({
    message: 'Sigur doriți să validați acest bon?',
    header: 'Confirmare validare',
    icon: 'pi pi-check',
    acceptClass: 'p-button-success',
    acceptLabel: 'Validează',
    rejectLabel: 'Anulează',
    accept: async () => {
      try {
        await store.approveReceipt(receipt.id)
        toast.add({
          severity: 'success',
          summary: 'Succes',
          detail: 'Bonul a fost validat',
          life: 3000,
        })
        await store.fetchReceipts()
        await store.fetchStats()
      } catch (error) {
        toast.add({
          severity: 'error',
          summary: 'Eroare',
          detail: error.message || 'Nu s-a putut valida bonul',
          life: 5000,
        })
      }
    },
  })
}

const confirmUnapprove = (receipt) => {
  confirm.require({
    message: 'Sigur doriți să anulați validarea acestui bon? Bonul va reveni în starea "În așteptare".',
    header: 'Confirmare anulare validare',
    icon: 'pi pi-undo',
    acceptClass: 'p-button-warning',
    acceptLabel: 'Anulează Validarea',
    rejectLabel: 'Renunță',
    accept: async () => {
      try {
        await store.unapproveReceipt(receipt.id)
        toast.add({
          severity: 'success',
          summary: 'Succes',
          detail: 'Validarea a fost anulată',
          life: 3000,
        })
        await store.fetchReceipts()
        await store.fetchStats()
      } catch (error) {
        toast.add({
          severity: 'error',
          summary: 'Eroare',
          detail: error.message || 'Nu s-a putut anula validarea',
          life: 5000,
        })
      }
    },
  })
}

const openRejectDialog = (receipt) => {
  receiptToReject.value = receipt
  rejectReason.value = ''
  rejectDialogVisible.value = true
}

const executeReject = async () => {
  if (!receiptToReject.value || rejectReason.value.length < 5) return

  try {
    await store.rejectReceipt(receiptToReject.value.id, rejectReason.value)
    toast.add({
      severity: 'success',
      summary: 'Succes',
      detail: 'Bonul a fost respins',
      life: 3000,
    })
    rejectDialogVisible.value = false
    await store.fetchReceipts()
    await store.fetchStats()
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: error.message || 'Nu s-a putut respinge bonul',
      life: 5000,
    })
  }
}

const confirmResubmit = (receipt) => {
  confirm.require({
    message: 'Sigur doriți să retrimiteți bonul spre aprobare?',
    header: 'Confirmare retrimitere',
    icon: 'pi pi-replay',
    acceptClass: 'p-button-success',
    acceptLabel: 'Retrimite',
    rejectLabel: 'Anulează',
    accept: async () => {
      try {
        await store.resubmitReceipt(receipt.id)
        toast.add({
          severity: 'success',
          summary: 'Succes',
          detail: 'Bonul a fost retrimis spre aprobare',
          life: 3000,
        })
        await store.fetchReceipts()
        await store.fetchStats()
      } catch (error) {
        toast.add({
          severity: 'error',
          summary: 'Eroare',
          detail: error.message || 'Nu s-a putut retrimite bonul',
          life: 5000,
        })
      }
    },
  })
}

// ============ Bulk Actions ============

const approveSelected = async () => {
  const pendingOnly = selectedReceipts.value.filter(r => r.status === 'pending_review')
  if (!pendingOnly.length) return

  bulkApproving.value = true
  let successCount = 0
  let errorCount = 0

  for (const receipt of pendingOnly) {
    try {
      await store.approveReceipt(receipt.id)
      successCount++
    } catch (error) {
      errorCount++
    }
  }

  bulkApproving.value = false
  selectedReceipts.value = []

  if (successCount > 0) {
    toast.add({
      severity: 'success',
      summary: 'Succes',
      detail: `${successCount} bonuri validate`,
      life: 3000,
    })
  }

  if (errorCount > 0) {
    toast.add({
      severity: 'warn',
      summary: 'Atenție',
      detail: `${errorCount} bonuri nu au putut fi validate`,
      life: 5000,
    })
  }

  await store.fetchReceipts()
  await store.fetchStats()
}

/**
 * US-113: Export selected receipts to Excel.
 * Exports receipt data including store name, date, amount, VAT, status etc.
 */
const exportSelectedReceipts = () => {
  const receipts = selectedReceipts.value
  if (receipts.length === 0) {
    toast.add({
      severity: 'warn',
      summary: 'Atenție',
      detail: 'Nu există bonuri selectate pentru export',
      life: 3000,
    })
    return
  }

  // Map receipt data to export format
  const exportData = receipts.map(r => ({
    'Magazin': r.store_name || r.partner_name || '-',
    'CUI': r.cui || '-',
    'Data': r.receipt_date ? formatDate(r.receipt_date) : '-',
    'Nr. Bon': r.receipt_number || '-',
    'Tip Document': r.receipt_type === 'receipt' ? 'Bon' : r.receipt_type === 'invoice' ? 'Factură' : r.receipt_type || '-',
    'Direcție': r.direction === 'expense' ? 'Cheltuială' : r.direction === 'income' ? 'Venit' : r.direction || '-',
    'Suma': r.amount || 0,
    'TVA': r.tva_total || 0,
    'Metodă Plată': getPaymentMethodLabel(r) || '-',
    'Status': getStatusLabel(r.status) || '-',
    'Creat de': r.created_by || '-',
    'Creat la': r.created_at ? formatDate(r.created_at) : '-',
  }))

  const result = exportToExcel(
    exportData,
    `bonuri_selectate_${receipts.length}`,
    'Bonuri'
  )

  if (result.success) {
    toast.add({
      severity: 'success',
      summary: 'Export reușit',
      detail: `${receipts.length} bonuri exportate cu succes`,
      life: 3000,
    })
  } else {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: 'Nu s-a putut exporta lista de bonuri',
      life: 5000,
    })
  }
}

/**
 * US-306: Export all currently visible receipts to Excel.
 * Exports all receipts from the current filtered list (excludes job items).
 */
const exportAllReceipts = () => {
  // Filter out job items - only export actual receipts
  const receiptsList = unifiedItems.value.filter(item => !isJobItem(item))

  if (receiptsList.length === 0) {
    toast.add({
      severity: 'warn',
      summary: 'Atenție',
      detail: 'Nu există bonuri de exportat',
      life: 3000,
    })
    return
  }

  // Map receipt data to export format
  const exportData = receiptsList.map(r => ({
    'Magazin': r.store_name || r.partner_name || '-',
    'CUI': r.cui || '-',
    'Data': r.receipt_date ? formatDate(r.receipt_date) : '-',
    'Nr. Bon': r.receipt_number || '-',
    'Tip Document': r.receipt_type === 'receipt' ? 'Bon' : r.receipt_type === 'invoice' ? 'Factură' : r.receipt_type || '-',
    'Direcție': r.direction === 'expense' ? 'Cheltuială' : r.direction === 'income' ? 'Venit' : r.direction || '-',
    'Suma': r.amount || 0,
    'TVA': r.tva_total || 0,
    'Metodă Plată': getPaymentMethodLabel(r) || '-',
    'Status': getStatusLabel(r.status) || '-',
    'Creat de': r.created_by || '-',
    'Creat la': r.created_at ? formatDate(r.created_at) : '-',
  }))

  const result = exportToExcel(
    exportData,
    `bonuri_export_${receiptsList.length}`,
    'Bonuri'
  )

  if (result.success) {
    toast.add({
      severity: 'success',
      summary: 'Export reușit',
      detail: `${receiptsList.length} bonuri exportate cu succes`,
      life: 3000,
    })
  } else {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: 'Nu s-a putut exporta lista de bonuri',
      life: 5000,
    })
  }
}

/**
 * US-502: Export all currently visible receipts to PDF.
 * Exports all receipts from the current filtered list (excludes job items).
 */
const exportAllReceiptsPDF = () => {
  // Filter out job items - only export actual receipts
  const receiptsList = unifiedItems.value.filter(item => !isJobItem(item))

  if (receiptsList.length === 0) {
    toast.add({
      severity: 'warn',
      summary: 'Atenție',
      detail: 'Nu există bonuri de exportat',
      life: 3000,
    })
    return
  }

  // Map receipt data to export format for PDF
  const exportData = receiptsList.map(r => ({
    store_name: r.store_name || r.partner_name || '-',
    cui: r.cui || '-',
    receipt_date: r.receipt_date ? formatDate(r.receipt_date) : '-',
    receipt_number: r.receipt_number || '-',
    receipt_type: r.receipt_type === 'receipt' ? 'Bon' : r.receipt_type === 'invoice' ? 'Factură' : r.receipt_type || '-',
    direction: r.direction === 'expense' ? 'Cheltuială' : r.direction === 'income' ? 'Venit' : r.direction || '-',
    amount: r.amount || 0,
    tva_total: r.tva_total || 0,
    status: getStatusLabel(r.status) || '-',
  }))

  // Define columns for PDF with proportional widths
  const columns = [
    { field: 'store_name', header: 'Magazin', type: 'text', width: 0.22 },
    { field: 'cui', header: 'CUI', type: 'text', width: 0.10 },
    { field: 'receipt_date', header: 'Data', type: 'text', width: 0.10 },
    { field: 'receipt_number', header: 'Nr. Bon', type: 'text', width: 0.10 },
    { field: 'receipt_type', header: 'Tip', type: 'text', width: 0.08 },
    { field: 'direction', header: 'Direcție', type: 'text', width: 0.10 },
    { field: 'amount', header: 'Suma', type: 'number', width: 0.10 },
    { field: 'tva_total', header: 'TVA', type: 'number', width: 0.10 },
    { field: 'status', header: 'Status', type: 'text', width: 0.10 },
  ]

  const result = exportToPDF(
    exportData,
    columns,
    `bonuri_export_${receiptsList.length}`,
    {
      companyName: companyStore.selectedCompany?.name || '',
      title: 'Lista Bonuri Fiscale',
      period: '',
    }
  )

  if (result.success) {
    toast.add({
      severity: 'success',
      summary: 'Export reușit',
      detail: `${receiptsList.length} bonuri exportate cu succes (PDF)`,
      life: 3000,
    })
  } else {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: 'Nu s-a putut exporta lista de bonuri în format PDF',
      life: 5000,
    })
  }
}

/**
 * US-026: Confirmation dialog for bulk delete.
 * Uses PrimeVue ConfirmDialog to ask user before deleting multiple receipts.
 */
const confirmBulkDelete = () => {
  const count = selectedReceipts.value.length
  if (count === 0) return

  confirm.require({
    message: `Ești sigur că vrei să ștergi ${count} ${count === 1 ? 'bon' : 'bonuri'}?`,
    header: 'Confirmare ștergere',
    icon: 'pi pi-exclamation-triangle',
    acceptClass: 'p-button-danger',
    acceptLabel: 'Șterge',
    rejectLabel: 'Anulează',
    accept: async () => {
      // US-027 will implement the actual bulk delete logic
      await executeBulkDelete()
    },
  })
}

/**
 * US-027: Execute bulk delete for selected receipts.
 * Calls DELETE /receipts/bulk, removes deleted receipts locally,
 * shows appropriate toast, and updates stats.
 */
const executeBulkDelete = async () => {
  const ids = selectedReceipts.value.map(r => r.id)
  const totalCount = ids.length

  if (totalCount === 0) return

  try {
    // Call bulk delete API
    const result = await store.bulkDeleteReceipts(ids)

    const deletedCount = result.deleted?.length || 0
    const failedCount = result.failed?.length || 0

    // Remove deleted receipts from local array (instant, no re-fetch)
    if (deletedCount > 0) {
      store.removeReceiptsLocally(result.deleted)
    }

    // US-028: Navigate to previous page if current page becomes empty
    if (store.receipts.length === 0 && pagination.value.page > 1) {
      store.setPage(pagination.value.page - 1)
      await store.fetchReceipts()
    }

    // Clear selection after delete and exit mobile selection mode (US-039)
    selectedReceipts.value = []
    mobileSelectionMode.value = false

    // Update stats via API call
    await store.fetchStats()

    // Show appropriate toast based on result
    if (failedCount === 0 && deletedCount > 0) {
      // All successful
      toast.add({
        severity: 'success',
        summary: 'Succes',
        detail: `${deletedCount} ${deletedCount === 1 ? 'bon șters' : 'bonuri șterse'}`,
        life: 3000,
      })
    } else if (deletedCount > 0 && failedCount > 0) {
      // Partial success
      toast.add({
        severity: 'warn',
        summary: 'Ștergere parțială',
        detail: `${deletedCount} din ${totalCount} șterse, ${failedCount} au eșuat`,
        life: 5000,
      })
    } else if (deletedCount === 0 && failedCount > 0) {
      // All failed
      const firstError = result.failed[0]?.error || 'Eroare necunoscută'
      toast.add({
        severity: 'error',
        summary: 'Eroare',
        detail: failedCount === 1 ? firstError : `${failedCount} bonuri nu au putut fi șterse`,
        life: 5000,
      })
    }
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: error.message || 'Eroare la ștergerea bonurilor',
      life: 5000,
    })
  }
}

// ============ US-006: Retry Actions ============

/**
 * Retry processing for a single failed receipt.
 * Shows loading state on the retry button during the operation.
 */
const retryReceipt = async (receipt) => {
  retryingReceipts.value[receipt.id] = true

  try {
    const result = await store.retryReceipt(receipt.id)
    toast.add({
      severity: 'success',
      summary: 'Succes',
      detail: result.message || 'Bon reîncarcat în procesare',
      life: 3000,
    })

    // Refresh the list to show updated status
    await store.fetchReceipts()
    await store.fetchStats()
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: error.message || 'Fișierul original nu mai este disponibil',
      life: 5000,
    })
  } finally {
    delete retryingReceipts.value[receipt.id]
  }
}

/**
 * Handler for BatchGroupHeader retry-all event.
 * Sets loading state and calls the retry API.
 */
const handleRetryBatchFailed = async (batchId) => {
  retryingBatches.value[batchId] = true

  try {
    const result = await store.retryBatchFailed(batchId)

    if (result.success) {
      toast.add({
        severity: 'success',
        summary: 'Succes',
        detail: result.message,
        life: 3000,
      })
    }

    if (result.failed_count > 0) {
      toast.add({
        severity: 'warn',
        summary: 'Atenție',
        detail: `${result.failed_count} bonuri nu au putut fi reîncarcate`,
        life: 5000,
      })
    }

    // Refresh the list to show updated status
    await store.fetchReceipts()
    await store.fetchStats()
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: error.message || 'Nu s-au putut reîncărca bonurile',
      life: 5000,
    })
  } finally {
    delete retryingBatches.value[batchId]
  }
}

// ============ US-020: Cancel Job Actions ============

/**
 * Show confirmation dialog for cancelling a job.
 * Uses PrimeVue ConfirmDialog to ask user before cancelling.
 *
 * @param {Object} job - The job item to cancel (from unifiedItems)
 */
const confirmCancelJob = (job) => {
  confirm.require({
    message: `Anulezi procesarea pentru "${job.filename}"?`,
    header: 'Confirmare anulare',
    icon: 'pi pi-times-circle',
    acceptClass: 'p-button-danger',
    acceptLabel: 'Anulează',
    rejectLabel: 'Nu',
    accept: async () => {
      await executeCancelJob(job.job_id, job.filename)
    }
  })
}

/**
 * US-020: Fade-out animation duration for cancelled jobs (matches CSS animation)
 */
const CANCEL_FADEOUT_DURATION_MS = 300

/**
 * Helper to wait for a specified number of milliseconds.
 * @param {number} ms - Milliseconds to wait
 */
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms))

/**
 * Execute the cancel operation for a job.
 * Shows loading state, triggers fade-out animation, then removes the job.
 *
 * Flow:
 * 1. Start loading state on button
 * 2. Start fade-out animation on row
 * 3. Wait for animation to complete (300ms)
 * 4. Call store's cancelJob to actually cancel and remove
 * 5. Show success/error toast
 *
 * @param {string} jobId - The job ID to cancel
 * @param {string} filename - The filename for toast messages
 */
const executeCancelJob = async (jobId, filename) => {
  cancellingJobs.value[jobId] = true

  // US-020: Start fade-out animation immediately
  cancellingJobIds.value.add(jobId)
  cancellingJobIds.value = new Set(cancellingJobIds.value) // Force reactivity

  // Wait for animation to complete before actually removing the row
  await sleep(CANCEL_FADEOUT_DURATION_MS)

  try {
    const result = await batchProgressStore.cancelJob(jobId)

    if (result.success) {
      toast.add({
        severity: 'success',
        summary: 'Succes',
        detail: `Procesarea pentru "${filename}" a fost anulată`,
        life: 3000,
      })
    } else {
      // On failure, remove the animation class (row stays visible)
      cancellingJobIds.value.delete(jobId)
      cancellingJobIds.value = new Set(cancellingJobIds.value)

      toast.add({
        severity: 'error',
        summary: 'Eroare',
        detail: result.message || 'Nu s-a putut anula procesarea',
        life: 5000,
      })
    }
  } catch (error) {
    // On error, remove the animation class (row stays visible)
    cancellingJobIds.value.delete(jobId)
    cancellingJobIds.value = new Set(cancellingJobIds.value)

    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: error.message || 'Nu s-a putut anula procesarea',
      life: 5000,
    })
  } finally {
    delete cancellingJobs.value[jobId]
    // Clean up animation state (already removed from store on success)
    cancellingJobIds.value.delete(jobId)
    cancellingJobIds.value = new Set(cancellingJobIds.value)
  }
}

// ============ US-021: Cancel All Jobs in Batch ============

/**
 * Handler for BatchGroupHeader cancel-all event.
 * Shows confirmation dialog with info about affected files and completed receipts.
 *
 * @param {Object} payload - Event payload from BatchGroupHeader
 * @param {string} payload.batchId - The batch ID to cancel
 * @param {number} payload.pendingProcessingCount - Count of jobs that will be cancelled
 * @param {number} payload.completedCount - Count of completed receipts that will remain
 */
const handleCancelBatchAll = (payload) => {
  const { batchId, pendingProcessingCount, completedCount } = payload

  // Build confirmation message
  let message = `Anulezi procesarea pentru ${pendingProcessingCount} ${pendingProcessingCount === 1 ? 'fișier' : 'fișiere'}?`

  // If there are completed receipts, mention they will remain
  if (completedCount > 0) {
    message += `\n\nBonurile deja procesate (${completedCount}) vor rămâne în sistem.`
  }

  confirm.require({
    message,
    header: 'Confirmare anulare batch',
    icon: 'pi pi-times-circle',
    acceptClass: 'p-button-danger',
    acceptLabel: 'Anulează tot',
    rejectLabel: 'Nu',
    accept: async () => {
      await executeCancelBatch(batchId, pendingProcessingCount)
    }
  })
}

/**
 * Execute batch cancellation.
 * Calls batchProgressStore.cancelBatch and shows toast notification.
 *
 * @param {string} batchId - The batch ID to cancel
 * @param {number} expectedCount - Expected number of cancelled jobs (for toast message)
 */
const executeCancelBatch = async (batchId, expectedCount) => {
  try {
    const result = await batchProgressStore.cancelBatch(batchId)

    if (result.success) {
      toast.add({
        severity: 'success',
        summary: 'Succes',
        detail: result.message || `${result.cancelledCount} fișiere anulate`,
        life: 3000,
      })
    } else {
      toast.add({
        severity: 'error',
        summary: 'Eroare',
        detail: result.message || 'Nu s-a putut anula batch-ul',
        life: 5000,
      })
    }
  } catch (error) {
    toast.add({
      severity: 'error',
      summary: 'Eroare',
      detail: error.message || 'Nu s-a putut anula batch-ul',
      life: 5000,
    })
  }
}

// ============ US-018/US-019: Job Transition Handler with Animations ============

/**
 * Handle job status transitions from batchProgressStore polling.
 * When jobs complete, refresh receipts to fetch the newly created receipt data.
 * When jobs fail, they remain as job rows with error messages displayed.
 *
 * US-019: Also triggers highlight animations for visual feedback.
 *
 * @param {number[]} completedReceiptIds - Receipt IDs from newly completed jobs
 * @param {string[]} failedJobIds - Job IDs of newly failed jobs
 */
const handleJobsTransition = async (completedReceiptIds, failedJobIds) => {
  console.log('[ReceiptsList] Job transitions detected:', {
    completedReceiptIds,
    failedJobIds
  })

  // US-019: Add completed receipts to highlight set for green animation
  // Use the receipt ID (not job_id) since completed jobs become receipts
  for (const receiptId of completedReceiptIds) {
    highlightCompleted.value.add(receiptId)

    // Remove from highlight set after animation completes
    setTimeout(() => {
      highlightCompleted.value.delete(receiptId)
      // Force reactivity update
      highlightCompleted.value = new Set(highlightCompleted.value)
    }, HIGHLIGHT_ANIMATION_DURATION_MS)
  }
  // Force reactivity update for initial additions
  if (completedReceiptIds.length > 0) {
    highlightCompleted.value = new Set(highlightCompleted.value)
  }

  // US-019: Add failed job IDs to highlight set for red animation
  // Use job_id format since failed jobs remain as job rows
  for (const jobId of failedJobIds) {
    const jobKey = `job_${jobId}`
    highlightFailed.value.add(jobKey)

    // Remove from highlight set after animation completes
    setTimeout(() => {
      highlightFailed.value.delete(jobKey)
      // Force reactivity update
      highlightFailed.value = new Set(highlightFailed.value)
    }, HIGHLIGHT_ANIMATION_DURATION_MS)
  }
  // Force reactivity update for initial additions
  if (failedJobIds.length > 0) {
    highlightFailed.value = new Set(highlightFailed.value)
  }

  // US-043: Instead of refreshing all receipts (which reorders by created_at),
  // fetch each completed receipt individually and insert it in place.
  // This maintains the original upload order within the batch.
  if (completedReceiptIds.length > 0) {
    console.log('[ReceiptsList] Fetching completed receipts individually to preserve order')

    // Fetch each receipt and insert at the beginning of the receipts list
    // This keeps them in the position where the jobs were (prepended to receipts)
    for (const receiptId of completedReceiptIds) {
      try {
        const receipt = await store.fetchReceiptById(receiptId)
        if (receipt) {
          // Insert at the beginning of the receipts list (where jobs are prepended)
          // This preserves the visual position - the receipt replaces its job
          store.insertReceiptInPlace(receipt)
          console.log(`[ReceiptsList] Receipt ${receiptId} inserted in place`)
        }
      } catch (err) {
        console.error(`[ReceiptsList] Failed to fetch receipt ${receiptId}:`, err)
      }
    }

    // Update stats separately (doesn't affect list ordering)
    await store.fetchStats()
  }

  // Failed jobs remain in the jobs Map and will be displayed as job rows
  // with error messages - no additional action needed
}

// ============ US-009/US-032: Auto-Resume Processing Functions ============

/**
 * Check for active processing batches and resume SSE/polling if needed.
 * Called on component mount to restore state after page refresh.
 *
 * US-023: Now also restores active jobs to batchProgressStore so they appear in table.
 * US-032: Uses SSE instead of polling for real-time updates.
 */
const checkAndResumeProcessing = async () => {
  // Check localStorage for stored batch IDs
  const storedBatchIds = batchProgressStore.getStoredBatchIds()

  if (storedBatchIds.length === 0) {
    console.log('[ReceiptsList] No stored batch IDs found')

    // US-032: Still check if there are processing receipts from DB
    // (could be from another session or direct API upload)
    if (inProcessingCount.value > 0) {
      console.log('[ReceiptsList] Found processing receipts in DB, connecting SSE')
      connectSSE()
    }
    return
  }

  console.log('[ReceiptsList] Found stored batch IDs:', storedBatchIds)

  // US-023: Restore jobs from each stored batch
  // This fetches current status from API and populates batchProgressStore.jobs
  let totalRestoredJobs = 0

  for (const storedBatchId of storedBatchIds) {
    const result = await batchProgressStore.restoreJobsFromBatch(storedBatchId)
    if (result.hasActiveJobs) {
      totalRestoredJobs += result.jobCount
    }
  }

  // If we restored active jobs, show notification and connect to SSE
  if (totalRestoredJobs > 0) {
    console.log(`[ReceiptsList] Restored ${totalRestoredJobs} active jobs from stored batches`)

    // US-032: Connect to SSE for real-time updates instead of polling
    connectSSE()

    // Show toast notification for resumed processing
    toast.add({
      severity: 'info',
      summary: 'Procesare în curs detectată',
      detail: `${totalRestoredJobs} ${totalRestoredJobs === 1 ? 'fișier' : 'fișiere'} în procesare`,
      life: 4000,
    })
  } else {
    // No active jobs found in stored batches
    // This means all jobs completed/failed between page load and now
    console.log('[ReceiptsList] No active jobs found in stored batches')

    // Check if there are still active processing items in receipts (from DB)
    const hasActiveProcessing = inProcessingCount.value > 0

    if (hasActiveProcessing) {
      // US-032: Connect to SSE for real-time updates instead of polling
      connectSSE()

      toast.add({
        severity: 'info',
        summary: 'Procesare în curs detectată',
        detail: 'Se actualizează statusul automat...',
        life: 4000,
      })
    }
  }
}

/**
 * Start auto-refresh interval to poll for processing updates.
 * Refreshes receipts list every 5 seconds while processing is active.
 *
 * US-032: This is now a fallback mechanism - SSE is the primary method.
 * Only used when SSE connection fails (see US-033 for graceful degradation).
 */
const startAutoRefresh = () => {
  // Don't start if already running
  if (autoRefreshInterval) {
    console.log('[ReceiptsList] Auto-refresh already running')
    return
  }

  console.log('[ReceiptsList] Starting auto-refresh polling')

  autoRefreshInterval = setInterval(async () => {
    // Refresh receipts to get latest processing status
    await store.fetchReceipts()
    await store.fetchStats()

    // Check if processing is still active
    if (inProcessingCount.value === 0) {
      console.log('[ReceiptsList] Processing complete, stopping auto-refresh')
      stopAutoRefresh()

      // Clean up all stored batches since processing is done
      batchProgressStore.clearAllStoredBatches()

      // Show completion toast
      toast.add({
        severity: 'success',
        summary: 'Procesare completă',
        detail: 'Toate bonurile au fost procesate',
        life: 4000,
      })
    }
  }, AUTO_REFRESH_INTERVAL_MS)
}

/**
 * Stop the auto-refresh interval.
 * Called on unmount or when processing completes.
 */
const stopAutoRefresh = () => {
  if (autoRefreshInterval) {
    clearInterval(autoRefreshInterval)
    autoRefreshInterval = null
    console.log('[ReceiptsList] Auto-refresh stopped')
  }
}

// ============ US-032: SSE Integration Functions ============

/**
 * Connect to SSE for real-time status updates.
 * Called when there are receipts in processing state.
 * Replaces polling for more efficient real-time updates.
 *
 * @param {string|null} batchId - Optional batch ID to filter events
 */
const connectSSE = (batchId = null) => {
  if (isSSEConnected) {
    console.log('[ReceiptsList] SSE already connected')
    return
  }

  console.log('[ReceiptsList] Connecting to SSE for real-time updates')
  sseService.connect(batchId)
  isSSEConnected = true

  // Stop polling if it was running - SSE replaces it
  stopAutoRefresh()
}

/**
 * Disconnect from SSE endpoint.
 * Called when no more receipts are in processing state,
 * when component unmounts, or on error.
 */
const disconnectSSE = () => {
  if (!isSSEConnected) {
    return
  }

  console.log('[ReceiptsList] Disconnecting from SSE')
  sseService.disconnect()
  isSSEConnected = false
}

/**
 * Handle SSE status change events.
 * Called by sseService when a receipt status update is received.
 * Updates the receipt in place without re-fetching the entire list.
 *
 * US-032: This is the core of SSE integration - efficient single-row updates.
 *
 * @param {Object} data - Status change event data from SSE
 * @param {number} data.receipt_id - Receipt ID that changed
 * @param {string} data.status - Workflow status (DRAFT, PENDING_REVIEW, etc.)
 * @param {string|null} data.processing_status - Processing status (pending, processing, completed, failed)
 * @param {string|null} data.batch_id - Batch ID this receipt belongs to
 */
const handleSSEStatusChange = async (data) => {
  console.log('[ReceiptsList] SSE status change received:', data)

  // Build updates object from SSE event data
  const updates = {}
  if (data.status !== undefined) {
    updates.status = data.status
  }
  if (data.processing_status !== undefined) {
    updates.processing_status = data.processing_status
  }

  // US-029: Update the receipt in place for efficient single-row re-render
  const wasUpdated = store.updateReceiptInPlace(data.receipt_id, updates)

  if (wasUpdated) {
    console.log(`[ReceiptsList] Receipt ${data.receipt_id} updated in place via SSE`)

    // US-019: Trigger highlight animation for status transitions
    if (data.processing_status === 'completed') {
      highlightCompleted.value.add(data.receipt_id)
      // Force reactivity update
      highlightCompleted.value = new Set(highlightCompleted.value)

      // Remove highlight after animation
      setTimeout(() => {
        highlightCompleted.value.delete(data.receipt_id)
        highlightCompleted.value = new Set(highlightCompleted.value)
      }, HIGHLIGHT_ANIMATION_DURATION_MS)
    } else if (data.processing_status === 'failed') {
      highlightFailed.value.add(data.receipt_id)
      // Force reactivity update
      highlightFailed.value = new Set(highlightFailed.value)

      // Remove highlight after animation
      setTimeout(() => {
        highlightFailed.value.delete(data.receipt_id)
        highlightFailed.value = new Set(highlightFailed.value)
      }, HIGHLIGHT_ANIMATION_DURATION_MS)
    }
  } else {
    // US-034: Receipt not found in current list
    // Instead of refreshing the whole list, check if it belongs to an active batch
    // and fetch/insert it individually to maintain list stability
    console.log(`[ReceiptsList] Receipt ${data.receipt_id} not found in current list`)

    // Check if this receipt belongs to an active batch we're tracking
    const storedBatchIds = batchProgressStore.getStoredBatchIds()
    const belongsToActiveBatch = data.batch_id && storedBatchIds.includes(data.batch_id)

    if (belongsToActiveBatch) {
      // Fetch the individual receipt and insert it locally
      console.log(`[ReceiptsList] Receipt ${data.receipt_id} belongs to active batch ${data.batch_id}, fetching individually...`)
      try {
        const receipt = await store.fetchReceiptById(data.receipt_id)
        if (receipt) {
          store.insertReceiptInPlace(receipt)
          console.log(`[ReceiptsList] Receipt ${data.receipt_id} inserted into list`)

          // Apply highlight animation for newly inserted receipt
          if (data.processing_status === 'completed') {
            highlightCompleted.value.add(data.receipt_id)
            highlightCompleted.value = new Set(highlightCompleted.value)
            setTimeout(() => {
              highlightCompleted.value.delete(data.receipt_id)
              highlightCompleted.value = new Set(highlightCompleted.value)
            }, HIGHLIGHT_ANIMATION_DURATION_MS)
          } else if (data.processing_status === 'failed') {
            highlightFailed.value.add(data.receipt_id)
            highlightFailed.value = new Set(highlightFailed.value)
            setTimeout(() => {
              highlightFailed.value.delete(data.receipt_id)
              highlightFailed.value = new Set(highlightFailed.value)
            }, HIGHLIGHT_ANIMATION_DURATION_MS)
          }
        }
      } catch (err) {
        console.error(`[ReceiptsList] Failed to fetch receipt ${data.receipt_id}:`, err)
      }
    } else {
      // Receipt doesn't belong to an active batch - just update stats
      // User can manually refresh to see the receipt
      console.log(`[ReceiptsList] Receipt ${data.receipt_id} not in active batch, skipping list refresh`)
    }

    // Only update stats, not the full list
    await store.fetchStats()
  }

  // Check if all processing is complete - disconnect SSE if so
  await checkAndDisconnectSSEIfDone()
}

/**
 * Check if all processing is complete and disconnect SSE if so.
 * Also refreshes stats to update the processing counts.
 */
const checkAndDisconnectSSEIfDone = async () => {
  // Refresh stats to get accurate processing counts
  await store.fetchStats()

  // Check if any receipts still processing
  if (inProcessingCount.value === 0) {
    console.log('[ReceiptsList] All processing complete, disconnecting SSE')
    disconnectSSE()

    // Clean up stored batches
    batchProgressStore.clearAllStoredBatches()

    // Show completion toast
    toast.add({
      severity: 'success',
      summary: 'Procesare completă',
      detail: 'Toate bonurile au fost procesate',
      life: 4000,
    })
  }
}

/**
 * Clean up batch IDs from localStorage for batches that have completed.
 * Checks each stored batch against current receipts to find completed ones.
 *
 * @param {string[]} storedBatchIds - Array of stored batch IDs to check
 */
const cleanupCompletedBatches = (storedBatchIds) => {
  // Get unique batch IDs from current receipts that still have active processing
  const activeBatchIds = new Set()

  for (const receipt of receipts.value) {
    if (
      receipt.batch_id &&
      (receipt.processing_status === 'pending' || receipt.processing_status === 'processing')
    ) {
      activeBatchIds.add(receipt.batch_id)
    }
  }

  // Remove stored batches that are no longer active
  for (const storedId of storedBatchIds) {
    if (!activeBatchIds.has(storedId)) {
      console.log(`[ReceiptsList] Batch ${storedId} is complete, removing from storage`)
      batchProgressStore.clearStoredBatch(storedId)
    }
  }
}
</script>

<style scoped>
/* Status Filters + Action Button Row */
/* US-610: Reduced margin-bottom to var(--space-sm) and use design tokens */
.status-actions-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  flex-wrap: wrap;
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-xs);
}

.status-chips {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  flex: 1;
}

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.25rem 0.5rem;
  border-radius: 4px;
  font-size: 0.8rem;
  color: var(--text-color-secondary);
  cursor: pointer;
  transition: all 0.15s ease;
  white-space: nowrap;
  border: 2px solid transparent;
}

.status-chip:hover {
  background: var(--surface-hover);
}

.status-chip.active {
  background: var(--surface-200);
  color: var(--text-color);
  font-weight: 600;
  position: relative;
}

.status-chip.active::after {
  content: '';
  position: absolute;
  bottom: -4px;
  left: 0;
  right: 0;
  height: 3px;
  background: var(--text-color);
  border-radius: 2px;
}

/* Color accents for active status */
.status-chip.status-draft.active { background: var(--blue-100); color: var(--blue-700); }
.status-chip.status-draft.active::after { background: var(--blue-700); }

.status-chip.status-pending.active { background: var(--yellow-100); color: var(--yellow-700); }
.status-chip.status-pending.active::after { background: var(--yellow-600); }

.status-chip.status-approved.active { background: var(--green-100); color: var(--green-700); }
.status-chip.status-approved.active::after { background: var(--green-600); }

.status-chip.status-rejected.active { background: var(--red-100); color: var(--red-700); }
.status-chip.status-rejected.active::after { background: var(--red-600); }

/* Search and Filters Row */
/* US-610: Reduced margin-bottom to var(--space-sm) and use design tokens */
.filters-row {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: var(--space-sm);
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--surface-border);
}

.filter-search {
  flex: 1 1 200px;
  min-width: 200px;
}

.filter-date {
  width: 130px;
}

.filter-direction {
  width: 110px;
}

.filter-status {
  width: 140px;
}

.filter-actions {
  display: flex;
  gap: 0.5rem;
  margin-left: auto;
}

/* US-502: Header actions bar for unified Filter/Reset/Export buttons */
.header-actions-bar {
  display: flex;
  gap: var(--space-xs);
  margin-left: auto;
  align-items: center;
}

.header-actions-bar .p-button {
  min-width: auto;
  padding: var(--space-sm);
}

.header-actions-bar .filter-active {
  background: var(--color-primary);
  border-color: var(--color-primary);
  color: var(--surface-ground);
}

.header-actions-bar .filter-active:hover {
  background: var(--color-primary-dark);
  border-color: var(--color-primary-dark);
}

/* Compact DataTable - styles moved to vendor/primevue-overrides.css */

/* Action buttons - always on same line */
.compact-table .button-group {
  display: flex;
  flex-wrap: nowrap;
  gap: 0.25rem;
}

/* Mobile Cards */
.receipt-cards {
  display: flex;
  flex-direction: column;
  gap: 0.625rem;
}

.receipt-card {
  background: var(--surface-card);
  border-radius: var(--radius-md);
  padding: var(--space-sm) var(--space-md);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  border: 1px solid var(--surface-border);
  transition: all var(--transition-fast);
  position: relative; /* US-038: For absolute positioned checkmark */
}

.receipt-card:active {
  background: var(--surface-ground);
  transform: scale(0.99);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

/* US-038: Mobile Selection Mode Styles */
.mobile-selection-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--space-sm) var(--space-md);
  background: var(--blue-50);
  border-radius: var(--radius-md);
  margin-bottom: var(--space-sm);
  border: 1px solid var(--blue-200);
}

.selection-header-left {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

.selection-header-left .exit-selection-btn {
  color: var(--blue-700);
}

.selection-header-left .selection-count {
  font-weight: var(--font-semibold);
  color: var(--blue-700);
  font-size: var(--text-sm);
}

.selection-header-right {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

/* Selected card state */
.receipt-card.card-selected {
  background: var(--blue-50);
  border-color: var(--blue-500);
}

.receipt-card.card-selected:active {
  background: var(--blue-100);
}

/* Selection checkmark indicator */
.selection-checkmark {
  position: absolute;
  top: var(--space-sm);
  right: var(--space-sm);
  width: 24px;
  height: 24px;
  background: var(--blue-500);
  border-radius: var(--radius-full);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: var(--text-xs);
  z-index: 1;
}

/* Dark mode support for selection */
[data-theme="dark"] .mobile-selection-header {
  background: var(--blue-900);
  border-color: var(--blue-700);
}

[data-theme="dark"] .selection-header-left .exit-selection-btn,
[data-theme="dark"] .selection-header-left .selection-count {
  color: var(--blue-300);
}

[data-theme="dark"] .receipt-card.card-selected {
  background: var(--blue-900);
  border-color: var(--blue-400);
}

[data-theme="dark"] .receipt-card.card-selected:active {
  background: var(--blue-800);
}

[data-theme="dark"] .selection-checkmark {
  background: var(--blue-400);
}

/* Auto dark mode (system preference) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .mobile-selection-header {
    background: var(--blue-900);
    border-color: var(--blue-700);
  }

  :root:not([data-theme]) .selection-header-left .exit-selection-btn,
  :root:not([data-theme]) .selection-header-left .selection-count {
    color: var(--blue-300);
  }

  :root:not([data-theme]) .receipt-card.card-selected {
    background: var(--blue-900);
    border-color: var(--blue-400);
  }

  :root:not([data-theme]) .receipt-card.card-selected:active {
    background: var(--blue-800);
  }

  :root:not([data-theme]) .selection-checkmark {
    background: var(--blue-400);
  }
}

/* US-103: Mobile Selection Bottom Bar styles now in MobileSelectionFooter.vue */

.card-row-1 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.25rem;
}

.card-row-1 .partner-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 0.125rem;
}

.card-row-1 .partner {
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--text-color);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.card-row-1 .cui {
  font-size: 0.7rem;
  color: var(--text-color-secondary);
}

.card-row-1 .amount-block {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.125rem;
}

.card-row-1 .amount {
  font-weight: 700;
  font-size: 0.875rem;
  color: var(--text-color);
}

.card-row-1 .amount-detail {
  font-size: 0.875rem;
  font-weight: 400;
  color: var(--text-color-secondary);
}

.card-row-1 .amount-detail.tva {
  color: var(--green-600);
}

.card-row-1 .amount-detail.payment {
  color: var(--purple-600);
}

.card-row-2 {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.75rem;
  color: var(--text-color-secondary);
  flex-wrap: wrap;
}

.card-row-2 .sep {
  color: var(--surface-border);
}

.card-row-2 .receipt-nr {
  font-weight: 500;
}

.card-row-2 .direction-out {
  color: var(--red-600);
  font-weight: 500;
}

.card-row-2 .direction-in {
  color: var(--green-600);
  font-weight: 500;
}

.card-row-3 {
  display: flex;
  align-items: center;
  gap: 0.375rem;
  font-size: 0.7rem;
  color: var(--text-color-secondary);
  margin-top: 0.25rem;
}

.card-row-3 .sep {
  color: var(--surface-border);
}

.card-row-3 .created-by {
  font-weight: 500;
}

.card-row-3 .attachments {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.125rem;
  color: var(--text-color-secondary);
}

/* Small status badge for mobile cards */
.status-badge-small {
  padding: 0.125rem 0.375rem;
  border-radius: 3px;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
}

.status-badge-small.status-draft { background-color: var(--blue-100); color: var(--blue-600); }
.status-badge-small.status-pending { background-color: var(--orange-100); color: var(--orange-600); }
.status-badge-small.status-approved { background-color: var(--green-100); color: var(--green-600); }
.status-badge-small.status-rejected { background-color: var(--red-100); color: var(--red-600); }
.status-badge-small.status-synced { background-color: var(--teal-100); color: var(--teal-600); }

/* Status text colors for mobile */
.status-text-draft { color: var(--blue-600); }
.status-text-pending_review { color: var(--orange-600); }
.status-text-approved { color: var(--green-600); }
.status-text-rejected { color: var(--red-600); }
.status-text-synced { color: var(--teal-600); }

/* Mobile Pagination */
.mobile-pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 1rem;
  padding: 1rem 0 0.5rem;
  margin-top: 0.5rem;
}

.page-info {
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--text-color-secondary);
  min-width: 60px;
  text-align: center;
}

/* Mobile Toolbar */
.mobile-toolbar-container {
  margin-bottom: 1rem;
}

.mobile-toolbar-buttons {
  display: flex;
  gap: 0.5rem;
  align-items: stretch;
}

/* Action buttons (Filtre, Resetează) - equal width */
.mobile-toolbar-buttons .p-button-outlined {
  flex: 1;
  min-width: 0;
  justify-content: center;
}

/* Bon Nou button - highlighted, takes more space */
.mobile-toolbar-buttons .p-button-success {
  flex: 1.2;
  min-width: 0;
  justify-content: center;
  font-weight: 600;
}

/* Active filter indicator */
.filter-active {
  border-color: var(--primary-color, #2563eb) !important;
  background: rgba(37, 99, 235, 0.05) !important;
  color: var(--primary-color, #2563eb) !important;
}

/* Force green background for Bon Nou button (desktop) */
.status-actions-row .p-button.p-button-success {
  background: var(--green-600, #16a34a) !important;
  border-color: var(--green-600, #16a34a) !important;
  color: white !important;
}

.status-actions-row .p-button.p-button-success:hover {
  background: var(--green-700, #15803d) !important;
  border-color: var(--green-700, #15803d) !important;
}

/* Mobile responsive */
@media (max-width: 768px) {
  .receipts-list-view {
    padding: 0.75rem;
  }

  /* Status + Action Row mobile */
  .status-actions-row {
    gap: 0.75rem;
    justify-content: center;
  }

  .status-chips {
    width: 100%;
    gap: 0.25rem;
    justify-content: center;
  }

  .status-actions-row .p-button {
    flex: 0 0 auto;
    min-width: 140px;
  }

  .status-chip {
    font-size: 0.75rem;
    padding: 0.2rem 0.4rem;
  }

  /* Filters row mobile - compact grid layout */
  .filters-row {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
    padding-bottom: 0.5rem;
  }

  /* Search field - full width at top */
  .filters-row .filter-search {
    grid-column: 1 / -1;
  }

  /* Status dropdown - full width below search */
  .filters-row .filter-status {
    grid-column: 1 / -1;
    width: 100% !important;
  }

  /* Direction and dates - 2 columns */
  .filter-direction,
  .filter-date {
    width: 100% !important;
    min-width: unset !important;
  }

  /* Remove desktop filter action buttons on mobile */
  .filter-actions {
    display: none;
  }
}

/* Reject dialog */
.reject-dialog-content {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.reject-dialog-content p {
  margin: 0;
  color: var(--text-color-secondary);
}

/* Text muted helper */
.text-muted {
  color: var(--text-color-secondary);
}

/* Compact table adjustments - styles moved to vendor/primevue-overrides.css */

/* Bulk Actions Bar */
.bulk-actions-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0.75rem 1rem;
  background: linear-gradient(135deg, var(--blue-50) 0%, var(--blue-100) 100%);
  border: 1px solid var(--blue-200);
  border-radius: 6px;
  margin-bottom: 0.75rem;
}

.selection-info {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  font-weight: 600;
  color: var(--blue-700);
}

.selection-info i {
  font-size: 1.1rem;
}

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

@media (max-width: 768px) {
  .bulk-actions-bar {
    flex-direction: column;
    gap: 0.75rem;
    align-items: stretch;
  }

  .bulk-buttons {
    justify-content: flex-end;
  }
}

/* ============ Batch Grouping Styles (US-002) ============ */

.batch-groups-container {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
}

.batch-group {
  border-radius: var(--radius-md);
  overflow: hidden;
}

.batch-group-content {
  border: 1px solid var(--surface-border);
  border-top: none;
  border-bottom-left-radius: var(--radius-md);
  border-bottom-right-radius: var(--radius-md);
  background: var(--surface-card);
}

/* Remove top border radius on grouped tables */
.batch-group-content .grouped-table {
  border-radius: 0;
}

/* Pagination for grouped view */
.grouped-pagination {
  display: flex;
  justify-content: center;
  padding: var(--space-md) 0;
  margin-top: var(--space-sm);
  border-top: 1px solid var(--surface-border);
}

/* Mobile adjustments for batch groups */
@media (max-width: 768px) {
  .batch-groups-container {
    gap: var(--space-xs);
  }

  .batch-group-content {
    overflow-x: auto;
  }
}

/* ============ US-005: Processing Status Filter Chips ============ */

/* In Processing chip - blue colors */
.status-chip.processing-chip-in-progress {
  background: var(--blue-100);
  color: var(--blue-700);
}

.status-chip.processing-chip-in-progress:hover {
  background: var(--blue-200);
}

.status-chip.processing-chip-in-progress.active {
  background: var(--blue-100);
  color: var(--blue-700);
  font-weight: var(--font-semibold);
}

.status-chip.processing-chip-in-progress.active::after {
  background: var(--blue-700);
}

/* Failed chip - red colors */
.status-chip.processing-chip-failed {
  background: var(--red-100);
  color: var(--red-700);
}

.status-chip.processing-chip-failed:hover {
  background: var(--red-200);
}

.status-chip.processing-chip-failed.active {
  background: var(--red-100);
  color: var(--red-700);
  font-weight: var(--font-semibold);
}

.status-chip.processing-chip-failed.active::after {
  background: var(--red-700);
}

/* ============ US-010: Row Lock for Processing Receipts ============ */
/* Note: PrimeVue row styles are in vendor/primevue-overrides.css */

/* ============ Bulk Upload Button Styles ============ */

/* Hidden file input */
.hidden-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  border: 0;
}

/* Desktop action buttons container */
.desktop-action-buttons {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
}

/* ============ Mobile Toolbar Enhancements ============ */

.mobile-toolbar-container {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  margin-bottom: var(--space-md);
}

.mobile-toolbar-actions {
  justify-content: flex-end;
}

/* ============ Mobile Card Processing & Batch Styles ============ */

/* Card processing state - visual indicator */
.receipt-card.card-processing {
  opacity: 0.85;
  background: linear-gradient(135deg, var(--surface-card) 0%, var(--blue-50) 100%);
  border-color: var(--blue-200);
}

/* Processing badge for mobile cards */
.processing-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.125rem 0.375rem;
  border-radius: 3px;
  font-size: 0.65rem;
  font-weight: var(--font-semibold);
  white-space: nowrap;
}

.processing-badge.processing-active {
  background: var(--blue-100);
  color: var(--blue-700);
}

.processing-badge.processing-failed {
  background: var(--red-100);
  color: var(--red-700);
  cursor: help;
}

/* Batch badge for mobile cards */
.batch-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.125rem;
  padding: 0.125rem 0.375rem;
  border-radius: 3px;
  font-size: 0.65rem;
  font-weight: var(--font-medium);
  background: var(--surface-200);
  color: var(--text-color-secondary);
  white-space: nowrap;
}

.batch-badge i {
  font-size: 0.6rem;
}

/* Retry button in mobile cards */
.retry-btn-mobile {
  margin-left: auto;
  flex-shrink: 0;
}

/* Card row 3 adjustments for new badges */
.card-row-3 {
  flex-wrap: wrap;
  row-gap: 0.25rem;
}

/* Dark mode adjustments */
[data-theme="dark"] .receipt-card.card-processing {
  background: linear-gradient(135deg, var(--surface-card) 0%, var(--blue-900) 100%);
  border-color: var(--blue-700);
}

[data-theme="dark"] .processing-badge.processing-active {
  background: var(--blue-900);
  color: var(--blue-300);
}

[data-theme="dark"] .processing-badge.processing-failed {
  background: var(--red-900);
  color: var(--red-300);
}

[data-theme="dark"] .batch-badge {
  background: var(--surface-700);
  color: var(--text-color-secondary);
}

/* ============ US-017: Job Row Styles ============ */

/* Filename display for job rows */
.job-filename {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--text-color);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.job-filename i {
  color: var(--text-color-secondary);
  flex-shrink: 0;
}

/* Processing indicator in Actions column for jobs */
.job-processing-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--blue-500);
}

.job-processing-indicator .pi-spinner {
  font-size: var(--text-base);
}

/* Dark mode adjustments for job rows */
[data-theme="dark"] .job-filename {
  color: var(--text-color);
}

[data-theme="dark"] .job-filename i {
  color: var(--text-color-secondary);
}

[data-theme="dark"] .job-processing-indicator {
  color: var(--blue-400);
}

/* ============ US-017: Mobile Job Card Styles ============ */

/* Job card styling */
.receipt-card.card-job {
  cursor: default;
  opacity: 0.9;
}

/* Filename display in mobile job cards */
.job-filename-mobile {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  color: var(--text-color);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.job-filename-mobile i {
  color: var(--text-color-secondary);
  flex-shrink: 0;
}

/* Processing indicator in mobile job cards */
.job-processing-indicator-mobile {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--blue-500);
}

/* Dark mode adjustments for mobile job cards */
[data-theme="dark"] .receipt-card.card-job {
  background: linear-gradient(135deg, var(--surface-card) 0%, var(--blue-900) 100%);
  border-color: var(--blue-700);
}

[data-theme="dark"] .job-filename-mobile {
  color: var(--text-color);
}

[data-theme="dark"] .job-processing-indicator-mobile {
  color: var(--blue-400);
}

/* ============ US-406: Mobile Failed Job Styles ============ */

/* Failed job indicator icon in mobile cards (replaces spinner) */
.job-failed-indicator-mobile {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--red-500);
}

.job-failed-indicator-mobile .pi-exclamation-triangle {
  font-size: var(--text-lg);
}

/* Error text color utility class */
.text-error {
  color: var(--red-600);
}

/* Truncated error message in job card row 2 */
.job-error-message {
  font-size: var(--text-xs);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: help;
}

/* Dark mode adjustments for mobile failed jobs */
[data-theme="dark"] .job-failed-indicator-mobile {
  color: var(--red-400);
}

[data-theme="dark"] .text-error {
  color: var(--red-400);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .job-failed-indicator-mobile {
    color: var(--red-400);
  }

  :root:not([data-theme]) .text-error {
    color: var(--red-400);
  }
}

/* ============ US-018: Failed Job Row Styles ============ */

/* Failed job indicator in Actions column */
.job-failed-indicator {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  color: var(--red-500);
  cursor: help;
}

.job-failed-indicator .pi-exclamation-triangle {
  font-size: var(--text-base);
}

/* Dark mode adjustments for failed job indicator */
[data-theme="dark"] .job-failed-indicator {
  color: var(--red-400);
}

/* ============ US-036: Receipt Filename Display Styles ============ */

/**
 * Filename display for receipt rows in desktop table.
 * Similar to job-filename but for completed receipts.
 */
.receipt-filename {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-sm);
  color: var(--text-color);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.receipt-filename i {
  color: var(--text-color-secondary);
  flex-shrink: 0;
}

/**
 * Mobile card filename display - small, grey text below partner info.
 * Following the acceptance criteria: "font mic, gri sub partener"
 */
.filename-mobile {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font-size: var(--text-xs);
  color: var(--text-color-secondary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  margin-top: var(--space-xs);
}

.filename-mobile i {
  font-size: 0.65rem;
  flex-shrink: 0;
}

/* Dark mode adjustments for receipt filename */
[data-theme="dark"] .receipt-filename {
  color: var(--text-color);
}

[data-theme="dark"] .receipt-filename i {
  color: var(--text-color-secondary);
}

[data-theme="dark"] .filename-mobile {
  color: var(--text-color-secondary);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .receipt-filename {
    color: var(--text-color);
  }

  :root:not([data-theme]) .receipt-filename i {
    color: var(--text-color-secondary);
  }

  :root:not([data-theme]) .filename-mobile {
    color: var(--text-color-secondary);
  }
}

/* ============ US-035: Failed Receipt Mobile Card Styles ============ */

/**
 * Failed receipt card styling - subtle red highlight for easy identification.
 * Mirrors the row-failed styling from primevue-overrides.css for consistency.
 */
.receipt-card.card-failed {
  border-left: 3px solid var(--red-500);
  background: linear-gradient(135deg, var(--surface-card) 0%, var(--red-50) 100%);
  border-color: var(--red-200);
}

.receipt-card.card-failed:hover {
  background: linear-gradient(135deg, var(--surface-hover) 0%, var(--red-100) 100%);
}

/* Dark mode adjustments for failed receipt cards */
[data-theme="dark"] .receipt-card.card-failed {
  background: linear-gradient(135deg, var(--surface-card) 0%, var(--red-900) 100%);
  border-color: var(--red-700);
}

[data-theme="dark"] .receipt-card.card-failed:hover {
  background: linear-gradient(135deg, var(--surface-hover) 0%, var(--red-800) 100%);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .receipt-card.card-failed {
    background: linear-gradient(135deg, var(--surface-card) 0%, var(--red-900) 100%);
    border-color: var(--red-700);
  }

  :root:not([data-theme]) .receipt-card.card-failed:hover {
    background: linear-gradient(135deg, var(--surface-hover) 0%, var(--red-800) 100%);
  }
}

/* Edit button in mobile cards for failed receipts */
.edit-btn-mobile {
  flex-shrink: 0;
}

/* ============ US-019: Mobile Card Highlight Animations ============ */

/**
 * Keyframe animations for mobile card highlight on status change.
 * These mirror the DataTable row animations but for mobile cards.
 */

/* Success highlight animation for mobile cards */
@keyframes cardHighlightGreen {
  0% {
    background-color: var(--green-100);
    border-color: var(--green-300);
  }
  100% {
    background-color: var(--surface-card);
    border-color: var(--surface-border);
  }
}

/* Error highlight animation for mobile cards */
@keyframes cardHighlightRed {
  0% {
    background-color: var(--red-100);
    border-color: var(--red-300);
  }
  100% {
    background-color: var(--surface-card);
    border-color: var(--surface-border);
  }
}

/* Apply green highlight animation to completed cards */
.receipt-card.card-highlight-completed {
  animation: cardHighlightGreen 2s ease-out forwards;
}

/* Apply red highlight animation to failed cards */
.receipt-card.card-highlight-failed {
  animation: cardHighlightRed 2s ease-out forwards;
}

/* Dark mode adjustments for mobile card animations */
@keyframes cardHighlightGreenDark {
  0% {
    background-color: color-mix(in srgb, var(--green-500) 25%, var(--surface-card));
    border-color: var(--green-700);
  }
  100% {
    background-color: var(--surface-card);
    border-color: var(--surface-border);
  }
}

@keyframes cardHighlightRedDark {
  0% {
    background-color: color-mix(in srgb, var(--red-500) 25%, var(--surface-card));
    border-color: var(--red-700);
  }
  100% {
    background-color: var(--surface-card);
    border-color: var(--surface-border);
  }
}

[data-theme="dark"] .receipt-card.card-highlight-completed {
  animation-name: cardHighlightGreenDark;
}

[data-theme="dark"] .receipt-card.card-highlight-failed {
  animation-name: cardHighlightRedDark;
}

/* System preference dark mode for mobile card animations */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .receipt-card.card-highlight-completed {
    animation-name: cardHighlightGreenDark;
  }

  :root:not([data-theme]) .receipt-card.card-highlight-failed {
    animation-name: cardHighlightRedDark;
  }
}

/* US-019: Accessibility - Disable animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  .receipt-card.card-highlight-completed,
  .receipt-card.card-highlight-failed {
    animation: none;
  }
}

/* ============ US-020: Cancel Job Button & Animation Styles ============ */

/* Cancel button for pending/processing jobs */
.cancel-job-btn {
  /* Slightly smaller than other buttons to fit with spinner */
  width: 28px !important;
  height: 28px !important;
  padding: 0 !important;
}

.cancel-job-btn:hover {
  background: var(--red-50) !important;
}

[data-theme="dark"] .cancel-job-btn:hover {
  background: var(--red-900) !important;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .cancel-job-btn:hover {
    background: var(--red-900) !important;
  }
}

/* US-020: Fade-out animation CSS is in vendor/primevue-overrides.css */
/* Row animation applied via .row-cancelling class from getRowClass() */

/* ============ US-040: Mobile Android-Native Layout Styles ============ */

/**
 * Mobile Android-Native Layout
 *
 * Implements a Gmail/WhatsApp-like layout with:
 * - Fixed top bar (56px)
 * - Horizontal scrollable filter chips
 * - Fixed bottom navigation (56px)
 * - FAB (56x56px) with scroll hide/show behavior
 *
 * PRD tokens used:
 * - topBarHeight: 56px
 * - bottomNavHeight: 56px
 * - fabSize: 56px
 * - fabBottomOffset: 72px (56px bottom nav + 16px spacing)
 * - touchTargetMin: 48px
 */

/* Main container adjustments for mobile layout */
.receipts-list-view.mobile-android-layout {
  padding-top: calc(56px + var(--space-sm)); /* Top bar height + spacing */
  padding-bottom: calc(56px + var(--space-sm)); /* Bottom nav height + spacing */
  min-height: 100vh;
}

.receipts-list-view.mobile-android-layout .roa-card {
  border-radius: 0;
  box-shadow: none;
  margin: 0;
  padding: var(--space-sm);
}

/* US-103: Mobile Top Bar styles now in MobileTopBar.vue */

/* Mobile Filter Chips Container */
.mobile-filter-chips-container {
  position: fixed;
  top: 56px; /* Below top bar */
  left: 0;
  right: 0;
  background: var(--surface-card);
  border-bottom: 1px solid var(--surface-border);
  z-index: 999;
  padding: var(--space-sm) 0;
}

.mobile-filter-chips {
  display: flex;
  gap: var(--space-sm);
  padding: 0 var(--space-md);
  overflow-x: auto;
  scrollbar-width: none; /* Firefox */
  -webkit-overflow-scrolling: touch;
}

.mobile-filter-chips::-webkit-scrollbar {
  display: none; /* Chrome, Safari */
}

.mobile-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-full);
  font-size: var(--text-sm);
  font-weight: var(--font-medium);
  color: var(--text-color-secondary);
  background: var(--surface-100);
  border: 1px solid var(--surface-border);
  cursor: pointer;
  white-space: nowrap;
  transition: all var(--transition-fast);
  min-height: 32px;
}

.mobile-filter-chip:active {
  transform: scale(0.95);
}

.mobile-filter-chip.active {
  background: var(--color-primary);
  color: var(--color-text-inverse);
  border-color: var(--color-primary);
}

.mobile-filter-chip .chip-badge {
  margin-left: var(--space-xs);
}

/* Filter chip color variants when active */
.mobile-filter-chip.chip-draft.active {
  background: var(--blue-600);
  border-color: var(--blue-600);
}

.mobile-filter-chip.chip-pending.active {
  background: var(--yellow-600);
  border-color: var(--yellow-600);
}

.mobile-filter-chip.chip-approved.active {
  background: var(--green-600);
  border-color: var(--green-600);
}

.mobile-filter-chip.chip-rejected.active {
  background: var(--red-600);
  border-color: var(--red-600);
}

.mobile-filter-chip.chip-processing {
  background: var(--blue-100);
  color: var(--blue-700);
  border-color: var(--blue-200);
}

.mobile-filter-chip.chip-processing.active {
  background: var(--blue-600);
  color: white;
  border-color: var(--blue-600);
}

.mobile-filter-chip.chip-failed {
  background: var(--red-100);
  color: var(--red-700);
  border-color: var(--red-200);
}

.mobile-filter-chip.chip-failed.active {
  background: var(--red-600);
  color: white;
  border-color: var(--red-600);
}

.mobile-filter-chip i {
  font-size: var(--text-xs);
}

/* US-103: Mobile Bottom Navigation styles now in MobileBottomNav.vue */

/* US-608: Mobile SpeedDial - Floating Action Button with animated options */
.mobile-speed-dial {
  position: fixed !important;
  bottom: 72px; /* 56px bottom nav + 16px spacing */
  right: var(--space-md);
  z-index: 999;
}

/* SpeedDial main button styling */
.mobile-speed-dial .p-speeddial-button {
  width: 56px !important;
  height: 56px !important;
  border-radius: 50% !important;
  background: var(--color-primary) !important;
  border: none !important;
  box-shadow: var(--shadow-lg) !important;
  transition: all var(--transition-fast) !important;
}

.mobile-speed-dial .p-speeddial-button:hover,
.mobile-speed-dial .p-speeddial-button:focus {
  background: var(--color-primary-dark) !important;
  box-shadow: var(--shadow-xl) !important;
}

.mobile-speed-dial .p-speeddial-button:active {
  transform: scale(0.95) !important;
}

.mobile-speed-dial .p-speeddial-button .p-button-icon {
  font-size: var(--text-2xl) !important;
  color: var(--color-text-inverse) !important;
}

/* SpeedDial action buttons styling */
.mobile-speed-dial .p-speeddial-action {
  width: 48px !important;
  height: 48px !important;
  background: var(--surface-card) !important;
  border: 1px solid var(--surface-border) !important;
  box-shadow: var(--shadow-md) !important;
  transition: all var(--transition-fast) !important;
}

.mobile-speed-dial .p-speeddial-action:hover,
.mobile-speed-dial .p-speeddial-action:focus {
  background: var(--surface-hover) !important;
  transform: scale(1.1) !important;
}

.mobile-speed-dial .p-speeddial-action .p-speeddial-action-icon {
  color: var(--color-primary) !important;
  font-size: var(--text-lg) !important;
}

/* SpeedDial mask overlay (background when open) */
.mobile-speed-dial .p-speeddial-mask {
  background: rgba(0, 0, 0, 0.4) !important;
}

/* SpeedDial tooltip labels */
.mobile-speed-dial .p-speeddial-action::before {
  content: attr(aria-label);
  position: absolute;
  right: 60px;
  white-space: nowrap;
  background: var(--surface-card);
  color: var(--text-color);
  padding: var(--space-xs) var(--space-sm);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm);
  font-weight: var(--font-medium);
  box-shadow: var(--shadow-sm);
  border: 1px solid var(--surface-border);
  opacity: 0;
  transform: translateX(10px);
  transition: all var(--transition-fast);
  pointer-events: none;
}

.mobile-speed-dial.p-speeddial-opened .p-speeddial-action::before {
  opacity: 1;
  transform: translateX(0);
}

/* Mobile Sidebar (Hamburger Menu) */
.mobile-sidebar {
  width: 280px !important;
}

.sidebar-header {
  padding: var(--space-md);
  border-bottom: 1px solid var(--surface-border);
}

.sidebar-title {
  font-size: var(--text-xl);
  font-weight: var(--font-bold);
  color: var(--color-primary);
}

.sidebar-menu {
  display: flex;
  flex-direction: column;
  padding: var(--space-sm) 0;
}

.sidebar-item {
  display: flex;
  align-items: center;
  gap: var(--space-md);
  padding: var(--space-md) var(--space-lg);
  color: var(--text-color);
  text-decoration: none;
  font-size: var(--text-base);
  font-weight: var(--font-medium);
  transition: background var(--transition-fast);
}

.sidebar-item:hover,
.sidebar-item:active {
  background: var(--surface-hover);
}

.sidebar-item.active,
.sidebar-item.router-link-active {
  color: var(--color-primary);
  background: var(--blue-50);
}

.sidebar-item i {
  font-size: var(--text-xl);
  width: 24px;
  text-align: center;
}

/* Adjust content area when filter chips are visible */
.receipts-list-view.mobile-android-layout .mobile-filter-chips-container + .roa-card,
.receipts-list-view.mobile-android-layout:has(.mobile-filter-chips-container) .roa-card {
  margin-top: 48px; /* Height of filter chips container */
}

/* Hide old mobile toolbar on Android layout (replaced by top bar) */
.receipts-list-view.mobile-android-layout .mobile-toolbar-container {
  display: none;
}

/* Adjust receipt cards container for mobile layout */
.receipts-list-view.mobile-android-layout .receipt-cards {
  padding-bottom: 80px; /* Space for FAB and bottom nav */
}

/* US-103: Dark mode for MobileTopBar now in component */

[data-theme="dark"] .mobile-filter-chips-container {
  background: var(--surface-card);
  border-bottom-color: var(--surface-border);
}

[data-theme="dark"] .mobile-filter-chip {
  background: var(--surface-700);
  border-color: var(--surface-border);
  color: var(--text-color-secondary);
}

[data-theme="dark"] .mobile-filter-chip.active {
  background: var(--blue-600);
  border-color: var(--blue-600);
  color: white;
}

[data-theme="dark"] .mobile-filter-chip.chip-processing {
  background: var(--blue-900);
  color: var(--blue-300);
  border-color: var(--blue-700);
}

[data-theme="dark"] .mobile-filter-chip.chip-failed {
  background: var(--red-900);
  color: var(--red-300);
  border-color: var(--red-700);
}

/* US-103: Dark mode for MobileBottomNav now in component */

/* US-608: Dark mode for SpeedDial */
[data-theme="dark"] .mobile-speed-dial .p-speeddial-button {
  background: var(--blue-600) !important;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
}

[data-theme="dark"] .mobile-speed-dial .p-speeddial-button:hover,
[data-theme="dark"] .mobile-speed-dial .p-speeddial-button:focus {
  background: var(--blue-700) !important;
}

[data-theme="dark"] .mobile-speed-dial .p-speeddial-action {
  background: var(--surface-card) !important;
  border-color: var(--surface-border) !important;
}

[data-theme="dark"] .mobile-speed-dial .p-speeddial-action:hover,
[data-theme="dark"] .mobile-speed-dial .p-speeddial-action:focus {
  background: var(--surface-hover) !important;
}

[data-theme="dark"] .mobile-speed-dial .p-speeddial-action .p-speeddial-action-icon {
  color: var(--blue-400) !important;
}

[data-theme="dark"] .mobile-speed-dial .p-speeddial-action::before {
  background: var(--surface-card);
  color: var(--text-color);
  border-color: var(--surface-border);
}

[data-theme="dark"] .sidebar-item.active,
[data-theme="dark"] .sidebar-item.router-link-active {
  color: var(--blue-400);
  background: var(--blue-900);
}

/* System preference dark mode support */
/* US-103: Auto dark mode for MobileTopBar now in component */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .mobile-filter-chips-container {
    background: var(--surface-card);
    border-bottom-color: var(--surface-border);
  }

  :root:not([data-theme]) .mobile-filter-chip {
    background: var(--surface-700);
    border-color: var(--surface-border);
    color: var(--text-color-secondary);
  }

  :root:not([data-theme]) .mobile-filter-chip.active {
    background: var(--blue-600);
    border-color: var(--blue-600);
    color: white;
  }

  :root:not([data-theme]) .mobile-filter-chip.chip-processing {
    background: var(--blue-900);
    color: var(--blue-300);
    border-color: var(--blue-700);
  }

  :root:not([data-theme]) .mobile-filter-chip.chip-failed {
    background: var(--red-900);
    color: var(--red-300);
    border-color: var(--red-700);
  }

  /* US-103: Auto dark mode for MobileBottomNav now in component */

  /* US-608: Auto dark mode for SpeedDial */
  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-button {
    background: var(--blue-600) !important;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important;
  }

  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-button:hover,
  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-button:focus {
    background: var(--blue-700) !important;
  }

  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-action {
    background: var(--surface-card) !important;
    border-color: var(--surface-border) !important;
  }

  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-action:hover,
  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-action:focus {
    background: var(--surface-hover) !important;
  }

  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-action .p-speeddial-action-icon {
    color: var(--blue-400) !important;
  }

  :root:not([data-theme]) .mobile-speed-dial .p-speeddial-action::before {
    background: var(--surface-card);
    color: var(--text-color);
    border-color: var(--surface-border);
  }

  :root:not([data-theme]) .sidebar-item.active,
  :root:not([data-theme]) .sidebar-item.router-link-active {
    color: var(--blue-400);
    background: var(--blue-900);
  }
}

/* Adjust layout when filter chips are visible - accounting for their height */
.receipts-list-view.mobile-android-layout:has(.mobile-filter-chips-container) {
  padding-top: calc(56px + 48px + var(--space-sm)); /* Top bar + filter chips + spacing */
}

/* ================================================
   US-503: BottomSheet Filters Styles
   Pattern from InvoicesView.vue
   ================================================ */

.bottom-sheet-title {
  font-size: var(--text-lg);
  font-weight: var(--font-semibold);
  color: var(--text-color);
  margin: 0 0 var(--space-md) 0;
}

.bottom-sheet-filters {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.bottom-sheet-actions {
  display: flex;
  gap: var(--space-sm);
  justify-content: flex-end;
  margin-top: var(--space-md);
  padding-top: var(--space-md);
  border-top: 1px solid var(--surface-border);
}
</style>

<!-- US-608: Non-scoped styles for SpeedDial mask (appended to body) -->
<style>
/* US-608: SpeedDial mask overlay - appears when SpeedDial is open */
.p-speeddial-mask.mobile-speed-dial-mask {
  position: fixed !important;
  top: 0 !important;
  left: 0 !important;
  width: 100% !important;
  height: 100% !important;
  background: rgba(0, 0, 0, 0.5) !important;
  z-index: 998 !important;
  animation: fadeIn 200ms ease-out !important;
}

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Dark mode mask */
[data-theme="dark"] .p-speeddial-mask.mobile-speed-dial-mask {
  background: rgba(0, 0, 0, 0.7) !important;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .p-speeddial-mask.mobile-speed-dial-mask {
    background: rgba(0, 0, 0, 0.7) !important;
  }
}
</style>

<!-- US-516: Non-scoped styles for Receipt Action Menu (teleported to body) -->
<style>
/* ============================================================================
 * US-516: Receipt Action Menu - Material Design 3 Styling
 * Non-scoped because PrimeVue Menu is teleported to document body
 * ============================================================================ */

/* Base menu container styling */
.receipt-action-menu.p-menu.p-menu-overlay {
  /* MD3 Surface: Use tonal surface for elevation */
  background: var(--md-sys-color-surface-container-high, var(--surface-card)) !important;
  border: 1px solid var(--md-sys-color-outline-variant, var(--surface-border)) !important;
  border-radius: var(--radius-lg, 12px) !important;
  min-width: 200px !important;
  max-width: 280px !important;

  /* MD3 Elevation: Softer shadow for modern look */
  box-shadow:
    0 2px 4px -1px rgba(0, 0, 0, 0.1),
    0 4px 6px -1px rgba(0, 0, 0, 0.06),
    0 8px 16px -2px rgba(0, 0, 0, 0.1) !important;

  /* Smooth entry animation */
  transform-origin: top center !important;
  animation: receiptMenuSlideIn 200ms ease-out !important;
  overflow: hidden !important;
}

/* MD3 Menu Animation */
@keyframes receiptMenuSlideIn {
  from {
    opacity: 0;
    transform: scale(0.92) translateY(-4px);
  }
  to {
    opacity: 1;
    transform: scale(1) translateY(0);
  }
}

/* Menu container padding for visual breathing room */
.receipt-action-menu.p-menu .p-menu-list {
  padding: var(--space-sm, 8px) 0 !important;
}

/* Menu item container */
.receipt-action-menu.p-menu .p-menuitem {
  padding: 0 !important;
  margin: 0 var(--space-xs, 4px) !important;
}

/* PrimeVue 4 uses .p-menuitem-content for the clickable area */
.receipt-action-menu.p-menu .p-menuitem-content {
  display: flex !important;
  align-items: center !important;
  gap: var(--space-md, 16px) !important;
  padding: var(--space-sm, 8px) var(--space-md, 16px) !important;
  min-height: 48px !important; /* MD3: 48px touch target for accessibility */
  font-size: var(--text-base, 1rem) !important;
  color: var(--md-sys-color-on-surface, var(--text-color)) !important;
  background: transparent !important;
  border-radius: var(--radius-md, 8px) !important;
  text-decoration: none !important;
  cursor: pointer !important;
  transition: background-color var(--transition-fast, 150ms) ease,
              transform var(--transition-fast, 150ms) ease !important;
  position: relative !important;
  overflow: hidden !important;
}

/* MD3 State Layer: Hover effect */
.receipt-action-menu.p-menu .p-menuitem-content:hover {
  background: var(--md-sys-color-surface-variant, var(--surface-hover)) !important;
}

/* MD3 State Layer: Active/Press effect */
.receipt-action-menu.p-menu .p-menuitem-content:active {
  background: var(--md-sys-color-primary-container, var(--primary-100)) !important;
  transform: scale(0.98) !important;
}

/* MD3 Focus visible state for keyboard navigation */
.receipt-action-menu.p-menu .p-menuitem:focus-visible .p-menuitem-content {
  outline: 2px solid var(--md-sys-color-primary, var(--color-primary)) !important;
  outline-offset: -2px !important;
}

/* Icon styling - MD3 leading icon */
.receipt-action-menu.p-menu .p-menuitem-icon {
  display: flex !important;
  align-items: center !important;
  justify-content: center !important;
  width: 24px !important;
  height: 24px !important;
  font-size: 1.25rem !important;
  color: var(--md-sys-color-on-surface-variant, var(--color-text-secondary)) !important;
  flex-shrink: 0 !important;
  transition: color var(--transition-fast, 150ms) ease !important;
}

/* Icon color on hover */
.receipt-action-menu.p-menu .p-menuitem-content:hover .p-menuitem-icon {
  color: var(--md-sys-color-primary, var(--color-primary)) !important;
}

/* Text styling - MD3 label */
.receipt-action-menu.p-menu .p-menuitem-text {
  font-weight: var(--font-medium, 500) !important;
  font-size: var(--text-base, 1rem) !important;
  line-height: 1.5 !important;
  color: var(--md-sys-color-on-surface, var(--text-color)) !important;
}

/* Spacing between menu items */
.receipt-action-menu.p-menu .p-menuitem + .p-menuitem {
  margin-top: 2px !important;
}

/* ============================================================================
 * US-516: Menu Separator (Divider before destructive actions)
 * ============================================================================ */
.receipt-action-menu.p-menu .p-menu-separator {
  margin: var(--space-sm, 8px) var(--space-md, 16px) !important;
  border: none !important;
  border-top: 1px solid var(--md-sys-color-outline-variant, var(--surface-border)) !important;
  height: 0 !important;
}

/* ============================================================================
 * US-516: Semantic Color Classes for Menu Items
 * ============================================================================ */

/* Danger/Destructive actions (Delete, Reject) */
.receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-content {
  color: var(--md-sys-color-error, var(--color-error)) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-icon {
  color: var(--md-sys-color-error, var(--color-error)) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-text {
  color: var(--md-sys-color-error, var(--color-error)) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-content:hover {
  background: var(--md-sys-color-error-container, var(--red-100)) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-content:active {
  background: var(--md-sys-color-error-container, var(--red-200)) !important;
}

/* Success actions (Approve, Validate) */
.receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-content {
  color: var(--color-success, #059669) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-icon {
  color: var(--color-success, #059669) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-text {
  color: var(--color-success, #059669) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-content:hover {
  background: var(--green-100, rgba(5, 150, 105, 0.1)) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-content:active {
  background: var(--green-200, rgba(5, 150, 105, 0.2)) !important;
}

/* Warning actions (Unapprove) */
.receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-content {
  color: var(--color-warning, #d97706) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-icon {
  color: var(--color-warning, #d97706) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-text {
  color: var(--color-warning, #d97706) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-content:hover {
  background: var(--yellow-100, rgba(217, 119, 6, 0.1)) !important;
}

.receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-content:active {
  background: var(--yellow-200, rgba(217, 119, 6, 0.2)) !important;
}

/* ============================================================================
 * US-516: Dark Mode Support
 * ============================================================================ */
[data-theme="dark"] .receipt-action-menu.p-menu.p-menu-overlay {
  background: var(--surface-800, #1e293b) !important;
  border-color: var(--surface-600, #475569) !important;
  box-shadow:
    0 4px 8px -2px rgba(0, 0, 0, 0.3),
    0 8px 16px -4px rgba(0, 0, 0, 0.25) !important;
}

[data-theme="dark"] .receipt-action-menu.p-menu .p-menuitem-content:hover {
  background: var(--surface-700, #334155) !important;
}

[data-theme="dark"] .receipt-action-menu.p-menu .p-menuitem-content:active {
  background: var(--surface-600, #475569) !important;
}

[data-theme="dark"] .receipt-action-menu.p-menu .p-menu-separator {
  border-top-color: var(--surface-600, #475569) !important;
}

/* Dark mode semantic colors */
[data-theme="dark"] .receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-content:hover {
  background: var(--red-900, rgba(220, 38, 38, 0.2)) !important;
}

[data-theme="dark"] .receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-content:hover {
  background: var(--green-900, rgba(5, 150, 105, 0.2)) !important;
}

[data-theme="dark"] .receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-content:hover {
  background: var(--yellow-900, rgba(217, 119, 6, 0.2)) !important;
}

/* Auto dark mode (system preference) */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme]) .receipt-action-menu.p-menu.p-menu-overlay {
    background: var(--surface-800, #1e293b) !important;
    border-color: var(--surface-600, #475569) !important;
    box-shadow:
      0 4px 8px -2px rgba(0, 0, 0, 0.3),
      0 8px 16px -4px rgba(0, 0, 0, 0.25) !important;
  }

  :root:not([data-theme]) .receipt-action-menu.p-menu .p-menuitem-content:hover {
    background: var(--surface-700, #334155) !important;
  }

  :root:not([data-theme]) .receipt-action-menu.p-menu .p-menuitem-content:active {
    background: var(--surface-600, #475569) !important;
  }

  :root:not([data-theme]) .receipt-action-menu.p-menu .p-menu-separator {
    border-top-color: var(--surface-600, #475569) !important;
  }

  :root:not([data-theme]) .receipt-action-menu.p-menu .p-menuitem.menu-item-danger .p-menuitem-content:hover {
    background: var(--red-900, rgba(220, 38, 38, 0.2)) !important;
  }

  :root:not([data-theme]) .receipt-action-menu.p-menu .p-menuitem.menu-item-success .p-menuitem-content:hover {
    background: var(--green-900, rgba(5, 150, 105, 0.2)) !important;
  }

  :root:not([data-theme]) .receipt-action-menu.p-menu .p-menuitem.menu-item-warning .p-menuitem-content:hover {
    background: var(--yellow-900, rgba(217, 119, 6, 0.2)) !important;
  }
}
</style>
