<template>
  <q-page padding>
    <div v-if="loading" class="flex flex-center" style="min-height:300px">
      <q-spinner size="40px" color="indigo-6" />
    </div>

    <template v-else-if="customer">
      <div class="row q-col-gutter-md">
        <div class="col-12 col-lg-8">

      <CustomerHeader :customer="customer">
        <template #contact><ContactCard :customer="customer" /></template>
        <template #info><CustomerInfoCard :customer="customer" /></template>
      </CustomerHeader>

      <q-expansion-item v-model="sectionsOpen.locations" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%">
            <q-icon name="location_on" size="20px" class="q-mr-xs" />
            Lieux de service ({{ locations.length }})
          </div>
        </template>

        <div v-if="!locations.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucun lieu de service</div>

      <div v-for="loc in sortedLocations" :key="loc.name" class="ops-card q-mb-md loc-card"
        :class="{ 'loc-inactive': !locHasSubs(loc.name) }">
        <div class="loc-header" :style="!locHasSubs(loc.name) ? 'cursor:pointer' : ''"
          @click="!locHasSubs(loc.name) && toggleLocCollapse(loc.name)">
          <div class="row items-center">
            <div v-if="!locHasSubs(loc.name)" class="col-auto q-mr-xs">
              <q-icon :name="locCollapsed[loc.name] === false ? 'expand_more' : 'chevron_right'" size="18px" color="grey-5" />
            </div>
            <div class="col">
              <div class="text-subtitle1 text-weight-bold" :class="{ 'text-grey-6': !locHasSubs(loc.name) }">
                <InlineField :value="loc.address_line" field="address_line" doctype="Service Location" :docname="loc.name"
                  placeholder="Adresse" @saved="v => loc.address_line = v.value" />
              </div>
              <div class="text-caption text-grey-6">
                <template v-for="f in locInlineFields" :key="f.field">
                  <template v-if="f.prefix">{{ f.prefix }}</template>
                  <InlineField :value="loc[f.field]" :field="f.field" doctype="Service Location" :docname="loc.name"
                    :placeholder="f.placeholder" @saved="v => loc[f.field] = v.value" />
                </template>
                <template v-if="!locHasSubs(loc.name)"> &middot; <em>Aucun abonnement</em></template>
              </div>
            </div>
            <div class="col-auto q-gutter-x-sm">
              <span v-if="locSubsMonthlyTotal(loc.name)" class="text-weight-bold" style="color:var(--ops-accent)">
                {{ formatMoney(locSubsMonthlyTotal(loc.name)) }}/m
              </span>
              <span class="ops-badge" :class="locHasSubs(loc.name) ? locStatusClass(loc.status) : 'inactive'">
                {{ locHasSubs(loc.name) ? loc.status : 'Inactif' }}
              </span>
              <q-btn v-if="!locHasSubs(loc.name) && can('delete_records')" flat round dense
                icon="delete_outline" size="sm" color="red-4" class="q-ml-xs"
                @click.stop="confirmDeleteLocation(loc)" :loading="deletingLoc === loc.name">
                <q-tooltip>Supprimer cette adresse</q-tooltip>
              </q-btn>
            </div>
          </div>
        </div>

        <div v-show="locHasSubs(loc.name) || locCollapsed[loc.name] === false">

        <div v-if="locEquip(loc.name).length || loc.connection_type" class="row items-center q-mt-sm q-mb-xs q-gutter-x-md">
          <div class="text-caption text-grey-6">
            <q-icon name="cable" size="14px" class="q-mr-xs" />
            <InlineField :value="loc.connection_type" field="connection_type" doctype="Service Location" :docname="loc.name"
              type="select" :options="['Fibre', 'Coax', 'DSL', 'Wireless', 'LTE']"
              placeholder="Type" @saved="v => loc.connection_type = v.value" />
            &middot; OLT: <InlineField :value="loc.olt_port" field="olt_port" doctype="Service Location" :docname="loc.name"
              placeholder="Port OLT" @saved="v => loc.olt_port = v.value" />
            &middot; <InlineField :value="loc.network_id" field="network_id" doctype="Service Location" :docname="loc.name"
              placeholder="Network ID" @saved="v => loc.network_id = v.value" />
          </div>
          <div class="device-strip">
            <div v-for="eq in locEquip(loc.name)" :key="eq.name" class="device-icon-chip" :class="deviceColorClass(eq.status)"
              @click="openModal('Service Equipment', eq.name, eq.equipment_type + (eq.brand ? ' — ' + eq.brand : ''))">
              <q-icon :name="deviceLucideIcon(eq.equipment_type)" size="20px" />
              <span v-if="isOnline(eq.serial_number) === true" class="acs-dot acs-online" />
              <span v-else-if="isOnline(eq.serial_number) === false" class="acs-dot acs-offline" />
              <span v-else-if="eq.serial_number && !getDevice(eq.serial_number)" class="acs-dot acs-probing" />
              <q-tooltip class="bg-grey-9 text-caption" :offset="[0, 6]" style="max-width:320px">
                <div><strong>{{ eq.equipment_type }}{{ eq.brand ? ' — ' + eq.brand : '' }}{{ eq.model ? ' ' + eq.model : '' }}</strong></div>
                <div>SN: {{ eq.serial_number }}</div>
                <template v-if="eq.mac_address"><div>MAC: {{ eq.mac_address }}</div></template>
                <template v-if="eq.olt_name"><div>OLT: {{ eq.olt_name }} — Slot {{ eq.olt_slot }}/Port {{ eq.olt_port }}/ONT {{ eq.olt_ontid }}</div></template>
                <template v-else-if="eq.ip_address"><div>IP: {{ eq.ip_address }}</div></template>
                <div>{{ eq.status }}</div>
                <template v-if="getDevice(eq.serial_number) || combinedStatus(eq.serial_number).source !== 'unknown'">
                  <q-separator dark class="q-my-xs" />
                  <div :style="{ color: combinedStatus(eq.serial_number).online ? '#4ade80' : '#f87171' }">
                    {{ combinedStatus(eq.serial_number).online ? '● En ligne' : '● Hors ligne' }}
                    <template v-if="getDevice(eq.serial_number)?.lastInform"> — {{ formatTimeAgo(getDevice(eq.serial_number).lastInform) }}</template>
                  </div>
                  <div class="text-grey-4" style="font-size:0.7rem">{{ combinedStatus(eq.serial_number).detail }}</div>
                  <template v-if="getDevice(eq.serial_number)?.opticalStatus">
                    <div>Fibre: <span :style="{ color: getDevice(eq.serial_number).opticalStatus === 'Up' ? '#4ade80' : '#f87171' }">{{ getDevice(eq.serial_number).opticalStatus }}</span></div>
                  </template>
                  <template v-if="getDevice(eq.serial_number).rxPower != null && getDevice(eq.serial_number).rxPower !== 0">
                    <div :style="{ color: signalColor(eq.serial_number) }">
                      Rx: {{ getDevice(eq.serial_number).rxPower }} dBm
                      <template v-if="getDevice(eq.serial_number).txPower != null && getDevice(eq.serial_number).txPower !== 0"> / Tx: {{ getDevice(eq.serial_number).txPower }} dBm</template>
                    </div>
                  </template>
                  <template v-if="getDevice(eq.serial_number).wifi">
                    <div v-if="getDevice(eq.serial_number).wifi.totalClients != null || getDevice(eq.serial_number).wifi.radio1?.clients != null">
                      WiFi: {{ getDevice(eq.serial_number).wifi.totalClients ?? ((getDevice(eq.serial_number).wifi.radio1?.clients || 0) + (getDevice(eq.serial_number).wifi.radio2?.clients || 0) + (getDevice(eq.serial_number).wifi.radio3?.clients || 0)) }} clients
                      <span v-if="getDevice(eq.serial_number).wifi.meshClients > 0">({{ getDevice(eq.serial_number).wifi.meshClients }} via mesh)</span>
                    </div>
                  </template>
                  <template v-if="getDevice(eq.serial_number).firmware"><div>FW: {{ getDevice(eq.serial_number).firmware }}</div></template>
                  <template v-if="getDevice(eq.serial_number).ip"><div>WAN IP: {{ getDevice(eq.serial_number).ip }}</div></template>
                  <template v-if="getDevice(eq.serial_number).ssid"><div>SSID: {{ getDevice(eq.serial_number).ssid }}</div></template>
                </template>
              </q-tooltip>
              <q-menu context-menu v-if="getDevice(eq.serial_number)">
                <q-list dense style="min-width:160px">
                  <q-item clickable v-close-popup @click="doReboot(eq)">
                    <q-item-section avatar><q-icon name="restart_alt" size="18px" /></q-item-section>
                    <q-item-section>Redémarrer</q-item-section>
                  </q-item>
                  <q-item clickable v-close-popup @click="doRefreshParams(eq)">
                    <q-item-section avatar><q-icon name="sync" size="18px" /></q-item-section>
                    <q-item-section>Rafraîchir params</q-item-section>
                  </q-item>
                </q-list>
              </q-menu>
            </div>
            <div class="device-icon-chip device-add-chip">
              <q-icon name="add" size="20px" />
              <q-tooltip>Ajouter</q-tooltip>
              <q-menu anchor="bottom right" self="top right" :offset="[0, 4]">
                <q-list dense style="min-width:240px">
                  <q-item clickable v-close-popup @click="openWizardForAddress(loc)">
                    <q-item-section avatar><q-icon name="description" size="18px" color="indigo-6" /></q-item-section>
                    <q-item-section>
                      <q-item-label>Nouvelle soumission</q-item-label>
                      <q-item-label caption>Wizard avec étapes dispatchables</q-item-label>
                    </q-item-section>
                  </q-item>
                  <q-separator />
                  <q-item clickable v-close-popup @click="openAddService(loc)">
                    <q-item-section avatar><q-icon name="wifi" size="18px" color="blue-6" /></q-item-section>
                    <q-item-section>Forfait / Service (direct)</q-item-section>
                  </q-item>
                  <q-item clickable v-close-popup @click="openAddService(loc, 'rabais')">
                    <q-item-section avatar><q-icon name="sell" size="18px" color="red-5" /></q-item-section>
                    <q-item-section>Rabais / Crédit</q-item-section>
                  </q-item>
                  <q-separator />
                  <q-item clickable v-close-popup @click="openAddEquipment(loc)">
                    <q-item-section avatar><q-icon name="router" size="18px" color="teal-6" /></q-item-section>
                    <q-item-section>Équipement</q-item-section>
                  </q-item>
                </q-list>
              </q-menu>
            </div>
          </div>
        </div>

            <div class="info-block subs-block">
              <div class="info-block-title" style="display:flex;align-items:center">
                Abonnements ({{ locSubs(loc.name).length }})
                <q-btn flat dense round size="xs" icon="add" color="indigo-6" class="q-ml-xs"
                  style="margin-top:-2px">
                  <q-tooltip>Ajouter un service</q-tooltip>
                  <q-menu anchor="bottom left" self="top left" :offset="[0, 4]">
                    <q-list dense style="min-width:220px">
                      <q-item clickable v-close-popup @click="openWizardForAddress(loc)">
                        <q-item-section avatar><q-icon name="description" size="18px" color="indigo-6" /></q-item-section>
                        <q-item-section>
                          <q-item-label>Nouvelle soumission</q-item-label>
                          <q-item-label caption>Wizard avec étapes</q-item-label>
                        </q-item-section>
                      </q-item>
                      <q-separator />
                      <q-item clickable v-close-popup @click="openAddService(loc)">
                        <q-item-section avatar><q-icon name="wifi" size="18px" color="blue-6" /></q-item-section>
                        <q-item-section>Forfait / Service (direct)</q-item-section>
                      </q-item>
                      <q-item clickable v-close-popup @click="openAddService(loc, 'rabais')">
                        <q-item-section avatar><q-icon name="sell" size="18px" color="red-5" /></q-item-section>
                        <q-item-section>Rabais / Crédit</q-item-section>
                      </q-item>
                    </q-list>
                  </q-menu>
                </q-btn>
              </div>
              <div v-if="!locSubs(loc.name).length" class="text-caption text-grey-5">Aucun</div>

              <template v-if="locSubsMonthly(loc.name).length">
                <template v-for="section in locSubsSections(loc.name, 'M')" :key="'m-'+section.key">
                  <div class="sub-section-header" @click="toggleSection(loc.name, section.key)">
                    <q-icon :name="sectionOpen(loc.name, section.key) ? 'expand_more' : 'chevron_right'" size="18px" />
                    <q-icon :name="section.icon" size="16px" class="text-grey-6 q-mr-xs" />
                    <span class="sub-section-title">{{ section.label }}</span>
                    <span class="sub-section-count">({{ section.items.length }})</span>
                    <span class="sub-section-subtotal">{{ formatMoney(sectionTotal(section.items)) }}</span>
                  </div>
                  <div v-show="sectionOpen(loc.name, section.key)">
                    <draggable :list="section.items" :group="{ name: 'loc-subs-' + loc.name, pull: true, put: true }" item-key="name"
                      handle=".drag-handle" ghost-class="sub-ghost" :animation="150"
                      @change="onSubDragChange($event, loc.name, section.items)">
                      <template #item="{ element: sub }">
                        <div class="sub-row clickable-row" :class="{ 'sub-rebate': isRebate(sub), 'sub-cancelled': sub.status === 'Cancelled' }">
                          <div class="row items-center no-wrap">
                            <q-icon name="drag_indicator" class="drag-handle text-grey-4 q-mr-xs" size="16px" style="cursor:grab" />
                            <div class="col" style="min-width:0" @click="openModal('Service Subscription', sub.name)">
                              <div class="row items-center no-wrap q-gutter-x-sm">
                                <code class="sub-sku">{{ sub.name }}</code>
                                <span class="text-weight-medium ellipsis-text" style="flex:1;min-width:0">{{ subMainLabel(sub) }}</span>
                                <!-- Inline price edit: double-click to edit. Negative values kept as-is for rebates. -->
                                <span class="sub-price-wrap" @click.stop @dblclick.stop
                                  :title="can('edit_records') ? 'Double-clic pour modifier le prix' : ''">
                                  <InlineField :value="sub.actual_price || 0"
                                    field="monthly_price" doctype="Service Subscription" :docname="sub.name"
                                    type="number" :formatter="formatMoney"
                                    :readonly="!can('edit_records')"
                                    @saved="onSubPriceSaved(sub, $event)">
                                    <template #display="{ displayValue }">
                                      <span class="sub-price" :class="{ 'text-red': isRebate(sub), 'text-weight-bold': !isRebate(sub) }">
                                        {{ displayValue }}
                                      </span>
                                    </template>
                                  </InlineField>
                                </span>
                              </div>
                            </div>
                            <div class="col-auto sub-actions">
                              <q-btn flat dense round size="xs" icon="confirmation_number" color="indigo-4"
                                @click.stop="openNewTicket(locations.find(l => l.name === loc.name), sub)">
                                <q-tooltip>Créer un ticket pour ce service</q-tooltip>
                              </q-btn>
                              <q-btn flat dense round size="xs" :icon="sub.billing_frequency === 'A' ? 'event' : 'event_repeat'"
                                @click.stop="toggleFrequency(sub)" :loading="subSaving === sub.name + ':freq'">
                                <q-tooltip>{{ sub.billing_frequency === 'A' ? 'Passer mensuel' : 'Passer annuel' }}</q-tooltip>
                              </q-btn>
                              <q-btn flat dense round size="xs"
                                :icon="sub.status === 'Cancelled' ? 'play_circle' : 'cancel'"
                                :color="sub.status === 'Cancelled' ? 'green' : 'red'"
                                @click.stop="toggleSubStatus(sub)" :loading="subSaving === sub.name + ':status'">
                                <q-tooltip>{{ sub.status === 'Cancelled' ? 'Réactiver' : 'Désactiver' }}</q-tooltip>
                              </q-btn>
                              <q-btn v-if="can('delete_records')" flat dense round size="xs" icon="delete_outline" color="red-4"
                                @click.stop="confirmDeleteSub(sub)">
                                <q-tooltip>Supprimer définitivement</q-tooltip>
                              </q-btn>
                            </div>
                          </div>
                        </div>
                      </template>
                    </draggable>
                  </div>
                </template>

                <div class="sub-total-line">
                  <span class="sub-total-label">Total mensuel</span>
                  <span class="sub-total-amount">{{ formatMoney(locSubsMonthlyTotal(loc.name)) }} /mois</span>
                </div>
              </template>

              <template v-if="locSubsAnnual(loc.name).length">
                <div class="sub-annual-divider">
                  <q-icon name="event" size="16px" /> Abonnements annuels ({{ locSubsAnnual(loc.name).length }})
                </div>
                <div v-for="sub in locSubsAnnual(loc.name)" :key="sub.name"
                  class="sub-row clickable-row" :class="{ 'sub-rebate': isRebate(sub), 'sub-cancelled': sub.status === 'Cancelled' }">
                  <div class="row items-center no-wrap">
                    <div class="col" style="min-width:0">
                      <div class="row items-center no-wrap q-gutter-x-sm" @click="openModal('Service Subscription', sub.name)">
                        <code class="sub-sku">{{ sub.name }}</code>
                        <span class="sub-freq annual">A</span>
                        <span class="text-weight-medium ellipsis-text" style="flex:1;min-width:0">{{ subMainLabel(sub) }}</span>
                        <span class="sub-price" :class="{ 'text-red': isRebate(sub), 'text-weight-bold': !isRebate(sub) }">{{ formatMoney(annualPrice(sub)) }}</span>
                        <!-- Inline edit on the per-month base price — annual total recomputes from it. -->
                        <span class="sub-price-wrap text-caption text-grey-5" style="white-space:nowrap" @click.stop @dblclick.stop
                          :title="can('edit_records') ? 'Double-clic pour modifier le prix mensuel' : ''">
                          (<InlineField :value="sub.actual_price || 0"
                            field="monthly_price" doctype="Service Subscription" :docname="sub.name"
                            type="number" :formatter="formatMoney"
                            :readonly="!can('edit_records')"
                            @saved="onSubPriceSaved(sub, $event)" />/m&times;12)
                        </span>
                      </div>
                      <div class="row items-center q-pl-lg q-mt-xs q-gutter-x-md">
                        <div class="sub-meta">
                          <q-toggle size="xs" dense
                            :model-value="!Number(sub.cancel_at_period_end)"
                            @update:model-value="toggleRecurring(sub)"
                            :color="Number(sub.cancel_at_period_end) ? 'grey' : 'green'"
                            :disable="togglingRecurring === sub.name" />
                          <span class="text-caption" :class="Number(sub.cancel_at_period_end) ? 'text-grey-5' : 'text-green-7'">
                            {{ Number(sub.cancel_at_period_end) ? 'Non récurrent' : 'Récurrent' }}
                          </span>
                          <q-spinner v-if="togglingRecurring === sub.name" size="12px" class="q-ml-xs" />
                        </div>
                        <div v-if="sub.current_invoice_start" class="text-caption text-grey-6">
                          <q-icon name="event" size="12px" class="q-mr-xs" />
                          Prochaine: {{ formatDate(sub.current_invoice_start) }}
                        </div>
                      </div>
                    </div>
                    <div class="col-auto sub-actions">
                      <q-btn flat dense round size="xs" icon="event_repeat"
                        @click.stop="toggleFrequency(sub)" :loading="subSaving === sub.name + ':freq'">
                        <q-tooltip>Passer mensuel</q-tooltip>
                      </q-btn>
                      <q-btn flat dense round size="xs"
                        :icon="sub.status === 'Cancelled' ? 'play_circle' : 'cancel'"
                        :color="sub.status === 'Cancelled' ? 'green' : 'red'"
                        @click.stop="toggleSubStatus(sub)" :loading="subSaving === sub.name + ':status'">
                        <q-tooltip>{{ sub.status === 'Cancelled' ? 'Réactiver' : 'Désactiver' }}</q-tooltip>
                      </q-btn>
                      <q-btn v-if="can('delete_records')" flat dense round size="xs" icon="delete_outline" color="red-4"
                        @click.stop="confirmDeleteSub(sub)">
                        <q-tooltip>Supprimer définitivement</q-tooltip>
                      </q-btn>
                    </div>
                  </div>
                </div>

                <div class="sub-total-line annual">
                  <span class="sub-total-label">Total annuel</span>
                  <span class="sub-total-amount">{{ formatMoney(locSubsAnnualTotal(loc.name)) }} /an</span>
                </div>
              </template>
            </div>

        <div class="q-mt-sm">
          <div class="info-block-title" style="display:flex;align-items:center">
            Tickets ({{ locTickets(loc.name).length }})
            <q-btn flat dense round size="xs" icon="add" color="indigo-6" class="q-ml-xs"
              @click="openNewTicket(loc)" style="margin-top:-2px">
              <q-tooltip>Créer un ticket pour cette adresse</q-tooltip>
            </q-btn>
          </div>
          <div v-for="t in locTickets(loc.name)" :key="t.name" class="ticket-row clickable-row" @click="openModal('Issue', t.name, t.subject)">
            <div class="row items-center no-wrap">
              <q-icon v-if="t.is_important" name="star" color="amber-8" size="14px" class="q-mr-xs" style="flex-shrink:0" />
              <div class="col" style="min-width:0">
                <span style="font-size:11px;color:#9e9e9e" class="q-mr-xs">{{ t.legacy_ticket_id || t.name }}</span>
                <span style="font-size:12.5px">{{ t.subject }}</span>
              </div>
              <div v-if="t.assigned_staff || t.opened_by_staff" class="avatar-stack q-mx-xs" style="flex-shrink:0">
                <q-avatar v-if="t.opened_by_staff && t.opened_by_staff !== t.assigned_staff"
                  size="20px" class="avatar-chip" :style="{ background: staffColor(t.opened_by_staff), zIndex: 1 }">
                  {{ staffInitials(t.opened_by_staff) }}
                  <q-tooltip>{{ t.opened_by_staff }}</q-tooltip>
                </q-avatar>
                <q-avatar v-if="t.assigned_staff"
                  size="20px" class="avatar-chip" :style="{ background: staffColor(t.assigned_staff), zIndex: 2 }">
                  {{ staffInitials(t.assigned_staff) }}
                  <q-tooltip>{{ t.assigned_staff }}</q-tooltip>
                </q-avatar>
              </div>
              <span style="font-size:11px;color:#9e9e9e;white-space:nowrap;flex-shrink:0" class="q-mx-xs">{{ t.opening_date }}</span>
              <span class="ops-badge" style="font-size:10px;padding:1px 5px;flex-shrink:0" :class="ticketStatusClass(t.status)">{{ t.status }}</span>
            </div>
          </div>
        </div>

        </div>
      </div>
      </q-expansion-item>

      <q-expansion-item v-model="sectionsOpen.tickets" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%;display:flex;align-items:center">
            <q-icon name="confirmation_number" size="20px" class="q-mr-xs" />
            Tickets ({{ tickets.length }}{{ !ticketsExpanded ? '+' : '' }})
            <span v-if="openTicketCount" class="ops-badge open q-ml-sm">{{ openTicketCount }} ouverts</span>
            <q-space />
            <q-btn flat dense size="sm" icon="add" label="Nouveau" color="indigo-6" no-caps
              @click.stop="openNewTicket()" class="q-mr-sm" />
          </div>
        </template>
        <div v-if="!tickets.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucun ticket</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="tickets" :columns="ticketCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10, sortBy: null }"
            @row-click="(_, row) => openModal('Issue', row.name, row.subject)">
            <template #body-cell-important="props">
              <q-td :props="props" style="padding:0 2px">
                <q-icon v-if="props.row.is_important" name="star" color="amber-8" size="14px" />
              </q-td>
            </template>
            <template #body-cell-legacy_id="props">
              <q-td :props="props" style="padding:0 4px">
                <span style="font-size:11px;color:#9e9e9e">{{ props.row.legacy_ticket_id || props.row.name }}</span>
              </q-td>
            </template>
            <template #body-cell-subject="props">
              <q-td :props="props" class="ticket-subject-cell">
                <div style="font-size:12.5px;line-height:1.3">{{ props.row.subject }}</div>
              </q-td>
            </template>
            <template #body-cell-assigned="props">
              <q-td :props="props" style="padding:0 2px">
                <div class="avatar-stack">
                  <q-avatar v-if="props.row.opened_by_staff && props.row.opened_by_staff !== props.row.assigned_staff"
                    size="22px" class="avatar-chip" :style="{ background: staffColor(props.row.opened_by_staff), zIndex: 1 }">
                    {{ staffInitials(props.row.opened_by_staff) }}
                    <q-tooltip>{{ props.row.opened_by_staff }}</q-tooltip>
                  </q-avatar>
                  <q-avatar v-if="props.row.assigned_staff"
                    size="22px" class="avatar-chip" :style="{ background: staffColor(props.row.assigned_staff), zIndex: 2 }">
                    {{ staffInitials(props.row.assigned_staff) }}
                    <q-tooltip>{{ props.row.assigned_staff }}</q-tooltip>
                  </q-avatar>
                </div>
              </q-td>
            </template>
            <template #body-cell-opening_date="props">
              <q-td :props="props" style="padding:0 4px">
                <span style="font-size:11px;color:#9e9e9e;white-space:nowrap">{{ props.row.opening_date }}</span>
              </q-td>
            </template>
            <template #body-cell-priority="props">
              <q-td :props="props" style="padding:0 2px">
                <InlineField :value="props.row.priority" field="priority" doctype="Issue" :docname="props.row.name"
                  type="select" :options="['Low', 'Medium', 'High', 'Urgent']"
                  @saved="v => props.row.priority = v.value">
                  <template #display>
                    <span class="ops-badge" style="font-size:10px;padding:1px 6px" :class="priorityClass(props.row.priority)">{{ props.row.priority }}</span>
                  </template>
                </InlineField>
              </q-td>
            </template>
            <template #body-cell-status="props">
              <q-td :props="props" style="padding:0 4px">
                <InlineField :value="props.row.status" field="status" doctype="Issue" :docname="props.row.name"
                  type="select" :options="['Open', 'Replied', 'On Hold', 'Resolved', 'Closed']"
                  @saved="v => props.row.status = v.value">
                  <template #display>
                    <span class="ops-badge" style="font-size:10px;padding:1px 6px" :class="ticketStatusClass(props.row.status)">{{ props.row.status }}</span>
                  </template>
                </InlineField>
              </q-td>
            </template>
          </q-table>
          <div v-if="!ticketsExpanded && tickets.length >= 10" class="text-center q-pa-xs">
            <q-btn flat dense no-caps color="indigo-6" :loading="loadingMoreTickets"
              label="Voir tous les tickets" icon="expand_more" @click="loadAllTickets" />
          </div>
        </div>
      </q-expansion-item>

      <q-expansion-item v-model="sectionsOpen.invoices" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title row items-center" style="font-size:1rem;width:100%">
            <q-icon name="receipt_long" size="20px" class="q-mr-xs" />
            Factures ({{ invoices.length }}{{ !invoicesExpanded ? '+' : '' }})
            <span v-if="totalOutstanding > 0" class="text-caption text-red q-ml-sm">Solde: {{ formatMoney(totalOutstanding) }}</span>
            <q-space />
            <div @click.stop>
              <q-btn flat dense size="sm" icon="add" color="indigo-6" no-caps
                label="Facture" @click="newInvoiceOpen = true">
                <q-tooltip>Creer une facture</q-tooltip>
              </q-btn>
            </div>
          </div>
        </template>
        <div v-if="!invoices.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucune facture</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="invoices" :columns="invoiceCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10, sortBy: 'posting_date', descending: true }"
            @row-click="(_, row) => openModal('Sales Invoice', row.name, 'Facture ' + row.name)">
            <template #body-cell-name="props">
              <q-td :props="props">
                <div class="text-weight-medium">{{ props.row.name }}</div>
                <div v-if="props.row.is_return" class="text-caption text-orange-8">
                  <q-icon name="reply" size="12px" /> Renversement de {{ props.row.return_against }}
                </div>
              </q-td>
            </template>
            <template #body-cell-status="props">
              <q-td :props="props"><span class="ops-badge" :class="invStatusClass(props.row.status)">{{ props.row.status }}</span></q-td>
            </template>
            <template #body-cell-grand_total="props">
              <q-td :props="props" class="text-right" :class="{ 'text-red': props.row.grand_total < 0 }">{{ formatMoney(props.row.grand_total) }}</q-td>
            </template>
            <template #body-cell-outstanding_amount="props">
              <q-td :props="props" class="text-right" :class="{ 'text-red': props.row.outstanding_amount > 0 }">{{ formatMoney(props.row.outstanding_amount) }}</q-td>
            </template>
          </q-table>
          <div v-if="!invoicesExpanded && invoices.length >= 5" class="text-center q-pa-xs">
            <q-btn flat dense no-caps color="indigo-6" :loading="loadingMoreInvoices"
              label="Voir toutes les factures" icon="expand_more" @click="loadAllInvoices" />
          </div>
        </div>
      </q-expansion-item>

      <q-expansion-item v-model="sectionsOpen.payments" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%">
            <q-icon name="payments" size="20px" class="q-mr-xs" />
            Paiements ({{ payments.length }}{{ !paymentsExpanded ? '+' : '' }})
          </div>
        </template>
        <div v-if="!payments.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucun paiement</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="payments" :columns="paymentCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10, sortBy: 'posting_date', descending: true }"
            @row-click="(_, row) => openModal('Payment Entry', row.name, 'Paiement ' + row.name)">
            <template #body-cell-paid_amount="props">
              <q-td :props="props" class="text-right">{{ formatMoney(props.row.paid_amount) }}</q-td>
            </template>
            <template #body-cell-actions="props">
              <q-td :props="props" @click.stop>
                <q-btn v-if="props.row.reference_no && props.row.reference_no.startsWith('pi_')"
                  flat round dense size="xs" icon="replay" color="orange-7" :loading="refunding"
                  @click="confirmRefund(props.row)">
                  <q-tooltip>Rembourser via Stripe</q-tooltip>
                </q-btn>
              </q-td>
            </template>
          </q-table>
          <div v-if="!paymentsExpanded && payments.length >= 5" class="text-center q-pa-xs">
            <q-btn flat dense no-caps color="indigo-6" :loading="loadingMorePayments"
              label="Voir tous les paiements" icon="expand_more" @click="loadAllPayments" />
          </div>
        </div>
      </q-expansion-item>

      <!-- VoIP Lines -->
      <q-expansion-item v-model="sectionsOpen.voip" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%">
            <q-icon name="phone" size="20px" class="q-mr-xs" />
            Lignes VoIP ({{ voipLines.length }})
            <span v-if="voipLines.filter(v => !v.e911_synced).length" class="text-caption text-orange q-ml-sm">
              {{ voipLines.filter(v => !v.e911_synced).length }} sans 911
            </span>
          </div>
        </template>
        <div v-if="!voipLines.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucune ligne VoIP</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="voipLines" :columns="voipCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 20 }"
            @row-click="(_, row) => openModal('VoIP Line', row.name, 'VoIP ' + row.did)">
            <template #body-cell-status="props">
              <q-td :props="props">
                <span class="ops-badge" :class="props.row.status === 'Active' ? 'open' : 'inactive'">{{ props.row.status }}</span>
              </q-td>
            </template>
            <template #body-cell-e911_synced="props">
              <q-td :props="props">
                <q-icon :name="props.row.e911_synced ? 'check_circle' : 'warning'" size="16px"
                  :color="props.row.e911_synced ? 'green-6' : 'orange-7'" />
              </q-td>
            </template>
          </q-table>
        </div>
      </q-expansion-item>

      <!-- Payment Methods + Actions -->
      <q-expansion-item v-model="sectionsOpen.paymentMethods" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%">
            <q-icon name="credit_card" size="20px" class="q-mr-xs" />
            Paiement ({{ paymentMethods.length }})
            <span v-if="paymentMethods.some(p => p.is_auto_ppa)" class="ops-badge open q-ml-sm" style="font-size:10px">PPA</span>
            <q-space />
            <div class="q-gutter-x-xs" @click.stop>
              <q-btn flat dense size="sm" icon="send" color="indigo-6" no-caps :loading="sendingLink"
                @click="sendPaymentLink('both')">
                <q-tooltip>Envoyer lien de paiement (SMS + Email)</q-tooltip>
              </q-btn>
              <q-btn flat dense size="sm" icon="bolt" color="orange-8" no-caps :loading="chargingCard"
                @click="chargeCard()">
                <q-tooltip>Prélever le solde maintenant</q-tooltip>
              </q-btn>
            </div>
          </div>
        </template>

        <div class="ops-card q-mb-md">
          <!-- Action buttons row -->
          <div class="row q-gutter-sm q-mb-md q-pa-sm" style="background:#f8fafc;border-radius:8px">
            <q-btn outline dense no-caps color="indigo-6" icon="open_in_new" label="Portail Stripe"
              size="sm" @click="openPortal">
              <q-tooltip>Ouvrir le portail Stripe pour gérer la carte</q-tooltip>
            </q-btn>
            <q-btn outline dense no-caps color="green-7" icon="add_card" label="Ajouter carte"
              size="sm" @click="createCheckout">
              <q-tooltip>Créer un lien Stripe Checkout (copié au presse-papier)</q-tooltip>
            </q-btn>
            <q-btn outline dense no-caps icon="sms" label="Lien SMS" size="sm"
              color="blue-7" :loading="sendingLink" @click="sendPaymentLink('sms')">
              <q-tooltip>Envoyer lien de paiement par SMS</q-tooltip>
            </q-btn>
            <q-btn outline dense no-caps icon="email" label="Lien Email" size="sm"
              color="purple-6" :loading="sendingLink" @click="sendPaymentLink('email')">
              <q-tooltip>Envoyer lien de paiement par Email</q-tooltip>
            </q-btn>
          </div>

          <div v-if="!paymentMethods.length" class="text-center text-grey-6 q-pa-md">Aucune méthode de paiement</div>
          <q-table v-else :rows="paymentMethods" :columns="paymentMethodCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10 }"
            @row-click="(_, row) => openModal('Payment Method', row.name, row.provider)">
            <template #body-cell-is_auto_ppa="props">
              <q-td :props="props" @click.stop>
                <q-toggle v-if="props.row.provider === 'Stripe'" dense size="sm"
                  :model-value="!!props.row.is_auto_ppa" :loading="togglingPpa"
                  @update:model-value="v => togglePpa(v).then(ok => { if (ok) props.row.is_auto_ppa = v ? 1 : 0 })" />
                <q-icon v-else-if="props.row.is_auto_ppa" name="autorenew" size="16px" color="green-6" />
              </q-td>
            </template>
            <template #body-cell-provider="props">
              <q-td :props="props">
                <q-icon :name="props.row.provider === 'Stripe' ? 'credit_card' : props.row.provider === 'Bank Draft' ? 'account_balance' : 'payment'"
                  size="14px" class="q-mr-xs" />
                {{ props.row.provider }}
              </q-td>
            </template>
          </q-table>
        </div>
      </q-expansion-item>

      <!-- Payment Arrangements -->
      <q-expansion-item v-model="sectionsOpen.arrangements" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%">
            <q-icon name="handshake" size="20px" class="q-mr-xs" />
            Ententes de paiement ({{ arrangements.length }})
            <span v-if="arrangements.filter(a => a.status === 'Open').length" class="text-caption text-blue q-ml-sm">
              {{ arrangements.filter(a => a.status === 'Open').length }} actives
            </span>
          </div>
        </template>
        <div v-if="!arrangements.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucune entente</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="arrangements" :columns="arrangementCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10, sortBy: 'date_agreed', descending: true }"
            @row-click="(_, row) => openModal('Payment Arrangement', row.name, 'Entente ' + row.name)">
            <template #body-cell-status="props">
              <q-td :props="props">
                <span class="ops-badge" :class="props.row.status === 'Open' ? 'open' : props.row.status === 'Completed' ? 'paid' : 'inactive'">{{ props.row.status }}</span>
              </q-td>
            </template>
            <template #body-cell-total_amount="props">
              <q-td :props="props" class="text-right">{{ formatMoney(props.row.total_amount) }}</q-td>
            </template>
          </q-table>
        </div>
      </q-expansion-item>

      <!-- Contrats de service (offres de service) — wizard-created artifacts
           that summarize récurrent + durée + benefits. This is the primary
           "sommaire" view after a projet est soumis. -->
      <q-expansion-item v-model="sectionsOpen.serviceContracts" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%;display:flex;align-items:center">
            <q-icon name="handshake" size="20px" color="orange-7" class="q-mr-xs" />
            Contrats de service ({{ serviceContracts.length }})
            <q-badge v-if="serviceContracts.some(c => c.status === 'Brouillon' || c.status === 'Envoyé')"
              color="orange-6" text-color="white" class="q-ml-sm">
              {{ serviceContracts.filter(c => c.status === 'Brouillon' || c.status === 'Envoyé').length }} en attente
            </q-badge>
            <q-space />
            <FlowQuickButton flat dense size="sm" icon="account_tree" label="Flows contrats"
              tooltip="Éditer les automatisations après signature"
              category="residential" applies-to="Service Contract"
              trigger-event="on_contract_signed" @click.stop />
          </div>
        </template>
        <div v-if="!serviceContracts.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucun contrat de service</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="serviceContracts" :columns="serviceContractCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10, sortBy: 'start_date', descending: true }"
            @row-click="(_, row) => openModal('Service Contract', row.name, 'Contrat ' + row.name)">
            <template #body-cell-duration_months="props">
              <q-td :props="props" class="text-right">{{ props.row.duration_months }} mois</q-td>
            </template>
            <template #body-cell-monthly_rate="props">
              <q-td :props="props" class="text-right">{{ formatMoney(props.row.monthly_rate) }}/m</q-td>
            </template>
            <template #body-cell-total_benefit_value="props">
              <q-td :props="props" class="text-right">
                <span v-if="props.row.total_benefit_value" class="text-green-7">
                  {{ formatMoney(props.row.total_benefit_value) }}
                </span>
                <span v-else class="text-grey-5">—</span>
              </q-td>
            </template>
            <template #body-cell-status="props">
              <q-td :props="props" class="text-center">
                <q-chip dense :color="contractStatusColor(props.row.status)" text-color="white"
                  size="sm" class="q-px-sm">
                  {{ props.row.status }}
                </q-chip>
              </q-td>
            </template>
          </q-table>
        </div>
      </q-expansion-item>

      <!-- Soumissions / Quotations -->
      <q-expansion-item v-model="sectionsOpen.quotations" header-class="section-header" class="q-mb-sm">
        <template #header>
          <div class="section-title" style="font-size:1rem;width:100%;display:flex;align-items:center">
            <q-icon name="request_quote" size="20px" class="q-mr-xs" />
            Soumissions ({{ quotations.length }})
            <q-space />
            <q-btn flat dense size="sm" icon="add" label="Nouvelle soumission" color="indigo-6" no-caps
              @click.stop="openNewQuotation()" class="q-mr-sm" />
          </div>
        </template>
        <div v-if="!quotations.length" class="ops-card text-center text-grey-6 q-pa-md q-mb-md">Aucune soumission</div>
        <div v-else class="ops-card q-mb-md">
          <q-table :rows="quotations" :columns="quotationCols" row-key="name"
            flat dense class="ops-table clickable-table"
            :pagination="{ rowsPerPage: 10, sortBy: 'transaction_date', descending: true }"
            @row-click="(_, row) => openModal('Quotation', row.name, 'Soumission ' + row.name)">
            <template #body-cell-grand_total="props">
              <q-td :props="props" class="text-right">{{ formatMoney(props.row.grand_total) }}</q-td>
            </template>
          </q-table>
        </div>
      </q-expansion-item>

      </div>

        <div class="col-12 col-lg-4">
          <div class="convos-sticky">
            <ChatterPanel
              :customer-name="customer.name"
              :customer-phone="customer.cell_phone || customer.tel_home || ''"
              :customer-phones="customerPhoneOptions"
              :customer-email="customer.email_billing || ''"
              :comments="comments"
              @note-added="onNoteAdded"
              @note-updated="onNoteAdded"
              @navigate="(dt, name) => openModal(dt, name)" />
          </div>
        </div>
      </div>

    </template>

    <div v-else class="ops-card text-center q-pa-xl">
      <q-icon name="error_outline" size="48px" color="grey-5" />
      <div class="text-h6 text-grey-6 q-mt-sm">Client introuvable</div>
      <q-btn flat color="indigo-6" label="Retour" icon="arrow_back" @click="$router.push('/clients')" class="q-mt-md" />
    </div>

    <!-- Add Service / Subscription dialog -->
    <q-dialog v-model="addServiceOpen" persistent>
      <q-card style="width:560px;max-width:95vw">
        <q-card-section class="row items-center q-pb-sm">
          <q-icon :name="addServiceMode === 'rabais' ? 'sell' : 'wifi'" size="22px"
            :color="addServiceMode === 'rabais' ? 'red-5' : 'indigo-6'" class="q-mr-sm" />
          <div class="text-subtitle1 text-weight-bold">
            {{ addServiceMode === 'rabais' ? 'Ajouter un rabais / crédit' : 'Ajouter un forfait / service' }}
          </div>
          <q-space />
          <q-badge v-if="addServiceLoc" color="indigo-1" text-color="indigo-8" class="q-pa-xs">
            {{ addServiceLoc.address_line || addServiceLoc.location_name }}
          </q-badge>
        </q-card-section>

        <q-separator />

        <q-card-section>
          <div class="row q-col-gutter-sm">
            <!-- Plan search (searches Subscription Plans linked to Items) -->
            <div class="col-12">
              <q-select v-model="newService.plan" label="Forfait / Plan (recherche catalogue)"
                outlined dense use-input input-debounce="300" emit-value map-options
                :options="planSearchResults" option-label="label" option-value="value"
                @filter="searchPlans" clearable
                @update:model-value="onPlanSelected">
                <template #no-option>
                  <q-item>
                    <q-item-section class="text-grey">Aucun forfait dans le catalogue</q-item-section>
                  </q-item>
                </template>
                <template #option="scope">
                  <q-item v-bind="scope.itemProps">
                    <q-item-section>
                      <q-item-label>{{ scope.opt.item_name }}</q-item-label>
                      <q-item-label caption>{{ scope.opt.item_code }} · {{ scope.opt.item_group || '' }}</q-item-label>
                    </q-item-section>
                    <q-item-section side>
                      <q-item-label v-if="scope.opt.cost" class="text-weight-bold">{{ formatMoney(scope.opt.cost) }}/m</q-item-label>
                    </q-item-section>
                  </q-item>
                </template>
              </q-select>
            </div>

            <!-- Description override -->
            <div class="col-12">
              <q-input v-model="newService.description" label="Description (override)" outlined dense
                :placeholder="newService.item_name || 'Laisser vide pour utiliser le nom du plan'" />
            </div>

            <!-- Price override + start date -->
            <div class="col-6">
              <q-input v-model.number="newService.price" :label="addServiceMode === 'rabais' ? 'Montant du rabais' : 'Prix mensuel'"
                outlined dense type="number" step="0.01"
                :prefix="addServiceMode === 'rabais' ? '-' : ''" suffix="$">
                <template #hint>
                  <span v-if="addServiceMode === 'rabais'" class="text-red">Le montant sera négatif (crédit)</span>
                </template>
              </q-input>
            </div>
            <div class="col-6">
              <q-input v-model="newService.start_date" label="Date de début" outlined dense type="date" />
            </div>
          </div>
        </q-card-section>

        <q-card-actions align="right">
          <q-btn flat label="Annuler" @click="addServiceOpen = false" />
          <q-btn unelevated :color="addServiceMode === 'rabais' ? 'red-5' : 'indigo-6'"
            :icon="addServiceMode === 'rabais' ? 'sell' : 'add'"
            :label="addServiceMode === 'rabais' ? 'Ajouter le rabais' : 'Ajouter le service'"
            :loading="addingService" :disable="!newService.plan && !newService.description"
            @click="createService" />
        </q-card-actions>
      </q-card>
    </q-dialog>

    <q-dialog v-model="addEquipOpen" persistent>
      <q-card style="width:520px;max-width:95vw">
        <q-card-section class="row items-center q-pb-sm">
          <q-icon name="add_circle" size="22px" color="indigo-6" class="q-mr-sm" />
          <div class="text-subtitle1 text-weight-bold">Ajouter un equipement</div>
          <q-space />
          <q-badge v-if="addEquipLoc" color="indigo-1" text-color="indigo-8" class="q-pa-xs">
            {{ addEquipLoc.address_line || addEquipLoc.location_name }}
          </q-badge>
        </q-card-section>

        <q-separator />

        <q-card-section>
          <div class="row items-center q-mb-md q-pa-sm" style="background:#f0fdf4;border-radius:8px;border:1px solid #bbf7d0">
            <q-icon name="qr_code_scanner" size="24px" color="green-7" class="q-mr-sm" />
            <div class="col">
              <div class="text-body2 text-weight-medium">Scanner un code-barres / serial</div>
              <div class="text-caption text-grey-6">Photo de l'etiquette, code-barres ou QR code</div>
            </div>
            <input ref="scanInput" type="file" accept="image/*" capture="environment" style="display:none"
              @change="onScanPhoto" />
            <q-btn unelevated dense color="green-7" icon="photo_camera" label="Scanner"
              :loading="scannerState.scanning.value" @click="$refs.scanInput.click()" />
          </div>

          <div v-if="scannerState.barcodes.value.length" class="q-mb-md">
            <div class="text-caption text-grey-7 q-mb-xs">Codes detectes :</div>
            <div class="row q-gutter-xs">
              <q-chip v-for="(bc, i) in scannerState.barcodes.value" :key="i" dense removable
                color="green-1" text-color="green-9" icon="qr_code"
                @remove="scannerState.barcodes.value.splice(i, 1)"
                clickable @click="applyScannedCode(bc.value)">
                {{ bc.value }}
                <q-tooltip>Cliquer pour utiliser comme N serie</q-tooltip>
              </q-chip>
            </div>
          </div>
          <div v-if="scannerState.error.value" class="text-caption text-orange-8 q-mb-sm">
            {{ scannerState.error.value }}
          </div>

          <div v-if="scannerState.lastPhoto.value" class="q-mb-md text-center">
            <img :src="scannerState.lastPhoto.value" style="max-height:120px;border-radius:8px;border:1px solid #e2e8f0" />
          </div>

          <q-separator class="q-mb-md" />

          <div class="row q-col-gutter-sm">
            <div class="col-6">
              <q-select v-model="newEquip.equipment_type" label="Type" outlined dense emit-value map-options
                :options="equipTypeOptions" />
            </div>
            <div class="col-6">
              <q-select v-model="newEquip.status" label="Statut" outlined dense emit-value map-options
                :options="equipStatusOptions" />
            </div>
            <div class="col-12">
              <q-input v-model="newEquip.serial_number" label="N de serie" outlined dense>
                <template #append>
                  <q-icon v-if="newEquip.serial_number && equipLookupResult" size="18px"
                    :name="equipLookupResult.found ? 'check_circle' : 'help_outline'"
                    :color="equipLookupResult.found ? 'green-6' : 'grey-5'" />
                  <q-spinner v-if="equipLookingUp" size="16px" color="indigo-6" />
                </template>
              </q-input>
              <div v-if="equipLookupResult?.found" class="text-caption text-orange-7 q-mt-xs">
                Cet equipement existe deja : {{ equipLookupResult.equipment.name }}
                ({{ equipLookupResult.equipment.equipment_type }}, {{ equipLookupResult.equipment.service_location || 'non assigne' }})
              </div>
            </div>
            <div class="col-6">
              <q-input v-model="newEquip.brand" label="Marque" outlined dense />
            </div>
            <div class="col-6">
              <q-input v-model="newEquip.model" label="Modele" outlined dense />
            </div>
            <div class="col-6">
              <q-input v-model="newEquip.mac_address" label="Adresse MAC" outlined dense />
            </div>
            <div class="col-6">
              <q-input v-model="newEquip.ip_address" label="Adresse IP" outlined dense />
            </div>
          </div>
        </q-card-section>

        <q-card-actions align="right">
          <q-btn flat label="Annuler" @click="closeAddEquipment" />
          <q-btn v-if="equipLookupResult?.found" unelevated color="amber-8" icon="link"
            label="Lier a cette adresse" :loading="addingEquip"
            @click="linkExistingEquipment" />
          <q-btn v-else unelevated color="indigo-6" icon="add" label="Creer"
            :loading="addingEquip" :disable="!newEquip.serial_number || !newEquip.equipment_type"
            @click="createEquipment" />
        </q-card-actions>
      </q-card>
    </q-dialog>

    <UnifiedCreateModal v-model="newTicketOpen" mode="ticket"
      :context="ticketContext" :locations="locations"
      @created="onTicketCreated" />

    <CreateInvoiceModal v-model="newInvoiceOpen" :customer="customer" @created="onInvoiceCreated" />

    <ProjectWizard v-model="newQuotationOpen" :customer="quotationCustomerContext"
      :delivery-address-id="wizardDeliveryAddressId" @created="onQuotationPublished" />

    <DetailModal
      v-model:open="modalOpen" :loading="modalLoading" :doctype="modalDoctype"
      :doc-name="modalDocName" :title="modalTitle" :doc="modalDoc"
      :comments="modalComments" :comms="modalComms" :files="modalFiles"
      :doc-fields="modalDocFields" :dispatch-jobs="modalDispatchJobs"
      @navigate="(dt, name, t) => openModal(dt, name, t)"
      @open-pdf="openPdf" @save-field="saveSubField"
      @toggle-recurring="toggleRecurringModal" @dispatch-created="onDispatchCreated"
      @dispatch-deleted="name => { modalDispatchJobs = modalDispatchJobs.filter(j => j.name !== name) }"
      @deleted="onEntityDeleted" />
  </q-page>
