<!--
  TechScanPage — camera-based equipment scanner for field techs at /j/scan.

  What it does in one sentence: the tech points their phone at an ONT/router
  label, Gemini reads the serial, and we look it up in ERPNext, optionally
  auto-linking the equipment to the tech's current Dispatch Job.

  ERPNext relationships touched (see docs/VISION_AND_OCR.md §10 for the full
  data-model diagram):

    Dispatch Job ─► Customer ─► Service Location  ◄── Service Equipment
                                                       (serial_number, barcode,
                                                        mac_address, status)

    The tech arrives via `/j/scan?job=JOB-001&customer=CUST-123&location=LOC-456`
    — when the scanned serial matches an unlinked Service Equipment row, we
    auto-patch customer + service_location on that row so the device is
    provably tied to that install address before the tech leaves.

    Subscription + Issue are NOT updated here directly: they link to the
    same Customer + Service Location, so the scan result flows through
    naturally for any downstream ticket view.

  Ported from apps/field/src/pages/ScanPage.vue during the field→ops
  unification (see docs/ARCHITECTURE.md §"Legacy Retirement Plan"). Adapted
  for the ops router:
    - device-detail route name: 'tech-device' (was 'device' in field)
    - same query-param contract from TechJobDetailPage.goScan()
-->
<template>
  <q-page padding class="scan-page tech-surface">
    <!-- Job context banner — set by TechJobDetailPage via ?job=&customer=&location= -->
    <q-card v-if="jobContext" flat bordered class="q-mb-md bg-blue-1">
      <q-card-section class="q-py-sm row items-center no-wrap">
        <q-icon name="work" color="primary" class="q-mr-sm" />
        <div class="col">
          <div class="text-subtitle2">{{ jobContext.customer_name || jobContext.customer }}</div>
          <div class="text-caption text-grey" v-if="jobContext.location_name">
            <q-icon name="place" size="xs" /> {{ jobContext.location_name }}
          </div>
        </div>
        <q-btn flat dense size="sm" icon="close" @click="jobContext = null" />
      </q-card-section>
      <q-card-section class="q-pt-none q-pb-sm text-caption text-blue-grey">
        Les équipements scannés seront automatiquement liés à ce client et cette adresse.
      </q-card-section>
    </q-card>

    <!-- Camera capture button -->
    <div class="text-center">
      <q-btn
        color="primary" icon="photo_camera" label="Scanner"
        size="lg" rounded unelevated
        @click="takePhoto"
        :loading="scanner.scanning.value"
        class="q-px-xl"
      />
      <input ref="cameraInput" type="file" accept="image/*" capture="environment" class="hidden" @change="onPhoto" />
    </div>

    <!-- Pending scan indicator (signal faible → queue offline) -->
    <div v-if="offline.pendingVisionCount > 0" class="text-center q-mt-sm">
      <q-chip icon="wifi_tethering_off" color="orange" text-color="white" dense clickable @click="offline.syncVisionQueue()">
        {{ offline.pendingVisionCount }} scan{{ offline.pendingVisionCount > 1 ? 's' : '' }} en attente · toucher pour réessayer
      </q-chip>
    </div>

    <!-- Last captured photo (thumbnail) -->
    <div v-if="scanner.lastPhoto.value" class="photo-preview q-mt-md">
      <img :src="scanner.lastPhoto.value" class="preview-img" @click="showFullPhoto = true" />
      <div v-if="scanner.scanning.value" class="preview-overlay">
        <q-spinner-dots size="32px" color="white" />
        <div class="text-white text-caption q-mt-xs">Analyse Gemini...</div>
      </div>
    </div>

    <!-- Error / status -->
    <div v-if="scanner.error.value" class="text-caption text-center q-mt-sm text-negative">
      {{ scanner.error.value }}
    </div>

    <!-- Manual entry fallback -->
    <q-input v-model="manualCode" label="Saisie manuelle SN / MAC" outlined dense class="q-mt-md"
      @keyup.enter="addManual">
      <template v-slot:append>
        <q-btn flat dense icon="add" @click="addManual" :disable="!manualCode.trim()" />
      </template>
    </q-input>

    <!-- Scanned barcodes — max 5 (see useScanner.MAX_BARCODES) -->
    <div v-if="scanner.barcodes.value.length > 0" class="q-mt-md">
      <div class="text-subtitle2 q-mb-xs">Codes détectés ({{ scanner.barcodes.value.length }})</div>
      <q-card v-for="bc in scanner.barcodes.value" :key="bc.value" class="q-mb-sm">
        <q-card-section class="q-py-sm row items-center no-wrap">
          <q-icon name="qr_code" class="q-mr-sm" color="primary" />
          <div class="col">
            <div class="text-subtitle2 mono">{{ bc.value }}</div>
          </div>
          <q-btn flat dense icon="search" @click="lookupDevice(bc.value)" :loading="lookingUp === bc.value" />
          <q-btn flat dense icon="close" color="negative" @click="scanner.removeBarcode(bc.value)" />
        </q-card-section>

        <!-- Lookup result -->
        <q-card-section v-if="lookupResults[bc.value]" class="q-pt-none">
          <div v-if="lookupResults[bc.value].found">
            <q-badge color="green" label="Trouvé" class="q-mb-xs" />
            <div class="text-caption">
              {{ lookupResults[bc.value].equipment.equipment_type }} —
              {{ lookupResults[bc.value].equipment.brand }} {{ lookupResults[bc.value].equipment.model }}
            </div>
            <div class="text-caption">
              Client: {{ lookupResults[bc.value].equipment.customer_name || lookupResults[bc.value].equipment.customer || 'Aucun' }}
            </div>
            <div v-if="!lookupResults[bc.value].equipment.service_location && !jobContext" class="q-mt-xs">
              <q-btn flat dense size="sm" color="orange" label="Lier à un service" icon="link"
                @click="openLinkDialog(bc.value, lookupResults[bc.value].equipment)" />
            </div>
            <div v-else-if="lookupResults[bc.value].equipment.service_location" class="text-caption text-green q-mt-xs">
              <q-icon name="check_circle" size="xs" class="q-mr-xs" />
              {{ lookupResults[bc.value].equipment.service_location }}
            </div>
            <q-btn flat dense size="sm" label="Détails" icon="open_in_new" class="q-mt-xs"
              @click="$router.push({ name: 'tech-device', params: { serial: lookupResults[bc.value].equipment.serial_number || bc.value } })" />
          </div>
          <div v-else>
            <q-badge color="orange" label="Non trouvé" class="q-mb-xs" />
            <q-btn flat dense size="sm" color="primary" label="Créer équipement" icon="add"
              @click="openCreateDialog(bc.value)" />
          </div>
        </q-card-section>
      </q-card>
    </div>

    <!-- Photo history (small thumbnails) -->
    <div v-if="scanner.photos.value.length > 1" class="q-mt-md">
      <div class="text-caption text-grey q-mb-xs">Photos capturées</div>
      <div class="row q-gutter-xs">
        <div v-for="(p, i) in scanner.photos.value" :key="i" class="photo-thumb" @click="viewPhoto(p)">
          <img :src="p.url" />
          <q-badge v-if="p.codes.length" color="green" floating :label="p.codes.length" />
        </div>
      </div>
    </div>

    <!-- Link all to account (manual, when no job context) -->
    <div v-if="scanner.barcodes.value.length > 0 && !jobContext && hasUnlinked" class="q-mt-sm">
      <q-btn color="orange" icon="link" label="Lier les équipements à un service"
        @click="openLinkDialogForAll" outline class="full-width" />
    </div>

    <!-- Full photo viewer -->
    <q-dialog v-model="showFullPhoto" maximized>
      <q-card class="bg-black column">
        <q-card-section class="col-auto row items-center">
          <div class="text-white text-subtitle2 col">Photo</div>
          <q-btn flat round icon="close" color="white" v-close-popup />
        </q-card-section>
        <q-card-section class="col column items-center justify-center">
          <img :src="fullPhotoUrl" style="max-width:100%; max-height:80vh; object-fit:contain" />
        </q-card-section>
      </q-card>
    </q-dialog>

    <!-- Create equipment dialog -->
    <q-dialog v-model="createDialog">
      <q-card style="min-width: 320px">
        <q-card-section>
          <div class="text-h6">Nouvel équipement</div>
        </q-card-section>
        <q-card-section>
          <q-input v-model="newEquip.serial_number" label="Numéro de série" outlined dense readonly class="q-mb-sm" />
          <q-select v-model="newEquip.equipment_type" :options="eqTypes" label="Type" outlined dense class="q-mb-sm" />
          <q-input v-model="newEquip.brand" label="Marque" outlined dense class="q-mb-sm" />
          <q-input v-model="newEquip.model" label="Modèle" outlined dense class="q-mb-sm" />
          <q-input v-model="newEquip.mac_address" label="MAC (optionnel)" outlined dense class="q-mb-sm" />
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat label="Annuler" v-close-popup />
          <q-btn color="primary" label="Créer" @click="createEquipment" :loading="creating" />
        </q-card-actions>
      </q-card>
    </q-dialog>

    <!-- Link device to service dialog (no job context path) -->
    <q-dialog v-model="linkDialog">
      <q-card style="min-width: 340px">
        <q-card-section>
          <div class="text-h6">Lier à un service</div>
          <div class="text-caption text-grey mono">{{ linkTarget?.serial_number }}</div>
        </q-card-section>
        <q-card-section>
          <q-input v-model="linkSearch" label="Rechercher client" outlined dense class="q-mb-sm"
            @update:model-value="searchCustomers" debounce="400">
            <template v-slot:append><q-icon name="search" /></template>
          </q-input>
          <q-list v-if="customerResults.length" bordered separator class="q-mb-sm" style="max-height: 150px; overflow-y: auto">
            <q-item v-for="c in customerResults" :key="c.name" clickable @click="selectCustomer(c)">
              <q-item-section>
                <q-item-label>{{ c.customer_name || c.name }}</q-item-label>
                <q-item-label caption>{{ c.name }}</q-item-label>
              </q-item-section>
            </q-item>
          </q-list>
          <div v-if="selectedCustomer">
            <div class="text-subtitle2 q-mb-xs">{{ selectedCustomer.customer_name || selectedCustomer.name }}</div>
            <div v-if="loadingLocations" class="text-center q-py-sm"><q-spinner size="sm" /></div>
            <q-list v-else-if="serviceLocations.length" bordered separator>
              <q-item v-for="loc in serviceLocations" :key="loc.name" clickable
                :class="{ 'bg-blue-1': selectedLocation?.name === loc.name }"
                @click="selectedLocation = loc">
                <q-item-section>
                  <q-item-label>{{ loc.location_name || loc.name }}</q-item-label>
                  <q-item-label caption>{{ loc.address_line }} {{ loc.city }}</q-item-label>
                </q-item-section>
                <q-item-section side v-if="selectedLocation?.name === loc.name">
                  <q-icon name="check_circle" color="primary" />
                </q-item-section>
              </q-item>
            </q-list>
            <div v-else class="text-caption text-grey">Aucune adresse de service</div>
          </div>
        </q-card-section>
        <q-card-actions align="right">
          <q-btn flat label="Annuler" v-close-popup />
          <q-btn color="primary" label="Lier" :disable="!selectedCustomer || !selectedLocation"
            @click="linkDeviceToService" :loading="linkingSingle" />
        </q-card-actions>
      </q-card>
    </q-dialog>
  </q-page>
