<script setup>
/**
 * Review & confirm an OCR-analyzed incoming invoice (Lexoffice-style).
 * Left: PDF preview. Right: per-entity preview cards the user approves
 * individually — VENDOR (reuse existing c_bpartner or create new), optional
 * CONTACT, and the INVOICE draft (header + lines) with confidence highlighting.
 * Nothing is created until Confirm; the confirm route creates only what's ticked
 * and learns the vendor profile from the approved values.
 */
import { ref, reactive, computed, shallowRef, watch, onMounted, onBeforeUnmount } from 'vue'
import { string } from 'alga-js'
import { SelectBox } from 'tedir-select'

const { t } = useTranslation()
const props = defineProps({ id: { type: [String, Number], required: true } })

const record = ref(null)
const confidence = ref({})
const loading = ref(true)
const processing = ref(false)
const alert = ref({ status: 200, message: '' })
const learnedApplied = ref(false)   // learned defaults (currency/doctype/tax) prefilled?
const learnedSamples = ref(0)       // how many prior confirmations trained this vendor

// PDF preview — @tato30/vue-pdf is loaded DYNAMICALLY on the client only; a static
// import pulls browser-pdfjs (DOMMatrix) into SSR and crashes a page reload.
const VuePDF = shallowRef(null)
const pdf = shallowRef(null)
const pages = ref(1)
const loadPdf = async () => {
  const m = await import('@tato30/vue-pdf')
  try { await import('@tato30/vue-pdf/style.css') } catch {}
  VuePDF.value = m.VuePDF
  const r = m.usePDF(`/api/accounting/incoming-invoices/${props.id}/pdf`)
  watch(r.pdf, v => { pdf.value = v }, { immediate: true })
  watch(r.pages, v => { pages.value = v || 1 }, { immediate: true })
}
// Render the canvas at ~2× the display width so it DOWNSCALES (crisp on HiDPI/retina);
// rendering at the display width (and stretching) is what made it look blurry.
const pdfPane = ref(null)
const pdfWidth = ref(1100)
const measurePdf = () => {
  const cw = pdfPane.value?.clientWidth || 560
  const dpr = (typeof window !== 'undefined' ? window.devicePixelRatio : 1) || 1
  pdfWidth.value = Math.min(2400, Math.max(800, Math.round((cw - 24) * Math.max(2, dpr))))
}

// Overlay: boxes drawn over the PDF where each field was detected. Editable —
// pick a field, draw a box on the PDF; we read the value from that region and
// remember the corrected position so the vendor profile learns it on confirm.
const showBoxes = ref(true)
const drawFirst = ref(false)   // "region → field" mode: sketch first, then choose the field
const FIELD_LABELS = {
  documentNo: 'Rechnungs-Nr.', issueDate: 'Datum', dueDate: 'Fällig',
  grandTotal: 'Brutto', taxTotal: 'MwSt', lineTotal: 'Netto',
  'seller.name': 'Lieferant', 'seller.vatId': 'USt-IdNr.', paymentIban: 'IBAN',
  email: 'E-Mail', 'address.street': 'Straße', 'address.postal': 'PLZ', 'address.city': 'Ort'
}
const editedBoxes = reactive({})       // fieldKey -> { page, nx, ny, nw, nh, manual:true }
const pickField = ref(null)            // fieldKey currently being re-picked
const draw = reactive({ active: false, page: 0, x0: 0, y0: 0, x1: 0, y1: 0 })
let drawBounds = null
// draw-first popup: a region was sketched, show its text + field options to map to
const pendingPick = reactive({ open: false, page: 0, rect: null, text: '', loading: false })

const fieldBoxes = computed(() => {
  const bb = { ...(record.value?.bbox || {}), ...editedBoxes }   // edited overrides detected
  const conf = confidence.value || {}
  const out = []
  for (const key of Object.keys(bb)) {
    const b = bb[key]
    if (!b || b.nx == null) continue
    out.push({ key, label: FIELD_LABELS[key] || key, page: b.page || 1, nx: b.nx, ny: b.ny, nw: b.nw, nh: b.nh,
               conf: conf[key] ?? null, manual: !!editedBoxes[key] })
  }
  return out
})
const boxesForPage = (p) => fieldBoxes.value.filter(b => b.page === p)
const hasBoxes = computed(() => fieldBoxes.value.length > 0)
const boxClass = (c) => c == null ? 'c-none' : c >= 0.75 ? 'c-hi' : c >= 0.5 ? 'c-mid' : 'c-lo'
const drawStyle = computed(() => ({
  left: (Math.min(draw.x0, draw.x1) * 100) + '%', top: (Math.min(draw.y0, draw.y1) * 100) + '%',
  width: (Math.abs(draw.x1 - draw.x0) * 100) + '%', height: (Math.abs(draw.y1 - draw.y0) * 100) + '%'
}))

// field -> form setter (with light parsing)
const FIELD_TARGETS = {
  documentNo:   (v) => { form.invoice.documentNo = v },
  issueDate:    (v) => { const d = parseDateStr(v); if (d) form.invoice.dateInvoiced = d },
  dueDate:      (v) => { const d = parseDateStr(v); if (d) form.invoice.dueDate = d },
  grandTotal:   (v) => { const n = parseAmtStr(v); if (n != null) form.invoice.grandTotal = n },
  lineTotal:    (v) => { const n = parseAmtStr(v); if (n != null) form.invoice.totalLines = n },
  'seller.name':  (v) => { form.vendor.name = v },
  'seller.vatId': (v) => { form.vendor.vatId = v.replace(/\s+/g, '') },
  paymentIban:    (v) => { form.vendor.iban = v.replace(/\s+/g, '') },
  email:          (v) => { const m = String(v).match(/[^\s@]+@[^\s@]+\.[^\s@]+/); form.vendor.email = m ? m[0] : v.trim() },
  'address.street': (v) => { form.vendor.address.address1 = v; form.vendor.createLocation = true },
  'address.postal': (v) => { const m = String(v).match(/\d{4,5}/); form.vendor.address.postal = m ? m[0] : v.trim(); form.vendor.createLocation = true },
  'address.city':   (v) => { form.vendor.address.city = v.replace(/\d{4,5}/, '').trim() || v.trim(); form.vendor.createLocation = true }
}
const PICKABLE = Object.keys(FIELD_TARGETS)