</template>

<script setup>
import { ref, computed, onMounted, reactive, watch } from 'vue'
import { Notify, useQuasar } from 'quasar'
import draggable from 'vuedraggable'
import { deleteDoc, createDoc, listDocs } from 'src/api/erp'
import { authFetch } from 'src/api/auth'
import { formatDate, formatMoney, staffColor, staffInitials } from 'src/composables/useFormatters'
import { locStatusClass, ticketStatusClass, invStatusClass, deviceColorClass, priorityClass } from 'src/composables/useStatusClasses'
import { useDetailModal } from 'src/composables/useDetailModal'
import { useSubscriptionGroups, isRebate, subMainLabel, sectionTotal, annualPrice } from 'src/composables/useSubscriptionGroups'
import { useSubscriptionActions } from 'src/composables/useSubscriptionActions'
import { useCustomerNotes } from 'src/composables/useCustomerNotes'
import { invoiceCols, paymentCols, ticketCols, voipCols, paymentMethodCols, arrangementCols, quotationCols, serviceContractCols } from 'src/config/table-columns'
import { deviceLucideIcon } from 'src/config/device-icons'
import DetailModal from 'src/components/shared/DetailModal.vue'
import CustomerHeader from 'src/components/customer/CustomerHeader.vue'
import ContactCard from 'src/components/customer/ContactCard.vue'
import CustomerInfoCard from 'src/components/customer/CustomerInfoCard.vue'
import InlineField from 'src/components/shared/InlineField.vue'
import ChatterPanel from 'src/components/customer/ChatterPanel.vue'
import UnifiedCreateModal from 'src/components/shared/UnifiedCreateModal.vue'
import FlowQuickButton from 'src/components/flow-editor/FlowQuickButton.vue'
import CreateInvoiceModal from 'src/components/shared/CreateInvoiceModal.vue'
import ProjectWizard from 'src/components/shared/ProjectWizard.vue'
import { useDeviceStatus } from 'src/composables/useDeviceStatus'
import { usePermissions } from 'src/composables/usePermissions'
import { usePaymentActions } from 'src/composables/usePaymentActions'
import { useClientData } from 'src/composables/useClientData'
import { useEquipmentActions } from 'src/composables/useEquipmentActions'
import { locInlineFields, equipTypeOptions, equipStatusOptions, defaultSectionsOpen, phoneLabelMap } from 'src/data/client-constants'
import { erpPdfUrl } from 'src/utils/erp-pdf'