</template>

<script setup>
import { ref, computed } from 'vue'
import { useRoute } from 'vue-router'
import { useScanner } from 'src/composables/useScanner'
import { listDocs, createDoc, updateDoc } from 'src/api/erp'
import { useOfflineStore } from 'src/stores/offline'
import { Notify } from 'quasar'

const route = useRoute()
const offline = useOfflineStore()
// Each new code triggers both a toast AND a silent ERPNext lookup. The
// callback fires for synchronous scans AND for scans that complete later
// from the offline vision queue — the tech gets notified either way.
const scanner = useScanner({
  onNewCode: (code) => {
    Notify.create({ type: 'positive', message: code, timeout: 2500, icon: 'qr_code', color: 'deep-purple' })
    lookupDevice(code)
  },
})

const cameraInput = ref(null)
const manualCode = ref('')
const lookingUp = ref(null)
const lookupResults = ref({})
const createDialog = ref(false)
const creating = ref(false)

// Photo viewer
const showFullPhoto = ref(false)
const fullPhotoUrl = ref('')

// Link dialog
const linkDialog = ref(false)
const linkTarget = ref(null)
const linkTargetBarcode = ref('')
const linkSearch = ref('')
const customerResults = ref([])
const selectedCustomer = ref(null)
const serviceLocations = ref([])
const selectedLocation = ref(null)
const loadingLocations = ref(false)
const linkingSingle = ref(false)