const parseAmtStr = (raw) => {
  let s = String(raw || '').replace(/[^0-9.,-]/g, '')
  if (!s) return null
  if (s.includes('.') && s.includes(',')) s = s.lastIndexOf(',') > s.lastIndexOf('.') ? s.replace(/\./g, '').replace(',', '.') : s.replace(/,/g, '')
  else if (s.includes(',')) s = /,(\d{1,2})$/.test(s) ? s.replace(/\./g, '').replace(',', '.') : s.replace(/,/g, '')
  const n = Number(s); return Number.isFinite(n) ? n : null
}
const parseDateStr = (raw) => {
  const s = String(raw || '')
  let m = s.match(/(\d{4})-(\d{2})-(\d{2})/); if (m) return `${m[1]}-${m[2]}-${m[3]}`
  m = s.match(/(\d{1,2})[.\/-](\d{1,2})[.\/-](\d{2,4})/)
  if (m) { let [, d, mo, y] = m; if (y.length === 2) y = '20' + y; return `${y}-${mo.padStart(2,'0')}-${d.padStart(2,'0')}` }
  return ''
}

const startPick = (key) => { pickField.value = key; drawFirst.value = false; closePendingPick() }
const cancelPick = () => { pickField.value = null; draw.active = false }
const toggleDrawFirst = () => { drawFirst.value = !drawFirst.value; if (drawFirst.value) cancelPick(); closePendingPick() }
const closePendingPick = () => { pendingPick.open = false; pendingPick.rect = null; pendingPick.text = '' }
// draw-first: assign the sketched region + its text to the field the user picked
const assignPendingTo = (key) => {
  const r = pendingPick.rect
  if (!r) return
  editedBoxes[key] = { page: pendingPick.page, nx: r.nx, ny: r.ny, nw: r.nw, nh: r.nh, manual: true }
  if (pendingPick.text && FIELD_TARGETS[key]) FIELD_TARGETS[key](pendingPick.text)
  closePendingPick()
}

// Extract text inside a drawn region. Digital PDFs: read the text layer (instant,
// client-side). Scanned PDFs (no text layer): fall back to server region-OCR.
const extractRegionText = async (page, rect) => {
  let clientText = ''
  try {
    const doc = pdf.value
    if (doc?.getPage) {
      const pg = await doc.getPage(page)
      const vp = pg.getViewport({ scale: 1 }); const vw = vp.width, vh = vp.height
      const tc = await pg.getTextContent()
      const hits = []
      for (const it of tc.items) {
        const tr = it.transform || [1, 0, 0, 1, 0, 0]
        const h = (it.height || Math.hypot(tr[1], tr[3]))
        const nx = tr[4] / vw, ny = (vh - tr[5] - h) / vh
        const w = (it.width || 0) / vw, hn = h / vh
        const cx = nx + w / 2, cy = ny + hn / 2
        if (cx >= rect.nx && cx <= rect.nx + rect.nw && cy >= rect.ny && cy <= rect.ny + rect.nh) hits.push({ ny, nx, text: it.str })
      }
      hits.sort((a, b) => Math.abs(a.ny - b.ny) > 0.012 ? a.ny - b.ny : a.nx - b.nx)
      clientText = hits.map(h => h.text).join(' ').replace(/\s+/g, ' ').trim()
    }
  } catch {}
  if (clientText) return clientText
  // scanned page → OCR just the region on the server
  try {
    const res = await $fetch(`/api/accounting/incoming-invoices/${props.id}/ocr-region`, {
      method: 'POST', headers: useRequestHeaders(['cookie']), body: { page, rect }
    })
    return res?.text || ''
  } catch { return '' }
}

const onDown = (p, ev) => {
  if (!pickField.value && !drawFirst.value) return
  if (pendingPick.open) closePendingPick()
  drawBounds = ev.currentTarget.getBoundingClientRect()
  draw.active = true; draw.page = p
  draw.x0 = draw.x1 = (ev.clientX - drawBounds.left) / drawBounds.width
  draw.y0 = draw.y1 = (ev.clientY - drawBounds.top) / drawBounds.height
}
const onMove = (ev) => {
  if (!draw.active || !drawBounds) return
  draw.x1 = Math.max(0, Math.min(1, (ev.clientX - drawBounds.left) / drawBounds.width))
  draw.y1 = Math.max(0, Math.min(1, (ev.clientY - drawBounds.top) / drawBounds.height))
}
const onUp = async () => {
  if (!draw.active) return
  draw.active = false
  const rect = { nx: Math.min(draw.x0, draw.x1), ny: Math.min(draw.y0, draw.y1), nw: Math.abs(draw.x1 - draw.x0), nh: Math.abs(draw.y1 - draw.y0) }
  if (rect.nw < 0.008 || rect.nh < 0.006) { pickField.value = null; return }
  const key = pickField.value
  if (key) {
    // field-first: assign the region straight to the chosen field
    pickField.value = null
    editedBoxes[key] = { page: draw.page, nx: rect.nx, ny: rect.ny, nw: rect.nw, nh: rect.nh, manual: true }
    const text = await extractRegionText(draw.page, rect)
    if (text && FIELD_TARGETS[key]) FIELD_TARGETS[key](text)
  } else if (drawFirst.value) {
    // draw-first: read the text, then let the user pick which field via the popup
    pendingPick.open = true; pendingPick.page = draw.page
    pendingPick.rect = { nx: rect.nx, ny: rect.ny, nw: rect.nw, nh: rect.nh }
    pendingPick.loading = true; pendingPick.text = ''
    const text = await extractRegionText(draw.page, rect)
    pendingPick.text = text || ''
    pendingPick.loading = false
  }
}
const onEsc = (e) => { if (e.key === 'Escape') { cancelPick(); closePendingPick() } }