const $q = useQuasar()
const { can } = usePermissions()
const props = defineProps({ id: String })

const equipment = ref([])
const ticketsExpanded = ref(false)
const invoicesExpanded = ref(false)
const paymentsExpanded = ref(false)

const { fetchStatus, fetchOltStatus, getDevice, isOnline, combinedStatus, signalQuality, rebootDevice, refreshDeviceParams } = useDeviceStatus()

const {
  modalOpen, modalLoading, modalDoctype, modalDocName, modalTitle,
  modalDoc, modalComments, modalComms, modalFiles, modalDocFields,
  modalDispatchJobs, openModal,
} = useDetailModal()

const {
  loading, customer, contact, locations, subscriptions, tickets,
  invoices, payments, voipLines, paymentMethods, arrangements, quotations,
  serviceContracts, comments, accountBalance,
  loadingMoreTickets, loadingMoreInvoices, loadingMorePayments,
  loadCustomer, loadAllTickets, loadAllInvoices, loadAllPayments,
} = useClientData({ equipment, modalOpen, ticketsExpanded, invoicesExpanded, paymentsExpanded, invalidateAll: () => invalidateAll(), fetchStatus, fetchOltStatus })

const {
  locSubs, locSubsMonthly, locSubsAnnual, locSubsMonthlyTotal, locSubsAnnualTotal,
  locSubsSections, sectionOpen, toggleSection, invalidateCache, invalidateAll,
} = useSubscriptionGroups(subscriptions)