const eqTypes = ['ONT', 'Modem', 'Routeur', 'Décodeur TV', 'VoIP', 'Amplificateur', 'Splitter', 'Câble', 'Autre']

// jobContext is passed through query params by TechJobDetailPage.goScan().
// If absent, the page still works — the tech has to manually pick a
// customer + service_location before any link happens.
const jobContext = ref(route.query.job ? {
  job: route.query.job,
  customer: route.query.customer,
  customer_name: route.query.customer_name,
  location: route.query.location,
  location_name: route.query.location_name,
} : null)

const newEquip = ref({ serial_number: '', equipment_type: 'ONT', brand: '', model: '', mac_address: '' })

const hasUnlinked = computed(() =>
  scanner.barcodes.value.some(bc => {
    const r = lookupResults.value[bc.value]
    return r?.found && !r.equipment.service_location
  })
)

// ─── Camera capture ──────────────────────────────────────────────────

function takePhoto () {
  // Reset so the same file re-triggers change when tech scans, undoes, scans
  if (cameraInput.value) cameraInput.value.value = ''
  cameraInput.value?.click()
}

async function onPhoto (e) {
  const file = e.target.files?.[0]
  if (!file) return
  // The scanner's onNewCode callback handles toasts + lookups, for both
  // synchronous and queued scans. We just await to make scanning.value work.
  await scanner.processPhoto(file)
}