// --- drag-to-move + resize existing boxes (detected or manual) ---
const dragState = reactive({ active: false, mode: '', key: '', page: 0, ox: 0, oy: 0, box: null })
let dragBounds = null
const clamp01 = (v) => Math.max(0, Math.min(1, v))
const startDrag = (mode, key, p, ev) => {
  if (pickField.value) return                       // drawing a new box takes precedence
  const layer = ev.currentTarget.closest('.box-layer') || ev.currentTarget.parentElement
  dragBounds = layer.getBoundingClientRect()
  const cur = fieldBoxes.value.find(b => b.key === key)
  if (!cur) return
  Object.assign(dragState, { active: true, mode, key, page: p, ox: ev.clientX, oy: ev.clientY,
    box: { nx: cur.nx, ny: cur.ny, nw: cur.nw, nh: cur.nh } })
  window.addEventListener('mousemove', onDragMove)
  window.addEventListener('mouseup', onDragUp)
}
const onBoxDown = (key, p, ev) => startDrag('move', key, p, ev)
const onHandleDown = (key, p, ev) => startDrag('resize', key, p, ev)
const onDragMove = (ev) => {
  if (!dragState.active || !dragBounds) return
  const dx = (ev.clientX - dragState.ox) / dragBounds.width
  const dy = (ev.clientY - dragState.oy) / dragBounds.height
  const b = dragState.box
  const nb = dragState.mode === 'move'
    ? { nx: clamp01(b.nx + dx), ny: clamp01(b.ny + dy), nw: b.nw, nh: b.nh }
    : { nx: b.nx, ny: b.ny, nw: Math.max(0.01, Math.min(1 - b.nx, b.nw + dx)), nh: Math.max(0.008, Math.min(1 - b.ny, b.nh + dy)) }
  editedBoxes[dragState.key] = { page: dragState.page, ...nb, manual: true }
}
const onDragUp = async () => {
  window.removeEventListener('mousemove', onDragMove)
  window.removeEventListener('mouseup', onDragUp)
  if (!dragState.active) return
  dragState.active = false
  const key = dragState.key
  const box = editedBoxes[key]                      // only set if it actually moved
  if (box && FIELD_TARGETS[key]) {
    const text = await extractRegionText(box.page, box)
    if (text) FIELD_TARGETS[key](text)
  }
}

const form = reactive({
  vendor: { mode: 'reuse', id: '', name: '', vatId: '', iban: '', email: '', createLocation: false,
            address: { address1: '', city: '', postal: '', countryId: '' } },
  contact: { create: false, name: '', email: '', phone: '' },
  invoice: { documentNo: '', dateInvoiced: '', dateAcct: '', dueDate: '', currency: 'EUR', currencyId: '',
             docType: '', docTypeId: '', isTaxIncluded: false, poReference: '', description: '',
             grandTotal: '', totalLines: '', charge: '', chargeId: '', tax: '', taxId: '', lines: [] }
})
const vendorMatch = reactive({ remembered: null, candidates: [] })

// dropdown option loading (mirrors InvoiceForm pattern)
const getOptions = ref({})
const loadingOpt = ref({})
const setOptions = async (url, key) => {
  loadingOpt.value[key] = true
  try { const res = await $fetch('/api/' + url, { headers: useRequestHeaders(['cookie']) }); if (res) getOptions.value[key] = res }
  finally { loadingOpt.value[key] = false }
}
const apDoctypeFilter = string.urlEncode("isSOTrx eq false AND (DocBaseType eq 'API' OR DocBaseType eq 'APC')")

const confClass = (key) => {
  const c = confidence.value?.[key]
  if (c == null) return ''
  return c >= 0.75 ? 'conf-hi' : c >= 0.5 ? 'conf-mid' : 'conf-lo'
}

// Which remap-chips are already mapped (have a value or a box) vs still pending,
// so the chip row shows at a glance what's done and what needs to be assigned.
const hasVal = (v) => v !== '' && v != null
const mappedKeys = computed(() => {
  const s = new Set()
  for (const b of fieldBoxes.value) s.add(b.key)        // detected + manually drawn boxes
  const f = form
  if (hasVal(f.invoice.documentNo)) s.add('documentNo')
  if (hasVal(f.invoice.dateInvoiced)) s.add('issueDate')
  if (hasVal(f.invoice.dueDate)) s.add('dueDate')
  if (hasVal(f.invoice.grandTotal)) s.add('grandTotal')
  if (hasVal(f.invoice.totalLines)) s.add('lineTotal')
  if (hasVal(f.vendor.name)) s.add('seller.name')
  if (hasVal(f.vendor.vatId)) s.add('seller.vatId')
  if (hasVal(f.vendor.iban)) s.add('paymentIban')
  if (hasVal(f.vendor.email)) s.add('email')
  if (hasVal(f.vendor.address.address1)) s.add('address.street')
  if (hasVal(f.vendor.address.postal)) s.add('address.postal')
  if (hasVal(f.vendor.address.city)) s.add('address.city')
  return s
})
const isMapped = (key) => mappedKeys.value.has(key)
const mappedCount = computed(() => PICKABLE.filter(k => mappedKeys.value.has(k)).length)

const populate = (ex) => {
  if (!ex) return
  form.vendor.name = ex.seller?.name || ''
  form.vendor.vatId = ex.seller?.vatId || ''
  form.vendor.iban = ex.paymentIban || ''
  form.invoice.documentNo = ex.documentNo || ''
  form.invoice.dateInvoiced = ex.issueDate || ''
  form.invoice.dateAcct = ex.issueDate || ''
  form.invoice.dueDate = ex.dueDate || ''
  form.invoice.currency = ex.currency || 'EUR'
  form.invoice.isTaxIncluded = !!ex.isTaxIncluded
  form.invoice.grandTotal = ex.grandTotal ?? ''
  form.invoice.totalLines = ex.lineTotal ?? ex.taxBasisTotal ?? ''
  form.invoice.lines = Array.isArray(ex.lines) && ex.lines.length
    ? ex.lines.map(l => ({ name: l.name || '', qty: l.qty ?? 1, price: l.netPrice ?? 0, lineTotal: l.lineTotal ?? 0 }))
    : [{ name: ex.documentNo ? ('Rechnung ' + ex.documentNo) : 'Eingangsrechnung', qty: 1, price: ex.lineTotal ?? ex.grandTotal ?? 0, lineTotal: ex.lineTotal ?? ex.grandTotal ?? 0 }]

  // Learned defaults for a recognized vendor (Document type / Currency / Tax),
  // remembered from previous confirmations of the same vendor → prefilled here.
  const prof = ex._profile || {}
  if (prof.currencyId) form.invoice.currencyId = prof.currencyId
  if (prof.defaultDoctypeId) form.invoice.docTypeId = prof.defaultDoctypeId
  if (prof.defaultTaxId) form.invoice.taxId = prof.defaultTaxId
  learnedApplied.value = !!(prof.currencyId || prof.defaultDoctypeId || prof.defaultTaxId)
  learnedSamples.value = prof.samplesCount || 0
}