const {
  subSaving, togglingRecurring,
  toggleSubStatus, toggleFrequency, toggleRecurring,
  toggleRecurringModal, saveSubField, logSubChange, onSubDragChange,
} = useSubscriptionActions(subscriptions, customer, comments, invalidateCache)

const { onNoteAdded } = useCustomerNotes(comments, customer)

const {
  sendingLink, chargingCard, togglingPpa, refunding,
  sendPaymentLink, chargeCard, togglePpa, openPortal, createCheckout,
  refundPayment,
} = usePaymentActions(customer)

const {
  scannerState, addEquipOpen, addEquipLoc, addingEquip,
  equipLookupResult, equipLookingUp, newEquip,
  openAddEquipment, closeAddEquipment, onScanPhoto,
  applyScannedCode, createEquipment, linkExistingEquipment,
} = useEquipmentActions(customer, equipment)

// ── Add Service / Subscription (native ERPNext Subscription) ────
const addServiceOpen = ref(false)
const addServiceLoc = ref(null)
const addServiceMode = ref('service') // 'service' | 'rabais'
const addingService = ref(false)
const planSearchResults = ref([])
const newService = reactive({
  plan: null,        // Subscription Plan name
  item_code: '',
  item_name: '',
  item_group: '',
  description: '',
  price: 0,
  start_date: new Date().toISOString().slice(0, 10),
})