function viewPhoto (photo) {
  fullPhotoUrl.value = photo.url
  showFullPhoto.value = true
}

// ─── Manual entry ────────────────────────────────────────────────────

function addManual () {
  const code = manualCode.value.trim()
  if (!code) return
  if (!scanner.barcodes.value.find(b => b.value === code)) {
    scanner.barcodes.value.push({ value: code, region: 'manuel' })
    lookupDevice(code)
  }
  manualCode.value = ''
}

// ─── Device lookup: 3-tier fallback ──────────────────────────────────
// 1. Exact serial_number match  (how 95% of scans resolve)
// 2. Barcode field match        (for legacy equipment imported with only
//                                the barcode sticker, no manufacturer SN)
// 3. MAC-suffix match           (when Gemini reads the MAC off the label
//                                instead of the SN — last 6 chars are
//                                unique within our installed base)

async function lookupDevice (serial) {
  lookingUp.value = serial
  try {
    const results = await listDocs('Service Equipment', {
      filters: { serial_number: serial },
      fields: ['name', 'serial_number', 'equipment_type', 'brand', 'model', 'customer', 'customer_name',
        'service_location', 'status', 'mac_address'],
      limit: 1,
    })
    if (results.length > 0) {
      lookupResults.value[serial] = { found: true, equipment: results[0] }
    } else {
      const byBarcode = await listDocs('Service Equipment', {
        filters: { barcode: serial },
        fields: ['name', 'serial_number', 'equipment_type', 'brand', 'model', 'customer', 'customer_name',
          'service_location', 'status', 'mac_address'],
        limit: 1,
      })
      if (byBarcode.length > 0) {
        lookupResults.value[serial] = { found: true, equipment: byBarcode[0] }
      } else {
        const normalized = serial.replace(/[:\-.]/g, '').toUpperCase()
        if (normalized.length === 12 && /^[A-F0-9]+$/.test(normalized)) {
          const byMac = await listDocs('Service Equipment', {
            filters: { mac_address: ['like', `%${normalized.slice(-6)}%`] },
            fields: ['name', 'serial_number', 'equipment_type', 'brand', 'model', 'customer', 'customer_name',
              'service_location', 'status', 'mac_address'],
            limit: 1,
          })
          if (byMac.length > 0) {
            lookupResults.value[serial] = { found: true, equipment: byMac[0] }
          } else {
            lookupResults.value[serial] = { found: false }
          }
        } else {
          lookupResults.value[serial] = { found: false }
        }
      }
    }
  } catch {
    lookupResults.value[serial] = { found: false }
  } finally {
    lookingUp.value = null
  }

  // Auto-link path: device exists but has no service_location, and the
  // tech opened the page from a Dispatch Job → patch customer + location
  // so the device is provably tied to this install address.
  const result = lookupResults.value[serial]
  if (result?.found && jobContext.value?.customer && !result.equipment.service_location) {
    await autoLinkToJob(serial, result.equipment)
  }
}

// ─── Auto-link device to job context ─────────────────────────────────

async function autoLinkToJob (serial, equipment) {
  if (!jobContext.value?.customer) return
  const updates = { customer: jobContext.value.customer }
  if (jobContext.value.location) updates.service_location = jobContext.value.location
  try {
    await updateDoc('Service Equipment', equipment.name, updates)
    equipment.customer = jobContext.value.customer
    equipment.customer_name = jobContext.value.customer_name
    if (jobContext.value.location) equipment.service_location = jobContext.value.location
    if (lookupResults.value[serial]) {
      lookupResults.value[serial].equipment = { ...equipment }
    }
    Notify.create({
      type: 'positive',
      message: 'Lié à ' + (jobContext.value.customer_name || jobContext.value.customer),
      caption: jobContext.value.location_name || undefined,
      icon: 'link',
    })
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur liaison: ' + e.message })
  }
}