const runVendorMatch = async () => {
  const ex = record.value?.extracted || {}
  try {
    const q = new URLSearchParams({ vat: ex.seller?.vatId || '', iban: ex.paymentIban || '', name: ex.seller?.name || '' }).toString()
    const res = await $fetch('/api/accounting/incoming-invoices/match-vendor?' + q, { headers: useRequestHeaders(['cookie']) })
    vendorMatch.remembered = res?.remembered || null
    vendorMatch.candidates = res?.candidates || []
    const best = res?.remembered || res?.candidates?.[0]
    if (best) { form.vendor.mode = 'reuse'; form.vendor.id = best.id }
    else { form.vendor.mode = 'create' }
  } catch { form.vendor.mode = form.vendor.name ? 'create' : 'reuse' }
}

onMounted(async () => {
  measurePdf()
  window.addEventListener('resize', measurePdf)
  window.addEventListener('keydown', onEsc)
  loadPdf()
  try {
    const res = await $fetch(`/api/accounting/incoming-invoices/${props.id}`, { headers: useRequestHeaders(['cookie']) })
    if (res?.record) {
      record.value = res.record
      confidence.value = res.record.confidence || {}
      populate(res.record.extracted)
      await runVendorMatch()
    } else {
      alert.value = { status: 404, message: t.value?.not_found || 'Nicht gefunden' }
    }
  } catch (e) {
    alert.value = { status: 500, message: e?.message || 'Fehler beim Laden' }
  } finally {
    loading.value = false
  }
})

onBeforeUnmount(() => {
  if (import.meta.client) {
    window.removeEventListener('resize', measurePdf)
    window.removeEventListener('keydown', onEsc)
  }
})

const addLine = () => form.invoice.lines.push({ name: '', qty: 1, price: 0, lineTotal: 0 })
const removeLine = (i) => form.invoice.lines.splice(i, 1)
const recalcTotals = () => {
  const net = form.invoice.lines.reduce((s, l) => s + (Number(l.lineTotal) || 0), 0)
  form.invoice.totalLines = Math.round(net * 100) / 100
}

const vendorCandidateOptions = computed(() => {
  const list = [...vendorMatch.candidates]
  if (vendorMatch.remembered && !list.find(c => c.id === vendorMatch.remembered.id)) list.unshift(vendorMatch.remembered)
  return list
})

const confirm = async () => {
  if (form.vendor.mode === 'reuse' && !form.vendor.id) { alert.value = { status: 400, message: t.value?.pick_vendor || 'Bitte Lieferant wählen oder neu anlegen' }; return }
  processing.value = true
  alert.value = { status: 200, message: '' }
  const payload = {
    vendor: form.vendor.mode === 'reuse'
      ? { mode: 'reuse', id: form.vendor.id }
      : { mode: 'create', name: form.vendor.name, vatId: form.vendor.vatId, iban: form.vendor.iban, email: form.vendor.email,
          address: form.vendor.createLocation ? form.vendor.address : undefined },
    contact: form.contact.create ? { create: true, name: form.contact.name, email: form.contact.email, phone: form.contact.phone } : { create: false },
    invoice: { ...form.invoice },
    fingerprint: record.value?.vendorFingerprint || '',
    // corrected field regions (normalized boxes) → feed the per-vendor learning
    fieldBoxes: { ...editedBoxes },
    learn: true
  }
  try {
    const res = await $fetch(`/api/accounting/incoming-invoices/${props.id}/confirm`, {
      method: 'POST', headers: useRequestHeaders(['cookie']), body: payload
    })
    if (Number(res?.status) === 200 && res?.cInvoiceId) {
      navigateTo(`/procurements/invoices/${res.cInvoiceId}/edit`)
    } else {
      alert.value = { status: res?.status || 500, message: res?.message || 'Konnte nicht verbucht werden' }
      processing.value = false
    }
  } catch (e) {
    alert.value = { status: 500, message: e?.message || 'Fehler' }
    processing.value = false
  }
}

const reject = async () => {
  await $fetch(`/api/accounting/incoming-invoices/${props.id}/reject`, { method: 'DELETE', headers: useRequestHeaders(['cookie']) })
  navigateTo('/accounting/incoming-invoices')
}
</script>