function openAddService (loc, mode = 'service') {
  addServiceLoc.value = loc
  addServiceMode.value = mode
  Object.assign(newService, {
    plan: null, item_code: '', item_name: '', item_group: '',
    description: '',
    price: 0,
    start_date: new Date().toISOString().slice(0, 10),
  })
  addServiceOpen.value = true
}

async function searchPlans (val, update) {
  // Empty query → show a reasonable top-of-catalog listing so the dispatcher
  // can browse even without typing ("list catalog + add manually" UX).
  // Short query (1 char) also returns the top slice — Frappe's `like %a%`
  // would match half the catalog, not helpful. 2+ chars → real filter.
  const browseAll = !val || val.length < 2
  try {
    const plans = await listDocs('Subscription Plan', {
      filters: {},
      ...(browseAll ? {} : {
        or_filters: [
          ['name', 'like', `%${val}%`],
          ['item', 'like', `%${val}%`],
          ['plan_name', 'like', `%${val}%`],
        ],
      }),
      fields: ['name', 'item', 'cost', 'plan_name'],
      limit: browseAll ? 50 : 20,
      orderBy: 'plan_name asc',
    })
    // Enrich with Item info
    const itemCodes = [...new Set(plans.map(p => p.item).filter(Boolean))]
    const itemMap = {}
    if (itemCodes.length) {
      const items = await listDocs('Item', {
        filters: { name: ['in', itemCodes] },
        fields: ['name', 'item_name', 'item_group'],
        limit: itemCodes.length,
      })
      for (const it of items) itemMap[it.name] = it
    }
    update(() => {
      planSearchResults.value = plans.map(p => {
        const item = itemMap[p.item] || {}
        return {
          label: `${item.item_name || p.plan_name || p.item} (${p.item})`,
          value: p.name,
          item_code: p.item,
          item_name: item.item_name || p.plan_name || p.item,
          item_group: item.item_group || '',
          cost: parseFloat(p.cost || 0),
        }
      })
    })
  } catch {
    update(() => { planSearchResults.value = [] })
  }
}