// ─── Create equipment (offline-aware) ────────────────────────────────

function openCreateDialog (serial) {
  newEquip.value = { serial_number: serial, equipment_type: 'ONT', brand: '', model: '', mac_address: '' }
  createDialog.value = true
}

async function createEquipment () {
  creating.value = true
  const data = {
    ...newEquip.value,
    status: 'Actif',
    customer: jobContext.value?.customer || '',
    service_location: jobContext.value?.location || '',
  }
  try {
    if (offline.online) {
      const doc = await createDoc('Service Equipment', data)
      lookupResults.value[data.serial_number] = { found: true, equipment: doc }
      Notify.create({ type: 'positive', message: 'Équipement créé' })
    } else {
      // No connectivity → queue the mutation for later replay
      await offline.enqueue({ type: 'create', doctype: 'Service Equipment', data })
      Notify.create({ type: 'info', message: 'Sauvegardé hors ligne' })
    }
    createDialog.value = false
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
  } finally {
    creating.value = false
  }
}

// ─── Manual link flow (no job context) ───────────────────────────────

function openLinkDialogForAll () {
  for (const bc of scanner.barcodes.value) {
    const r = lookupResults.value[bc.value]
    if (r?.found && !r.equipment.service_location) {
      openLinkDialog(bc.value, r.equipment)
      return
    }
  }
}

function openLinkDialog (barcode, equipment) {
  linkTarget.value = equipment
  linkTargetBarcode.value = barcode
  linkSearch.value = ''
  customerResults.value = []
  selectedCustomer.value = null
  serviceLocations.value = []
  selectedLocation.value = null
  // Pre-select the customer if the device already has one, so the tech
  // only has to pick the address.
  if (equipment.customer) {
    selectedCustomer.value = { name: equipment.customer, customer_name: equipment.customer_name }
    loadServiceLocations(equipment.customer)
  }
  linkDialog.value = true
}

async function searchCustomers (text) {
  if (!text || text.length < 2) { customerResults.value = []; return }
  try {
    customerResults.value = await listDocs('Customer', {
      filters: { customer_name: ['like', `%${text}%`] },
      fields: ['name', 'customer_name'],
      limit: 10,
    })
  } catch { customerResults.value = [] }
}

async function selectCustomer (customer) {
  selectedCustomer.value = customer
  customerResults.value = []
  linkSearch.value = ''
  selectedLocation.value = null
  await loadServiceLocations(customer.name)
}

async function loadServiceLocations (customerId) {
  loadingLocations.value = true
  try {
    serviceLocations.value = await listDocs('Service Location', {
      filters: { customer: customerId },
      fields: ['name', 'location_name', 'address_line', 'city', 'connection_type'],
      limit: 50,
    })
  } catch { serviceLocations.value = [] }
  finally { loadingLocations.value = false }
}

async function linkDeviceToService () {
  if (!linkTarget.value || !selectedCustomer.value || !selectedLocation.value) return
  linkingSingle.value = true
  try {
    await updateDoc('Service Equipment', linkTarget.value.name, {
      customer: selectedCustomer.value.name,
      service_location: selectedLocation.value.name,
    })
    linkTarget.value.customer = selectedCustomer.value.name
    linkTarget.value.customer_name = selectedCustomer.value.customer_name
    linkTarget.value.service_location = selectedLocation.value.name
    if (lookupResults.value[linkTargetBarcode.value]) {
      lookupResults.value[linkTargetBarcode.value].equipment = { ...linkTarget.value }
    }
    Notify.create({ type: 'positive', message: 'Lié à ' + (selectedLocation.value.location_name || selectedLocation.value.name) })
    linkDialog.value = false
  } catch (e) {
    Notify.create({ type: 'negative', message: 'Erreur: ' + e.message })
  } finally {
    linkingSingle.value = false
  }
}
</script>

<style lang="scss" scoped>
.scan-page {
  padding-bottom: 80px !important;  /* TechLayout has a bottom tab bar */
}

.photo-preview {
  position: relative;
  text-align: center;
}

.preview-img {
  max-width: 100%;
  max-height: 250px;
  border-radius: 12px;
  cursor: pointer;
}

.preview-overlay {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 12px;
}

.photo-thumb {
  position: relative;
  width: 60px;
  height: 60px;
  border-radius: 8px;
  overflow: hidden;
  cursor: pointer;
  img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
}

.mono {
  font-family: monospace;
}
</style>