<template>
  <div>
    <AlertBox :alert="alert" />
    <div v-if="loading" class="loading"><i class="ti ti-loader-2 spin"></i> {{ t.loading || 'Lädt…' }}</div>

    <div v-else class="review-grid">
      <!-- LEFT: PDF preview with editable detected-field overlay -->
      <div class="pdf-pane" ref="pdfPane">
        <div class="pdf-head">
        <div class="pdf-toolbar">
          <span class="pdf-title"><i class="ti ti-file-text"></i> {{ t.preview || 'Vorschau' }}</span>
          <div class="pdf-tools">
            <button type="button" class="mode-toggle" :class="{ on: drawFirst }" @click="toggleDrawFirst"
                    :title="t.draw_first_mode || 'Bereich → Feld'">
              <i class="ti ti-lasso"></i> {{ t.draw_first_mode || 'Bereich → Feld' }}
            </button>
            <label v-if="hasBoxes" class="boxes-toggle">
              <input type="checkbox" v-model="showBoxes" /> {{ t.show_detected_fields || 'Erkannte Felder' }}
            </label>
          </div>
        </div>

        <div class="remap-bar">
          <span class="remap-lbl">
            <i class="ti ti-click"></i> {{ t.remap_field || 'Feld zuordnen' }}
            <span class="remap-count">{{ mappedCount }}/{{ PICKABLE.length }}</span>:
          </span>
          <button v-for="k in PICKABLE" :key="k" type="button" class="chip"
                  :class="{ on: pickField === k, mapped: isMapped(k) && pickField !== k, pending: !isMapped(k) && pickField !== k }"
                  :title="isMapped(k) ? (t.mapped || 'Zugeordnet') : (t.pending_map || 'Noch nicht zugeordnet')"
                  @click="pickField === k ? cancelPick() : startPick(k)">
            <i v-if="isMapped(k) && pickField !== k" class="ti ti-check chip-ic"></i>
            <i v-else-if="pickField !== k" class="ti ti-circle-dashed chip-ic"></i>
            {{ FIELD_LABELS[k] || k }}
          </button>
        </div>

        <div v-if="pickField" class="pick-banner">
          <i class="ti ti-pointer"></i>
          {{ (t.draw_box_around || 'Rahmen um das Feld ziehen') + ': ' }}<strong>{{ FIELD_LABELS[pickField] || pickField }}</strong>
          <button class="pick-cancel" @click="cancelPick">{{ t.cancel || 'Abbrechen' }} (Esc)</button>
        </div>
        <div v-else-if="drawFirst" class="pick-banner draw1">
          <i class="ti ti-lasso"></i>
          {{ t.draw_first_hint || 'Bereich aufziehen — danach das passende Feld wählen' }}
          <button class="pick-cancel" @click="toggleDrawFirst">{{ t.cancel || 'Abbrechen' }} (Esc)</button>
        </div>
        </div>

        <ClientOnly>
          <div v-if="pdf" class="pdf-scroll">
            <div v-for="p in pages" :key="p" class="pdf-page" :class="{ picking: pickField || drawFirst }">
              <component :is="VuePDF" v-if="VuePDF" :pdf="pdf" :page="p" :width="pdfWidth" />
              <div v-if="showBoxes || pickField" class="box-layer">
                <div v-for="b in boxesForPage(p)" :key="b.key" class="fbox"
                     :class="[boxClass(b.conf), { manual: b.manual, draggable: !pickField }]"
                     :style="{ left:(b.nx*100)+'%', top:(b.ny*100)+'%', width:(b.nw*100)+'%', height:(b.nh*100)+'%' }"
                     :title="b.label" @mousedown.stop.prevent="onBoxDown(b.key, p, $event)">
                  <span class="fbox-label">{{ b.label }}<i v-if="b.manual" class="ti ti-hand-finger"></i></span>
                  <span v-if="!pickField" class="fbox-handle" @mousedown.stop.prevent="onHandleDown(b.key, p, $event)"></span>
                </div>
                <div v-if="draw.active && draw.page === p" class="draw-rect" :style="drawStyle"></div>
              </div>
              <div v-if="pickField || drawFirst" class="pick-layer"
                   @mousedown.prevent="onDown(p, $event)" @mousemove="onMove($event)" @mouseup="onUp" @mouseleave="onUp"></div>

              <!-- draw-first: highlight the sketched region + show its text and field options -->
              <template v-if="pendingPick.open && pendingPick.page === p">
                <div class="pending-rect" :style="{ left:(pendingPick.rect.nx*100)+'%', top:(pendingPick.rect.ny*100)+'%', width:(pendingPick.rect.nw*100)+'%', height:(pendingPick.rect.nh*100)+'%' }"></div>
                <div class="pick-popup" :style="{ left:(pendingPick.rect.nx*100)+'%', top:((pendingPick.rect.ny+pendingPick.rect.nh)*100)+'%' }"
                     @mousedown.stop.prevent @click.stop>
                  <div class="pp-head">
                    <span class="pp-title"><i class="ti ti-scan"></i> {{ t.detected_text || 'Erkannter Text' }}</span>
                    <button class="pp-close" @mousedown.stop @click="closePendingPick"><i class="ti ti-x"></i></button>
                  </div>
                  <div class="pp-text">
                    <i v-if="pendingPick.loading" class="ti ti-loader-2 spin"></i>
                    <span v-else>{{ pendingPick.text || ('— ' + (t.no_text_found || 'kein Text erkannt') + ' —') }}</span>
                  </div>
                  <div class="pp-flbl">{{ t.map_to_field || 'Welchem Feld zuordnen?' }}</div>
                  <div class="pp-fields">
                    <button v-for="k in PICKABLE" :key="k" type="button" class="pp-chip" :class="{ mapped: isMapped(k) }"
                            @mousedown.stop @click="assignPendingTo(k)">{{ FIELD_LABELS[k] || k }}</button>
                  </div>
                </div>
              </template>
            </div>
          </div>
          <div v-else class="pdf-empty">{{ t.loading_pdf || 'PDF wird geladen…' }}</div>
        </ClientOnly>
      </div>

      <!-- RIGHT: recommendation cards -->
      <div class="cards-pane">
        <!-- VENDOR -->
        <div class="rcard">
          <div class="rcard-h"><i class="ti ti-building-store"></i> {{ t.vendor || 'Lieferant' }}</div>
          <div class="seg">
            <button :class="['seg-btn', { on: form.vendor.mode === 'reuse' }]" @click="form.vendor.mode = 'reuse'" :disabled="!vendorCandidateOptions.length">
              <i class="ti ti-link"></i> {{ t.use_existing || 'Bestehenden verwenden' }}
            </button>
            <button :class="['seg-btn', { on: form.vendor.mode === 'create' }]" @click="form.vendor.mode = 'create'">
              <i class="ti ti-plus"></i> {{ t.create_new || 'Neu anlegen' }}
            </button>
          </div>

          <div v-if="form.vendor.mode === 'reuse'">
            <p v-if="vendorMatch.remembered" class="hint ok"><i class="ti ti-brain"></i> {{ t.remembered_vendor || 'Gelernter Lieferant aus früherer Rechnung' }}</p>
            <ClientOnly>
              <ComboBox v-model="form.vendor.id"
                :options="vendorCandidateOptions.length ? vendorCandidateOptions : [{ id: form.vendor.id, name: form.vendor.name }]"
                prop="name" dataprop="id" datatype="string" :size="5" :select="true"
                @click="setOptions('partners/partners/select?vendor=true', 'vendors')" />
            </ClientOnly>
            <p v-if="!vendorCandidateOptions.length" class="hint warn">{{ t.no_vendor_match || 'Kein passender Lieferant gefunden — bitte neu anlegen.' }}</p>
          </div>

          <div v-else class="grid2">
            <label class="lbl">{{ t.name || 'Name' }}</label>
            <input class="input" :class="confClass('seller.name')" v-model="form.vendor.name" />
            <label class="lbl">{{ t.vat_id || 'USt-IdNr.' }}</label>
            <input class="input" :class="confClass('seller.vatId')" v-model="form.vendor.vatId" />
            <label class="lbl">IBAN</label>
            <input class="input" :class="confClass('paymentIban')" v-model="form.vendor.iban" />
            <label class="lbl">E-Mail</label>
            <input class="input" v-model="form.vendor.email" />
            <label class="lbl">{{ t.create_address || 'Adresse anlegen' }}</label>
            <div><input type="checkbox" v-model="form.vendor.createLocation" /></div>
            <template v-if="form.vendor.createLocation">
              <label class="lbl">{{ t.street || 'Straße' }}</label>
              <input class="input" v-model="form.vendor.address.address1" />
              <label class="lbl">PLZ / {{ t.city || 'Ort' }}</label>
              <div class="inline2"><input class="input" v-model="form.vendor.address.postal" placeholder="PLZ" /><input class="input" v-model="form.vendor.address.city" :placeholder="t.city || 'Ort'" /></div>
            </template>
          </div>
        </div>

        <!-- CONTACT (optional) -->
        <div class="rcard">
          <div class="rcard-h">
            <label class="chk"><input type="checkbox" v-model="form.contact.create" /> <i class="ti ti-user-plus"></i> {{ t.create_contact || 'Kontakt anlegen' }}</label>
          </div>
          <div v-if="form.contact.create" class="grid2">
            <label class="lbl">{{ t.name || 'Name' }}</label><input class="input" v-model="form.contact.name" />
            <label class="lbl">E-Mail</label><input class="input" v-model="form.contact.email" />
            <label class="lbl">{{ t.phone || 'Telefon' }}</label><input class="input" v-model="form.contact.phone" />
          </div>
          <p v-else class="hint">{{ t.contact_optional_hint || 'Optional — nur anlegen, wenn ein Ansprechpartner erfasst werden soll.' }}</p>
        </div>

        <!-- INVOICE -->
        <div class="rcard">
          <div class="rcard-h"><i class="ti ti-file-invoice"></i> {{ t.invoice || 'Rechnung' }} ({{ t.draft || 'Entwurf' }})</div>
          <p class="booked-to">
            <i class="ti ti-arrow-narrow-right"></i>
            {{ t.will_be_booked_to || 'Wird verbucht auf' }}:
            <strong v-if="form.vendor.mode === 'reuse'">{{ (vendorCandidateOptions.find(v => v.id === form.vendor.id) || {}).name || ('#' + form.vendor.id) }} <span class="badge existing">{{ t.existing || 'bestehend' }}</span></strong>
            <strong v-else>{{ form.vendor.name || '—' }} <span class="badge neu">{{ t.new_label || 'NEU' }}</span></strong>
          </p>
          <p v-if="learnedApplied" class="learned-hint">
            <i class="ti ti-brain"></i>
            {{ t.learned_defaults || 'Belegart, Währung & Steuer aus früheren Rechnungen dieses Lieferanten vorbelegt' }}<span v-if="learnedSamples"> ({{ learnedSamples }})</span>
          </p>
          <p v-else class="learn-note">
            <i class="ti ti-bulb"></i>
            {{ t.will_learn_defaults || 'Belegart, Währung & Steuer unten werden beim Bestätigen für diesen Lieferanten gemerkt und bei der nächsten Rechnung automatisch vorbelegt.' }}
          </p>

          <div class="grid2">
            <label class="lbl">{{ t.invoice_no || 'Rechnungsnr.' }}</label>
            <input class="input" :class="confClass('documentNo')" v-model="form.invoice.documentNo" />
            <label class="lbl">{{ t.date || 'Datum' }}</label>
            <input class="input" type="date" :class="confClass('issueDate')" v-model="form.invoice.dateInvoiced" />
            <label class="lbl">{{ t.due_date || 'Fällig am' }}</label>
            <input class="input" type="date" :class="confClass('dueDate')" v-model="form.invoice.dueDate" />
            <label class="lbl">{{ t.doc_type || 'Belegart' }}</label>
            <ClientOnly>
              <SelectBox v-model="form.invoice.docTypeId" :options="getOptions?.['doctypes']?.records || [{ id: form.invoice.docTypeId, Name: form.invoice.docType }]"
                prop="Name" dataprop="id" datatype="string" :size="5" @click="setOptions('filters/c_doctype/' + apDoctypeFilter, 'doctypes')"
                @change="(v,o)=>{form.invoice.docType=o?.Name}" :loading="loadingOpt?.['doctypes'] ?? true" />
            </ClientOnly>
            <label class="lbl">{{ t.currency || 'Währung' }}</label>
            <ClientOnly>
              <SelectBox v-model="form.invoice.currencyId" :options="getOptions?.['currencies']?.records || [{ id: form.invoice.currencyId, ISO_Code: form.invoice.currency }]"
                prop="ISO_Code" dataprop="id" datatype="string" :size="5" @click="setOptions('settings/currencies', 'currencies')"
                @change="(v,o)=>{form.invoice.currency=o?.ISO_Code}" :loading="loadingOpt?.['currencies'] ?? true" />
            </ClientOnly>
            <label class="lbl">{{ t.tax || 'Steuer' }}</label>
            <ClientOnly>
              <SelectBox v-model="form.invoice.taxId" :options="getOptions?.['taxes']?.records || [{ id: form.invoice.taxId, Name: form.invoice.tax }]"
                prop="Name" dataprop="id" datatype="string" :size="5" @click="setOptions('models/c_tax', 'taxes')"
                @change="(v,o)=>{form.invoice.tax=o?.Name}" :loading="loadingOpt?.['taxes'] ?? true" />
            </ClientOnly>
            <label class="lbl">{{ t.tax_included || 'Brutto (inkl. MwSt)' }}</label>
            <div><input type="checkbox" v-model="form.invoice.isTaxIncluded" /></div>
          </div>

          <div class="lines">
            <div class="lines-h">
              <span>{{ t.line_items || 'Positionen' }}</span>
              <button class="button is-small is-light" @click="addLine"><i class="ti ti-plus"></i></button>
            </div>
            <div v-for="(l, i) in form.invoice.lines" :key="i" class="line-row">
              <input class="input is-small l-name" v-model="l.name" :placeholder="t.description || 'Beschreibung'" />
              <input class="input is-small l-num" type="number" v-model.number="l.qty" placeholder="Mng" />
              <input class="input is-small l-num" type="number" step="0.01" v-model.number="l.price" placeholder="Preis" />
              <input class="input is-small l-num" type="number" step="0.01" v-model.number="l.lineTotal" placeholder="Summe" @change="recalcTotals" />
              <button class="button is-small is-light" @click="removeLine(i)"><i class="ti ti-x"></i></button>
            </div>
            <div class="totals">
              <span>{{ t.net || 'Netto' }}: <b>{{ form.invoice.totalLines }}</b></span>
              <span :class="confClass('grandTotal')">{{ t.gross || 'Brutto' }}:
                <input class="input is-small l-num" type="number" step="0.01" v-model.number="form.invoice.grandTotal" /></span>
            </div>
          </div>
        </div>

        <div class="actions-bar">
          <button class="button is-danger is-light" @click="reject" :disabled="processing"><i class="ti ti-trash"></i> {{ t.reject || 'Ablehnen' }}</button>
          <button class="button is-primary" @click="confirm" :disabled="processing">
            <i class="ti" :class="processing ? 'ti-loader-2 spin' : 'ti-check'"></i>
            {{ t.confirm_create_draft || 'Bestätigen & Entwurf anlegen' }}
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
/* PDF left ~75%, form right ~25% (form has a sensible min width). */
.review-grid { display: grid; grid-template-columns: minmax(0, 3fr) minmax(360px, 1fr); gap: 20px; align-items: start; padding: 4px; }
@media (max-width: 1100px) { .review-grid { grid-template-columns: 1fr; } }
/* no top padding: the sticky head provides the top spacing and sits flush at the
   scroll-area top, so no PDF content can peek above it while scrolling */