function onPlanSelected (val) {
  if (!val) return
  const plan = planSearchResults.value.find(p => p.value === val)
  if (plan) {
    newService.item_code = plan.item_code
    newService.item_name = plan.item_name
    newService.item_group = plan.item_group
    if (!newService.description) newService.description = plan.item_name
    if (!newService.price) {
      newService.price = addServiceMode.value === 'rabais' ? Math.abs(plan.cost) : plan.cost
    }
  }
}

// Map an item_group string (e.g. "Internet", "Téléphonie IP", "Forfait Bundle")
// to one of the Service Subscription service_category enum values. Falls
// back to "Autre" when we can't guess.
function inferServiceCategory (itemGroup, planLabel = '') {
  const g = `${itemGroup || ''} ${planLabel || ''}`.toLowerCase()
  if (g.includes('iptv') || g.includes('tvbevo') || g.includes(' tv') || g.startsWith('tv')) return 'IPTV'
  if (g.includes('voip') || g.includes('tele') || g.includes('phone')) return 'VoIP'
  if (g.includes('ftth') || g.includes('fibre') || g.includes('internet')) return 'Internet'
  if (g.includes('bundle') || g.includes('forfait')) return 'Bundle'
  if (g.includes('heberg') || g.includes('host')) return 'Hébergement'
  return 'Autre'
}