.pdf-pane { position: sticky; top: 12px; background: var(--theme-bg-secondary,#f1f5f9); border-radius: 14px; padding: 0 14px 14px; max-height: calc(100vh - 120px); overflow: auto; }
/* keep the toolbar + remap chips visible while scrolling the PDF. Sticks flush at
   top:0 (no negative top margin → no gap), and uses a distinct (white) background
   with a border + shadow so it reads as a separate bar, not part of the PDF area. */
.pdf-head { position: sticky; top: 0; z-index: 10; background: var(--theme-bg-primary,#fff); margin: 0 -14px 10px; padding: 14px 14px 10px; border-radius: 14px 14px 0 0; border-bottom: 1px solid var(--theme-border,#e6e8ec); box-shadow: 0 6px 14px -8px rgba(0,0,0,.32); }
.pdf-toolbar { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 10px; }
.pdf-tools { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; justify-content: flex-end; }
.pdf-title { font-weight: 700; color: var(--theme-text-primary,#1f2937); display: flex; gap: 6px; align-items: center; }
.boxes-toggle { font-size: .82rem; color: var(--theme-text-secondary,#64748b); display: flex; align-items: center; gap: 6px; cursor: pointer; }
.remap-bar { display: flex; flex-wrap: wrap; align-items: center; gap: 6px; margin-bottom: 10px; }
.remap-lbl { font-size: .76rem; color: var(--theme-text-secondary,#64748b); display: flex; gap: 4px; align-items: center; }
.chip { font-size: .72rem; border: 1px solid var(--theme-border,#d1d5db); background: var(--theme-bg-primary,#fff); border-radius: 20px; padding: 3px 9px; cursor: pointer; color: var(--theme-text-secondary,#475569); display: inline-flex; align-items: center; gap: 4px; }
.chip:hover { border-color: #6366f1; color: #4338ca; }
.chip-ic { font-size: .8rem; }
/* mapped = green/active so the user sees what's already assigned */
.chip.mapped { background: #ecfdf5; border-color: #6ee7b7; color: #047857; font-weight: 600; }
.chip.mapped .chip-ic { color: #10b981; }
/* pending = dashed, muted so it's clearly "still to map" */
.chip.pending { border-style: dashed; color: var(--theme-text-secondary,#94a3b8); }
.chip.pending .chip-ic { color: #cbd5e1; }
.chip.on { background: #6366f1; border-color: #6366f1; color: #fff; font-weight: 600; }
.remap-count { font-size: .68rem; font-weight: 700; background: #eef2ff; color: #4338ca; border-radius: 10px; padding: 0 6px; margin-left: 2px; }
.pick-banner { background: #eef2ff; color: #3730a3; border: 1px solid #c7d2fe; border-radius: 8px; padding: 6px 10px; font-size: .82rem; margin-bottom: 10px; display: flex; align-items: center; gap: 8px; }
.pick-banner.draw1 { background: #ecfeff; color: #155e75; border-color: #a5f3fc; }
.pick-cancel { margin-left: auto; border: none; background: #c7d2fe; color: #312e81; border-radius: 6px; padding: 2px 8px; cursor: pointer; font-size: .74rem; }
.pick-banner.draw1 .pick-cancel { background: #a5f3fc; color: #155e75; }
/* mode toggle (field-first ↔ region-first) */
.mode-toggle { font-size: .72rem; border: 1px solid #a5f3fc; background: #ecfeff; color: #0e7490; border-radius: 20px; padding: 3px 10px; cursor: pointer; display: inline-flex; align-items: center; gap: 4px; font-weight: 600; }
.mode-toggle:hover { border-color: #22d3ee; }
.mode-toggle.on { background: #06b6d4; border-color: #06b6d4; color: #fff; }
/* draw-first: pending region + text/field popup */
.pending-rect { position: absolute; border: 2px solid #06b6d4; background: rgba(6,182,212,.10); border-radius: 3px; box-sizing: border-box; pointer-events: none; z-index: 20; }
.pick-popup { position: absolute; z-index: 30; margin-top: 6px; width: 250px; max-width: 80%; background: #fff; border: 1px solid var(--theme-border,#e5e7eb); border-radius: 10px; box-shadow: 0 12px 30px rgba(15,23,42,.22); padding: 10px; font-size: .8rem; line-height: 1.3; }
.pp-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; }
.pp-title { font-weight: 700; color: #0e7490; display: flex; align-items: center; gap: 5px; font-size: .76rem; }
.pp-close { border: none; background: transparent; cursor: pointer; color: #94a3b8; }
.pp-text { background: var(--theme-bg-secondary,#f1f5f9); border-radius: 6px; padding: 6px 8px; color: var(--theme-text-primary,#1f2937); max-height: 70px; overflow: auto; word-break: break-word; min-height: 26px; }
.pp-flbl { font-size: .7rem; color: var(--theme-text-secondary,#64748b); margin: 8px 0 4px; font-weight: 600; }
.pp-fields { display: flex; flex-wrap: wrap; gap: 4px; }
.pp-chip { font-size: .7rem; border: 1px solid var(--theme-border,#d1d5db); background: var(--theme-bg-primary,#fff); border-radius: 14px; padding: 2px 8px; cursor: pointer; color: var(--theme-text-secondary,#475569); }
.pp-chip:hover { border-color: #06b6d4; color: #0e7490; background: #ecfeff; }
.pp-chip.mapped { background: #ecfdf5; border-color: #6ee7b7; color: #047857; }
.learned-hint { background: #f5f3ff; color: #5b21b6; border: 1px solid #ddd6fe; border-radius: 8px; padding: 6px 10px; font-size: .78rem; margin-bottom: 12px; display: flex; align-items: center; gap: 6px; }
/* shown BEFORE a vendor is trained — tells the user these three fields are the
   ones learned per vendor (there's no PDF-region picker for them) */
.learn-note { background: #f8fafc; color: #475569; border: 1px dashed #cbd5e1; border-radius: 8px; padding: 6px 10px; font-size: .76rem; margin-bottom: 12px; display: flex; align-items: flex-start; gap: 6px; line-height: 1.35; }
.learn-note i { margin-top: 1px; color: #f59e0b; }
.pdf-page { position: relative; margin-bottom: 12px; line-height: 0; }
.pdf-page :deep(canvas) { width: 100% !important; height: auto !important; border-radius: 6px; box-shadow: 0 2px 10px rgba(0,0,0,.08); display: block; }
.box-layer { position: absolute; inset: 0; pointer-events: none; }
.fbox { position: absolute; border: 1.5px solid; border-radius: 3px; box-sizing: border-box; pointer-events: auto; }
.fbox.draggable { cursor: move; }
.fbox-handle { position: absolute; right: -5px; bottom: -5px; width: 11px; height: 11px; background: #6366f1; border: 2px solid #fff; border-radius: 2px; cursor: nwse-resize; box-shadow: 0 1px 3px rgba(0,0,0,.3); }
.fbox-label { position: absolute; top: 0; left: 0; transform: translateY(-100%); font-size: .6rem; font-weight: 700; line-height: 1.2; padding: 1px 5px; border-radius: 4px 4px 4px 0; white-space: nowrap; color: #fff; display: flex; align-items: center; gap: 3px; }
.fbox.c-hi { border-color: #22c55e; } .fbox.c-hi .fbox-label { background: #22c55e; }
.fbox.c-mid { border-color: #f59e0b; } .fbox.c-mid .fbox-label { background: #f59e0b; }
.fbox.c-lo { border-color: #ef4444; } .fbox.c-lo .fbox-label { background: #ef4444; }
.fbox.c-none { border-color: #94a3b8; } .fbox.c-none .fbox-label { background: #94a3b8; }
.fbox.manual { border-color: #6366f1 !important; border-style: solid; } .fbox.manual .fbox-label { background: #6366f1 !important; }
.draw-rect { position: absolute; border: 2px dashed #6366f1; background: rgba(99,102,241,.12); border-radius: 3px; box-sizing: border-box; }
.pick-layer { position: absolute; inset: 0; cursor: crosshair; background: rgba(99,102,241,.04); }
.pdf-empty { text-align: center; color: #94a3b8; padding: 40px; }
.cards-pane { display: flex; flex-direction: column; gap: 14px; }
.rcard { border: 1px solid var(--theme-border,#e5e7eb); border-radius: 12px; padding: 14px 16px; background: var(--theme-bg-primary,#fff); }
.rcard-h { font-weight: 700; display: flex; align-items: center; gap: 8px; margin-bottom: 10px; color: var(--theme-text-primary,#111); }
.seg { display: flex; gap: 8px; margin-bottom: 12px; }
.seg-btn { flex: 1; border: 1px solid var(--theme-border,#d1d5db); background: var(--theme-bg-secondary,#f8fafc); border-radius: 8px; padding: 8px; cursor: pointer; font-size: .82rem; }
.seg-btn.on { border-color: #3b82f6; background: rgba(59,130,246,.08); color: #1d4ed8; font-weight: 600; }
.seg-btn:disabled { opacity: .5; cursor: not-allowed; }
.grid2 { display: grid; grid-template-columns: 108px 1fr; gap: 8px 10px; align-items: center; }
.lbl { font-size: .8rem; color: var(--theme-text-secondary,#64748b); }
.input { width: 100%; }
.inline2 { display: flex; gap: 6px; }
.hint { font-size: .78rem; color: var(--theme-text-secondary,#64748b); margin-top: 6px; }
.hint.ok { color: #15803d; } .hint.warn { color: #b45309; }
.chk { display: flex; align-items: center; gap: 8px; cursor: pointer; }
.booked-to { background: var(--theme-bg-secondary,#f8fafc); border-radius: 8px; padding: 8px 10px; font-size: .85rem; margin-bottom: 12px; }
.badge { font-size: .68rem; padding: 1px 7px; border-radius: 10px; margin-left: 4px; }
.badge.existing { background: #dbeafe; color: #1e40af; } .badge.neu { background: #dcfce7; color: #15803d; }
.lines { margin-top: 14px; }
.lines-h { display: flex; justify-content: space-between; align-items: center; font-size: .82rem; color: var(--theme-text-secondary,#64748b); margin-bottom: 6px; }
.line-row { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 6px; }
.l-name { flex: 1 1 100%; } .l-num { width: 62px; flex: 1; }
.totals { display: flex; justify-content: flex-end; gap: 18px; font-size: .85rem; margin-top: 8px; align-items: center; }
.totals .l-num { width: 110px; }
.actions-bar { display: flex; justify-content: space-between; gap: 10px; margin-top: 4px; }
.conf-hi { border-color: #86efac !important; background: #f0fdf4; }
.conf-mid { border-color: #fde68a !important; background: #fffbeb; }
.conf-lo { border-color: #fecaca !important; background: #fef2f2; }
.loading { text-align: center; padding: 40px; color: #94a3b8; }
.spin { animation: spin 1s linear infinite; display: inline-block; }
@keyframes spin { to { transform: rotate(360deg); } }
</style>