async function createService () {
  if (!addServiceLoc.value || (!newService.plan && !newService.description)) return
  addingService.value = true
  try {
    const price = addServiceMode.value === 'rabais'
      ? -Math.abs(newService.price || 0)
      : (newService.price || 0)

    const custId = customer.value.name
    const locName = addServiceLoc.value.name
    const today = newService.start_date
    const planLabel = newService.description || newService.item_name || 'Service'
    const category = inferServiceCategory(newService.item_group, planLabel)

    // Create a Service Subscription — flat doc, no parent/child plans table.
    // This is also what contracts + chain activation produce, so anything
    // manually added here lives in the same universe.
    const doc = await createDoc('Service Subscription', {
      customer: custId,
      customer_name: customer.value.customer_name || '',
      service_location: locName,
      service_category: category,
      plan_name: planLabel.slice(0, 140),
      monthly_price: price,
      billing_cycle: 'Mensuel',
      start_date: today,
      status: 'Actif',                      // manual add → immediately active
      product_sku: newService.item_code || '',
    })

    // Add to local subscriptions list in the same UI row shape produced by
    // useClientData.loadSubscriptions.
    subscriptions.value.push({
      name: doc.name,
      subscription: doc.name,
      plan_name: planLabel,
      item_code: newService.item_code || '',
      item_name: planLabel,
      item_group: newService.item_group || category,
      custom_description: planLabel,
      actual_price: price,
      service_location: locName,
      billing_frequency: 'M',
      status: 'Active',
      start_date: today,
      end_date: null,
      cancel_at_period_end: 0,
      qty: 1,
    })
    invalidateCache(locName)
    invalidateAll()

    Notify.create({ type: 'positive', message: `${planLabel} ajouté (${formatMoney(price)}/m)`, position: 'top', timeout: 3000 })
    addServiceOpen.value = false
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + (e.message || 'Création impossible'), position: 'top', timeout: 4000 })
  } finally {
    addingService.value = false
  }
}

// Inline price edit callback — the InlineField has already persisted
// `monthly_price` to ERPNext; we mirror it into the UI row
// (`actual_price`), invalidate the location cache so the monthly total
// recomputes, and drop an audit line.
function onSubPriceSaved (sub, evt) {
  const oldPrice = Number(sub.actual_price || 0)
  const newPrice = Number(evt.value || 0)
  sub.actual_price = newPrice
  if (sub.service_location) invalidateCache(sub.service_location)
  if (oldPrice !== newPrice) {
    logSubChange(sub, `Prix modifié: ${formatMoney(oldPrice)} → ${formatMoney(newPrice)}`)
  }
}

// Delete a subscription row. Uses the Quasar Dialog for a confirm step
// because a click-through on a red bin would be too trigger-happy.
async function confirmDeleteSub (sub) {
  const label = sub.plan_name || sub.item_name || sub.name
  $q.dialog({
    title: 'Supprimer ce service ?',
    message: `<div><code>${sub.name}</code> — ${label} (${formatMoney(sub.actual_price)})</div>`
      + '<div class="text-caption text-grey-6 q-mt-sm">L\'historique de facturation reste intact.</div>',
    html: true,
    cancel: { flat: true, label: 'Annuler' },
    ok: { color: 'red', label: 'Supprimer', unelevated: true, icon: 'delete' },
    persistent: true,
  }).onOk(async () => {
    try {
      await deleteDoc('Service Subscription', sub.name)
      const idx = subscriptions.value.findIndex(s => s.name === sub.name)
      if (idx >= 0) subscriptions.value.splice(idx, 1)
      if (sub.service_location) invalidateCache(sub.service_location)
      invalidateAll()
      Notify.create({ type: 'positive', message: `${label} supprimé`, position: 'top', timeout: 2500 })
    } catch (e) {
      Notify.create({ type: 'negative', message: 'Erreur: ' + (e.message || 'Suppression impossible'), position: 'top', timeout: 4000 })
    }
  })
}

function onDispatchCreated (job) { modalDispatchJobs.value.push(job) }

function formatTimeAgo (dateStr) {
  if (!dateStr) return ''
  const diff = Date.now() - new Date(dateStr).getTime()
  const mins = Math.floor(diff / 60000)
  if (mins < 1) return 'à l\'instant'
  if (mins < 60) return `il y a ${mins}m`
  const hrs = Math.floor(mins / 60)
  return hrs < 24 ? `il y a ${hrs}h` : `il y a ${Math.floor(hrs / 24)}j`
}

function signalColor (serial) {
  const q = signalQuality(serial)
  return q === 'excellent' ? '#4ade80' : q === 'good' ? '#a3e635' : q === 'fair' ? '#fbbf24' : '#f87171'
}

async function doReboot (eq) {
  try {
    await rebootDevice(eq.serial_number)
    Notify.create({ type: 'positive', message: `Redémarrage envoyé: ${eq.serial_number}`, position: 'top' })
  } catch (e) {
    Notify.create({ type: 'negative', message: `Erreur: ${e.message}`, position: 'top' })
  }
}

async function doRefreshParams (eq) {
  try {
    await refreshDeviceParams(eq.serial_number)
    Notify.create({ type: 'info', message: `Rafraîchissement lancé: ${eq.serial_number}`, position: 'top' })
    setTimeout(() => fetchStatus([eq]), 3000)
  } catch (e) {
    Notify.create({ type: 'negative', message: `Erreur: ${e.message}`, position: 'top' })
  }
}

const locCollapsed = reactive({})
const locHasSubs = (locName) => subscriptions.value.some(s => s.service_location === locName)
const toggleLocCollapse = (locName) => { locCollapsed[locName] = locCollapsed[locName] === false }
const sortedLocations = computed(() => {
  const withSubs = locations.value.filter(l => locHasSubs(l.name))
  const withoutSubs = locations.value.filter(l => !locHasSubs(l.name))
  return [...withSubs, ...withoutSubs]
})

const deletingLoc = ref(null)
function confirmDeleteLocation (loc) {
  if (locHasSubs(loc.name)) return
  $q.dialog({
    title: 'Supprimer cette adresse ?',
    message: `${loc.address_line || loc.name} sera supprimee definitivement.`,
    cancel: { flat: true, label: 'Annuler' },
    ok: { color: 'negative', label: 'Supprimer', flat: true },
    persistent: true,
  }).onOk(async () => {
    deletingLoc.value = loc.name
    try {
      await deleteDoc('Service Location', loc.name)
      locations.value = locations.value.filter(l => l.name !== loc.name)
      Notify.create({ type: 'positive', message: 'Adresse supprimee' })
    } catch (e) {
      Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
    } finally {
      deletingLoc.value = null
    }
  })
}

function onEntityDeleted (docName) {
  equipment.value = equipment.value.filter(e => e.name !== docName)
  invoices.value = invoices.value.filter(i => i.name !== docName)
}

const sectionsOpen = ref({ ...defaultSectionsOpen })

const openTicketCount = computed(() => tickets.value.filter(t => t.status === 'Open').length)
const totalOutstanding = computed(() => accountBalance.value?.balance ?? 0)

const customerPhoneOptions = computed(() => {
  if (!customer.value) return []
  return Object.entries(phoneLabelMap)
    .filter(([k]) => customer.value[k])
    .map(([k, label]) => ({ label: `${label}: ${customer.value[k]}`, value: customer.value[k] }))
})

const locEquip = (locName) => equipment.value.filter(e => e.service_location === locName)
const locTickets = (locName) => tickets.value.filter(t => t.service_location === locName)

async function openPdf (name) {
  try {
    const res = await authFetch(erpPdfUrl(name))
    if (!res.ok) throw new Error('PDF error ' + res.status)
    const blob = await res.blob()
    const url = URL.createObjectURL(blob)
    window.open(url, '_blank')
    setTimeout(() => URL.revokeObjectURL(url), 60000)
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur PDF : ' + e.message })
  }
}

const newTicketOpen = ref(false)
const ticketContext = ref({})

const newQuotationOpen = ref(false)
const wizardDeliveryAddressId = ref('')
const quotationCustomerContext = computed(() => {
  if (!customer.value) return null
  const primaryLoc = sortedLocations.value?.find(l => locHasSubs(l.name)) || sortedLocations.value?.[0]
  return {
    name: customer.value.name,
    customer_name: customer.value.customer_name,
    cell_phone: customer.value.cell_phone,
    tel_home: customer.value.tel_home,
    email_billing: customer.value.email_billing,
    email_id: customer.value.email_id,
    service_location: wizardDeliveryAddressId.value || primaryLoc?.name || '',
  }
})

function openNewQuotation () {
  wizardDeliveryAddressId.value = ''
  newQuotationOpen.value = true
}

function openWizardForAddress (loc) {
  wizardDeliveryAddressId.value = loc?.name || ''
  newQuotationOpen.value = true
}

function onQuotationPublished () {
  loadCustomer?.()
}

function openNewTicket (loc = null, sub = null) {
  const subLabel = sub ? (sub.custom_description || sub.item_name || sub.name) : ''
  const locLabel = loc ? (loc.address_line || loc.location_name || '') : ''
  ticketContext.value = {
    customer: customer.value?.name || '',
    subject: sub ? `${subLabel} — ${locLabel}`.trim().replace(/ — $/, '') : '',
    service_location: loc?.name || null,
    locationLabel: loc ? (loc.address_line || loc.location_name || '') : '',
    subscription: sub?.name || '',
  }
  newTicketOpen.value = true
}

function onTicketCreated (doc) {
  tickets.value.unshift({
    name: doc.name, subject: doc.subject, status: 'Open',
    priority: doc.priority, opening_date: new Date().toISOString().slice(0, 10),
    service_location: doc.service_location || '', issue_type: doc.issue_type || '',
  })
  openModal('Issue', doc.name, doc.subject)
}

// Service Contract status → chip color. Matches ERPNext status values.
function contractStatusColor (status) {
  switch (status) {
    case 'Actif': return 'green-6'
    case 'Envoyé': return 'orange-6'
    case 'Brouillon': return 'blue-grey-5'
    case 'Résilié': return 'red-6'
    case 'Complété': return 'indigo-6'
    case 'Expiré': return 'grey-5'
    default: return 'grey-5'
  }
}

const newInvoiceOpen = ref(false)

function onInvoiceCreated (doc) {
  invoices.value.unshift({
    name: doc.name,
    posting_date: doc.posting_date,
    due_date: doc.due_date,
    grand_total: doc.grand_total || 0,
    outstanding_amount: doc.outstanding_amount || doc.grand_total || 0,
    status: doc.status || 'Draft',
    is_return: doc.is_return || 0,
    return_against: doc.return_against || '',
  })
  openModal('Sales Invoice', doc.name, 'Facture ' + doc.name)
}

function confirmRefund (pe) {
  $q.dialog({
    title: 'Rembourser ce paiement ?',
    message: `${pe.name} — ${formatMoney(pe.paid_amount)} (${pe.reference_no})\nCe remboursement sera envoyé via Stripe et enregistré dans ERPNext.`,
    cancel: { flat: true, label: 'Annuler' },
    ok: { color: 'orange-8', label: 'Rembourser', flat: true },
    persistent: true,
  }).onOk(async () => {
    const res = await refundPayment(pe.name)
    if (res?.ok) {
      // Refresh payments list
      loadCustomer(props.id)
    }
  })
}

watch(() => props.id, (newId) => { if (newId) loadCustomer(newId) })
onMounted(() => loadCustomer(props.id))
</script>

<style lang="scss" scoped>
.section-title { font-weight: 700; color: var(--ops-text); display: flex; align-items: center; }
.kpi-label { font-size: 0.7rem; color: var(--ops-text-muted); text-transform: uppercase; letter-spacing: 0.05em; }
.info-grid { display: flex; flex-direction: column; gap: 6px; }
.info-row { display: flex; align-items: center; gap: 8px; font-size: 0.875rem; }
.info-label { font-size: 0.75rem; font-weight: 600; color: var(--ops-text-muted); min-width: 80px; }
.editable-row .q-field { flex: 1; min-width: 0; }
.editable-row .q-field :deep(.q-field__control) { height: 24px; min-height: 24px; }
.editable-row .q-field :deep(.q-field__native),
.editable-row .q-field :deep(.q-field__input) { padding: 0; min-height: 24px; font-size: 0.875rem; }
.editable-input { font-size: 0.875rem !important; }
.editable-row .q-field :deep(.q-field__native):hover,
.editable-row .q-field :deep(.q-field__input):hover { background: #f1f5f9; border-radius: 4px; }
.editable-row .q-field :deep(.q-field__native):focus,
.editable-row .q-field :deep(.q-field__input):focus { background: #e0f2fe; border-radius: 4px; }

.loc-card { border-left: 3px solid var(--ops-accent); }
.loc-card.loc-inactive { border-left-color: #cbd5e1; opacity: 0.7; }
.loc-card.loc-inactive:hover { opacity: 0.9; }
.loc-header { padding-bottom: 8px; border-bottom: 1px solid var(--ops-border); }
.info-block { background: #f8fafc; border-radius: 8px; padding: 10px 12px; }
.info-block-title { font-size: 0.75rem; font-weight: 700; color: var(--ops-text-muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 6px; }

.sub-row, .equip-row { padding: 6px 0; &:not(:last-child) { border-bottom: 1px solid #e2e8f0; } }
.sub-total-line { display: flex; align-items: center; justify-content: flex-end; gap: 12px; padding: 8px 0 4px; margin-top: 4px; border-top: 2px solid #334155; }
.sub-total-label { font-size: 0.85rem; font-weight: 700; text-transform: uppercase; color: #334155; }
.sub-total-amount { font-size: 1rem; font-weight: 800; color: var(--ops-accent); min-width: 100px; text-align: right; }
.sub-total-line.annual { border-top: 2px solid #92400e; .sub-total-label { color: #92400e; } .sub-total-amount { color: #92400e; } }
.sub-annual-divider { display: flex; align-items: center; gap: 6px; margin-top: 16px; padding: 8px 0 4px; border-top: 1px dashed #d97706; font-size: 0.8rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; color: #92400e; }
.sub-section-header { display: flex; align-items: center; gap: 4px; padding: 6px 0 4px; cursor: pointer; user-select: none; &:hover { background: #f8fafc; } }
.sub-section-title { font-size: 0.85rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; color: #334155; }
.sub-section-count { font-size: 0.75rem; color: #94a3b8; }
.sub-section-subtotal { margin-left: auto; font-size: 0.8rem; font-weight: 700; color: var(--ops-accent); }
.sub-ghost { opacity: 0.4; background: #e0f2fe; border-radius: 6px; }
.drag-handle:hover { color: #64748b !important; }
/* Rebate rows: nested visually under the service above via stronger indent +
   smaller type. The left border + arrow-glyph treatment mirrors what shows up
   on the invoice PDF so dispatchers can preview the print order. */
.sub-rebate {
  padding-left: 32px; border-left: 2px solid #fca5a5; margin-left: 12px;
  background: #fef2f2; border-radius: 0 6px 6px 0;
  font-size: 0.85rem;
  position: relative;
}
.sub-rebate::before {
  content: '↳'; position: absolute; left: 12px; top: 6px;
  font-size: 0.75rem; color: #ef4444; opacity: 0.7;
}
.sub-rebate .sub-sku { font-size: 0.55rem; width: 64px; min-width: 64px; max-width: 64px; }
.sub-rebate .sub-price { font-size: 0.8rem; }
.sub-cancelled { opacity: 0.5; text-decoration: line-through; }
.sub-actions { display: flex; gap: 2px; opacity: 0; transition: opacity 0.15s; }
.sub-row:hover .sub-actions { opacity: 1; }
.sub-sku { font-size: 0.6rem; font-weight: 400; color: #94a3b8; background: none; padding: 0; white-space: nowrap; display: inline-block; width: 72px; min-width: 72px; max-width: 72px; text-align: left; overflow: hidden; text-overflow: ellipsis; }
.sub-freq { font-size: 0.65rem; font-weight: 700; color: #9ca3af; &.annual { color: #92400e; background: #fef3c7; } background: #f1f5f9; border-radius: 3px; padding: 1px 4px; }
.sub-price { font-size: 0.85rem; white-space: nowrap; }
/* InlineField wraps the price in a <span>/<input>; force children to inherit
   the row-level color so `.text-red` on the display span works for rebates
   and the edit input picks up the same accent. */
.sub-price-wrap { display: inline-flex; align-items: center; }
.sub-price-wrap :deep(input) { color: inherit; text-align: right; max-width: 80px; }
.sub-rebate .sub-price-wrap, .sub-rebate .sub-price-wrap :deep(*) { color: #dc2626; }
.sub-meta { display: flex; align-items: center; gap: 2px; }
.ellipsis-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ticket-row { padding: 4px 0; &:not(:last-child) { border-bottom: 1px solid #e2e8f0; } }
.memo-row { padding: 10px 0; &:not(:last-child) { border-bottom: 1px solid var(--ops-border); } }

.notes-panel { position: sticky; top: 16px; background: #fff; border: 1px solid var(--ops-border, #e2e8f0); border-radius: 12px; display: flex; flex-direction: column; max-height: calc(100vh - 100px); }
.notes-panel-header { padding: 16px 16px 12px; border-bottom: 1px solid var(--ops-border, #e2e8f0); display: flex; align-items: center; }
.note-input-wrap { padding: 12px 14px; border-bottom: 1px solid var(--ops-border, #e2e8f0); }
.note-input-placeholder { padding: 10px 12px; background: #f8fafc; border-radius: 8px; color: #9ca3af; font-size: 0.875rem; cursor: text; }
.notes-list { overflow-y: auto; flex: 1; }
.note-item { padding: 14px 14px 12px; border-bottom: 1px solid #f1f5f9; }
.note-item:last-child { border-bottom: none; }
.note-item:hover .note-menu-btn { opacity: 1; }
.note-sticky { background: #fffbeb; border-left: 3px solid #f59e0b; }
.note-header { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 8px; }
.note-avatar { width: 32px; height: 32px; border-radius: 50%; background: #f1f5f9; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.note-menu-btn { opacity: 0; transition: opacity 0.15s; }
.note-content { font-size: 0.875rem; line-height: 1.55; color: #374151; word-break: break-word; padding-left: 42px; }

:deep(.section-header) { padding: 8px 0; min-height: unset; background: transparent; border-bottom: 1px solid var(--ops-border, #e2e8f0); }
code { background: #e2e8f0; padding: 1px 4px; border-radius: 3px; font-size: 0.8rem; }
.clickable-row { cursor: pointer; transition: background 0.1s; border-radius: 6px; padding-left: 4px; padding-right: 4px; margin-left: -4px; margin-right: -4px; &:hover { background: #eef2ff; } }
.ticket-subject-cell { white-space: normal !important; word-break: break-word; max-width: 0; width: 100%; }
.clickable-table :deep(thead th) { font-size: 11px; padding: 4px; }
.avatar-stack { display: flex; align-items: center; justify-content: center; }
.avatar-stack .avatar-chip { color: #fff; font-size: 9px; font-weight: 600; border: 1.5px solid #fff; margin-left: -8px; }
.avatar-stack .avatar-chip:first-child { margin-left: 0; }
.device-strip { display: flex; flex-wrap: wrap; gap: 6px; }
.device-icon-chip {
  display: flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 8px; cursor: pointer; transition: transform 0.15s; position: relative;
  &:hover { transform: scale(1.1); }
  &.small { width: 28px; height: 28px; border-radius: 6px; flex-shrink: 0; }
  &.dev-green { background: #dcfce7; color: #16a34a; }
  &.dev-red { background: #fee2e2; color: #dc2626; }
  &.dev-grey { background: #f1f5f9; color: #94a3b8; }
}
.acs-dot { position: absolute; top: 2px; right: 2px; width: 8px; height: 8px; border-radius: 50%; border: 1.5px solid #fff; }
.acs-online { background: #22c55e; box-shadow: 0 0 4px #22c55e88; }
.acs-offline { background: #ef4444; box-shadow: 0 0 4px #ef444488; }
.acs-probing { background: #f59e0b; animation: acs-pulse 1.2s ease-in-out infinite; }
@keyframes acs-pulse {
  0%, 100% { opacity: 1; box-shadow: 0 0 4px #f59e0b88; }
  50% { opacity: 0.3; box-shadow: 0 0 8px #f59e0bcc; }
}
.device-add-chip {
  background: #f1f5f9; color: #94a3b8; border: 2px dashed #cbd5e1;
  &:hover { border-color: #6366f1; color: #6366f1; background: #eef2ff; }
}
</style>
