<script setup>
import { array, string } from 'alga-js'
import { SelectBox } from 'tedir-select'

const route = useRoute()
const config = useRuntimeConfig()
const { t } = useTranslation()
const organizationId = useCookie('logship_organization_id')
const warehouseId = useCookie('logship_warehouse_id')

// Limited role (merchant) flag — used to hide the per-line detail toggle
// since the merchant isn't meant to see the full field grid for a line.
const { getMenuType } = useRoleMenu()
const isLimitedRole = computed(() => getMenuType() === 'c')

const props = defineProps({
  modelValue: Array,
  action: String,
  info: Object
})

const emit = defineEmits(['update:modelValue', 'save'])

const entries = ref(props.modelValue || [])
watch(() => props.modelValue, () => {
  entries.value = props.modelValue
})

const editable = computed(() => props.action !== 'view')
const locked = computed(() => props.info?.docStatusId === 'CO')
const readonly = computed(() => !editable.value || locked.value)

const labels = computed(() => ({
  pos: 'Pos.',
  product: t.value?.product || 'Artikel',
  quantity: t.value?.quantity || 'Menge',
  price: t.value?.price || 'Preis',
  amount: t.value?.amount || 'Betrag'
}))

const fmtMoney = new Intl.NumberFormat('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
const curSymbol = (c) => {
  const cur = String(c || '').toUpperCase()
  return { EUR: '€', USD: '$', GBP: '£' }[cur] || (c || '€')
}
const displayCurrency = computed(() => curSymbol(entries.value?.[0]?.currency || props.info?.currency))
const netTotal = computed(() => entries.value.reduce((acc, e) => (e.isActive === false ? acc : acc + (Number(e.lineNetAmt) || 0)), 0))

const lineNumber = computed(() => {
  let lineNum = 1
  if(Number(entries.value.length || 0) === 1) {
    lineNum = 2
  } else if(Number(entries.value.length || 0) >= 2) {
    lineNum = Number(array.math(entries.value, 'max', 'line')) + 1
  }
  return lineNum
})

// Index of the most recently added line — its product search gets autofocus so
// "Add position" drops the user straight into the article search.
const focusIndex = ref(-1)

const addLine = () => {
  entries.value.push({
    id: '',
    client: props.info?.client || '',
    clientId: props.info?.clientId || '',
    organization: props.info?.organization || '',
    organizationId: props.info?.organizationId || organizationId.value || '',
    warehouse: props.info?.warehouse || '',
    warehouseId: props.info?.warehouseId || '',
    order: props.info?.documentNo || '',
    orderId: props.info?.id || '',
    currency: props.info?.currency || '',
    currencyId: props.info?.currencyId || '',
    isActive: true,
    line: lineNumber.value,
    product: '',
    productId: '',
    gallery: {},
    galleries: [],
    qtyEntered: 1,
    qtyOrdered: 1,
    qtyReserved: 0.0,
    qtyDelivered: 0.0,
    qtyInvoiced: 0.0,
    uom: 'Each',
    uomId: '100',
    priceActual: 0,
    priceEntered: 0,
    priceList: 0,
    tax: '',
    taxId: '',
    discount: 0,
    lineNetAmt: 0,
    attributeSetInstance: '',
    attributeSetInstanceId: '',
    charge: '',
    chargeId: '',
    project: '',
    projectId: '',
    campaign: '',
    campaignId: '',
    printLabelCode: '',
    qtyavailable: 0,
    qtyonhand: 0,
    qtyreserved: 0,
    qtypurchased: 0,
    show: false,
    alert: {
      status: '',
      message: '',
      processing: false,
    }
  })
  focusIndex.value = entries.value.length - 1
  save()
}

// Reset lines + product caches when the order context that scopes the article
// search changes (org / warehouse / pricelist / currency) — NOT on every deep
// info mutation, so unrelated form edits don't wipe already-entered lines.
watch(() => [props.info?.organizationId, props.info?.warehouseId, props.info?.priceListId, props.info?.currencyId, props.info?.id], () => {
  entries.value = []
  products.value = {}
  filtering.value = {}
  focusIndex.value = -1
  save()
})

const loading = ref({})
const filtering = ref({})
const products = ref({})
const getOptions = ref({})
const filterProductGallery = (galleries, strapiProduct) => {
  let newGallery = galleries?.[0] ?? {}
  if(strapiProduct?.Main_Image && strapiProduct?.Main_Image != 0) {
    newGallery = galleries.filter(i => i.id == strapiProduct.Main_Image)?.[0] ?? {}
  }
  return newGallery
}
const setOptions = async (url, key) => {
  loading.value[key] = true
  const res = await $fetch('/api/'+url, {
    headers: useRequestHeaders(['cookie'])
  })
  if(res) {
    getOptions.value[key] = res
  }
  loading.value[key] = false
}

const filterOptions = async (model, key, filter = '') => {
  if(filter) {
    await setOptions('filters/'+model+'/'+filter, key)
    filtering.value[key] = filter
  } else {
    if(!filtering.value?.[key] || filtering.value?.[key] === '') {
      await setOptions('models/'+model, key)
    }
  }
}

// BOM / JTL-BOM articles are hidden from the position picker (search + ProductChooser),
// UNLESS the article has M_Product.isEnabledBom = 'Y' — the server-side excludeBom
// filter (materials/products/filter.get.ts) exempts those so they stay searchable/addable.
// TODO(org-setting): planned per-organization checkbox will decide whether BOM articles
// may be added to orders — resolve that org flag here instead of the hardcoded `true`.
const excludeBom = true

// Product fetches get their own path (instead of setOptions) so out-of-order
// search responses can't overwrite newer results, and galleries load lazily.
const productReqSeq = {}
const fetchProducts = async (key, url) => {
  const seq = (productReqSeq[key] = (productReqSeq[key] || 0) + 1)
  loading.value[key] = true
  try {
    const res = await $fetch('/api/'+url, {
      headers: useRequestHeaders(['cookie'])
    })
    if(seq !== productReqSeq[key]) return
    getOptions.value[key] = res
    const records = res?.records || []
    products.value[key] = records
    // Load galleries lazily in background — don't block the dropdown
    for(const item of records) {
      if(item?.Strapi_Product_documentId) {
        $fetch(`/api/materials/products/${item.Strapi_Product_documentId}/galleries`).then((resp) => {
          if(resp?.data) {
            item.galleries = resp.data?.image ?? []
            item.gallery = filterProductGallery(item.galleries, resp.data)
            // Trigger reactivity update
            products.value[key] = [...(products.value[key] || [])]
          }
        }).catch(() => {})
      }
    }
  } finally {
    if(seq === productReqSeq[key]) loading.value[key] = false
  }
}

const priceListVersionCache = {}
const filterProducts = async (key, filter = '') => {
  const newOrganizationId = isNaN(props.info?.organizationId) === false ? props.info?.organizationId : organizationId.value
  const newWarehouseId = props.info?.warehouseId || warehouseId.value
  const searchTerm = filter ? filter.toLowerCase() : ''

  // Same query already loaded → reuse (also covers reopening the dropdown)
  if((filtering.value?.[key] ?? null) === searchTerm && products.value?.[key] !== undefined) return
  filtering.value[key] = searchTerm
  loading.value[key] = true

  let params = `m_warehouse_id=eq.${newWarehouseId}&org_id=${newOrganizationId}${excludeBom ? '&excludeBom=true' : ''}`
  if(props.info?.priceListId) {
    let versionId = priceListVersionCache[props.info.priceListId]
    if(!versionId) {
      const res = await $fetch(`/api/materials/products/price/version/${props.info.priceListId}`)
      versionId = res?.records?.[0]?.id
      if(versionId) priceListVersionCache[props.info.priceListId] = versionId
    }
    if(versionId) params += `&m_pricelist_version_id=eq.${versionId}`
  }
  if(searchTerm) params += `&search=${searchTerm}`

  await fetchProducts(key, `materials/products/filter?${params}`)
}

const retrieve = async () => {
  // Only meaningful when the table edits an existing order — in the quick-add
  // flows (action === 'create') route.params.id belongs to an unrelated page.
  if(route.params?.id && props.action !== 'create') {
    const res = await $fetch('/api/orders/'+route.params.id+'/order-line', {
      headers: useRequestHeaders(['cookie'])
    })
    if(res) {
      entries.value = res.records.map(item => {
        return {
          id: item?.id || '',
          client: item?.AD_Client_ID?.identifier || '',
          clientId: item?.AD_Client_ID?.id || '',
          organization: item?.AD_Org_ID?.identifier || '',
          organizationId: item?.AD_Org_ID?.id || '',
          warehouse: item?.M_Warehouse_ID?.identifier || '',
          warehouseId: item?.M_Warehouse_ID?.id || '',
          order: item?.C_Order_ID?.identifier || '',
          orderId: item?.C_Order_ID?.id || '',
          isActive: item?.IsActive ?? true,
          line: item?.Line || '',
          product: item?.M_Product_ID?.identifier || item?.M_Product_ID?.Name || '',
          productId: item?.M_Product_ID?.id || '',
          galleries: [],
          qtyEntered: item?.QtyEntered || 1,
          qtyOrdered: item?.QtyOrdered || 1,
          qtyReserved: item?.QtyReserved || 0.0,
          qtyDelivered: item?.QtyDelivered || 0.0,
          qtyInvoiced: item?.QtyInvoiced || 0.0,
          uom: item?.C_UOM_ID?.identifier || '',
          uomId: item?.C_UOM_ID?.id || '',
          priceActual: item?.PriceActual || 0,
          priceEntered: item?.PriceEntered || 0,
          priceList: item?.PriceList || 0,
          tax: item?.C_Tax_ID?.identifier || '',
          taxId: item?.C_Tax_ID?.id || '',
          discount: item?.Discount || 0,
          lineNetAmt: item?.LineNetAmt || 0,
          currency: item?.C_Currency_ID?.identifier || '',
          currencyId: item?.C_Currency_ID?.id || '',
          attributeSetInstance: item?.M_AttributeSetInstance_ID?.identifier || '',
          attributeSetInstanceId: item?.M_AttributeSetInstance_ID?.id || '',
          charge: item?.C_Charge_ID?.identifier || '',
          chargeId: item?.C_Charge_ID?.id || '',
          project: item?.C_Project_ID?.identifier || '',
          projectId: item?.C_Project_ID?.id || '',
          campaign: item?.C_Campaign_ID?.identifier || '',
          campaignId: item?.C_Campaign_ID?.id || '',
          printLabelCode: item?.printLabelCode ?? item?.PrintLabelCode ?? '',
          qtyavailable: 0,
          qtyonhand: 0,
          qtyreserved: 0,
          qtypurchased: 0,
          show: false,
          alert: {
            status: '',
            message: '',
            processing: false,
          }
        }
      }).sort((a, b) => Number(a.line) - Number(b.line))

      const newEntries = []
      for(let item of entries.value) {
        const response = await $fetch(`/api/materials/products/${item.productId}`)
        if(response?.Strapi_Product_documentId) {
          const resp = await $fetch(`/api/materials/products/${response.Strapi_Product_documentId}/galleries`)
          if(resp?.data) {
            item.galleries = resp.data?.image ?? []
            item.gallery = filterProductGallery(item.galleries, resp.data)
          }
        }
        newEntries.push(item)
      }
      entries.value = newEntries
    }
  }
}
onMounted(retrieve)

const destroy = async (entry, index) => {
  entries.value.splice(index, 1)
  // Rows are keyed by index — a splice remounts later rows, which must not
  // re-trigger the autofocus of a previously added line.
  focusIndex.value = -1
  save()

  if(entry.id) {
    await $fetch('/api/orders/'+entry.id+'/order-line', {
      method: 'DELETE',
      headers: useRequestHeaders(['cookie'])
    })
  }
}

const round2 = (n) => Math.round((Number(n) || 0) * 100) / 100
const recalc = (entry, syncOrdered = false) => {
  entry.lineNetAmt = round2(Number(entry.qtyEntered) * Number(entry.priceEntered))
  if(syncOrdered) entry.qtyOrdered = entry.qtyEntered
  save()
}

const chooseLineIndex = ref(NaN)
const chooseLineEntry = ref(null)
const showProductChooser = ref(false)
const hideProductCharge = ref(false)
const productChooserHandler = (prod) => {
  if(chooseLineIndex.value !== NaN && chooseLineEntry.value !== null) {
    const newEntry = chooseLineEntry.value
    applyProduct(newEntry, prod)
    entries.value.splice(chooseLineIndex.value, 1, newEntry)

    chooseLineIndex.value = NaN
    chooseLineEntry.value = null
  }

  hideProductCharge.value = true
}

// M_ProductPrice may be a single object (pricelist version resolved) or the raw
// expanded array — read both shapes.
const priceField = (prod, field) =>
  prod?.product_price ?? prod?.M_ProductPrice?.[field] ?? prod?.M_ProductPrice?.[0]?.[field] ?? 0

const applyProduct = (newEntry, prod) => {
  newEntry.product = prod?.Name || ''
  newEntry.productId = prod?.id || ''
  newEntry.uom = prod?.uom_name || prod?.C_UOM_ID?.identifier || ''
  newEntry.uomId = prod?.c_uom_id || prod?.C_UOM_ID?.id || ''
  newEntry.priceActual = priceField(prod, 'PriceStd')
  newEntry.priceEntered = priceField(prod, 'PriceList')
  newEntry.priceList = priceField(prod, 'PriceList')
  newEntry.charge = ''
  newEntry.chargeId = ''
  newEntry.lineNetAmt = round2(Number(newEntry.qtyEntered) * Number(newEntry.priceEntered))
  newEntry.gallery = prod?.gallery ?? {}
  newEntry.galleries = prod?.galleries ?? []
  newEntry.qtyavailable = 0
  newEntry.qtyonhand = 0
  newEntry.qtyreserved = 0
  newEntry.qtypurchased = 0
}

const productHandler = (newEntry, prod) => {
  applyProduct(newEntry, prod)
  hideProductCharge.value = true
  save()
}

const clearProduct = (entry) => {
  entry.product = ''
  entry.productId = ''
  entry.gallery = {}
  entry.galleries = []
  entry.priceActual = 0
  entry.priceEntered = 0
  entry.priceList = 0
  entry.lineNetAmt = 0
  save()
}

const save = async () => {
  emit('update:modelValue', entries.value)
  emit('save', entries.value)
}

const thumbSrc = (gallery) => {
  const f = gallery?.formats?.small ?? gallery?.formats?.thumbnail
  return f ? (config.public.strapi + '/files-api/' + f.hash + f.ext) : ''
}

const showAttachmentViewer = ref(false)
const showAttachmentViewerData = ref({})
const viewAttachmentViewer = (fileData) => {
  showAttachmentViewer.value = true
  showAttachmentViewerData.value = fileData
}
watch(showAttachmentViewer, () => {
  if(!showAttachmentViewer.value) {
    showAttachmentViewerData.value = {}
  }
})
</script>

<template>
  <div class="plt" :class="{ 'plt--view': !editable }" @mouseleave="save">
    <!-- Header -->
    <header class="plt-head">
      <div class="plt-head-left">
        <span class="plt-head-icon"><i class="ti ti-list-details"></i></span>
        <div class="plt-head-text">
          <h4 class="plt-title">Positionen</h4>
          <p class="plt-sub">Artikel für den Auftrag erfassen</p>
        </div>
        <span v-if="entries.length" class="plt-count">{{ entries.length }}</span>
      </div>
      <button v-if="editable && !locked && entries.length" type="button" class="plt-add" @click="addLine">
        <i class="ti ti-plus"></i>
        <span>Position hinzufügen</span>
      </button>
    </header>

    <!-- Empty state -->
    <div v-if="!entries.length" class="plt-empty">
      <span class="plt-empty-icon"><i class="ti ti-shopping-cart"></i></span>
      <p class="plt-empty-title">Noch keine Positionen</p>
      <p class="plt-empty-sub">Füge Artikel hinzu — Suche nach Name, Artikelnummer, SKU oder EAN.</p>
      <button v-if="editable && !locked" type="button" class="plt-add plt-add--primary" @click="addLine">
        <i class="ti ti-plus"></i>
        <span>Erste Position hinzufügen</span>
      </button>
    </div>

    <!-- Lines -->
    <div v-else class="plt-body">
      <div class="plt-cols plt-cols--head">
        <span class="plt-h plt-h--center">{{ labels.pos }}</span>
        <span class="plt-h"></span>
        <span class="plt-h">{{ labels.product }}</span>
        <span class="plt-h plt-h--center">{{ labels.quantity }}</span>
        <span class="plt-h plt-h--right">{{ labels.price }}</span>
        <span class="plt-h plt-h--right">{{ labels.amount }}</span>
        <span v-if="editable" class="plt-h"></span>
      </div>

      <template v-for="(entry, index) in entries" :key="index">
        <div class="plt-cols plt-row" :class="{ 'plt-row--inactive': entry.isActive === false }">
          <!-- Pos -->
          <div class="plt-cell plt-cell--pos">
            <span class="plt-mlabel">{{ labels.pos }}</span>
            <span v-if="readonly" class="plt-pos-badge">{{ entry.line }}</span>
            <input v-else type="number" min="0" v-model.number="entry.line" @change="save" class="plt-inp plt-inp--num plt-inp--pos" />
          </div>

          <!-- Image -->
          <div class="plt-cell plt-cell--img">
            <button v-if="entry?.gallery?.id" type="button" class="plt-thumb plt-thumb--btn" title="Bild ansehen" @click="viewAttachmentViewer(entry?.gallery ?? {})">
              <img :src="thumbSrc(entry.gallery)" :alt="entry?.gallery?.name || entry.product" loading="lazy" />
            </button>
            <div v-else class="plt-thumb"><i class="ti ti-photo"></i></div>
          </div>

          <!-- Product search -->
          <div class="plt-cell plt-cell--product">
            <ContextMenu v-model="entry.productId" :items="[(entry.productId ? 'zoom' : 'clear')]" :info="{windowFrom: 'zoom', toPath: '/materials/products/'+entry.productId+'/edit', pathName: 'Product: '+entry.product}">
              <span v-if="readonly" class="plt-product-name">{{ entry.product }}</span>
              <ProductSearchSelect
                v-else
                v-model="entry.productId"
                :display="entry.product"
                :options="products?.['products_'+index] || (entry.productId ? [{ id: entry.productId, Name: entry.product }] : [])"
                :loading="loading?.['products_'+index] ?? false"
                :currency="entry.currency || info?.currency || 'EUR'"
                :autofocus="index === focusIndex"
                @open="filterProducts('products_'+index)"
                @search="(val) => filterProducts('products_'+index, string.urlEncode(val))"
                @select="(opt) => productHandler(entry, opt)"
                @clear="clearProduct(entry)"
              />
            </ContextMenu>
            <!-- Unit is almost always "Each" — only surface a picker when the
                 chosen product has no unit, so the user has to set it. -->
            <div v-if="!readonly && entry.productId && !entry.uomId" class="plt-uom-missing">
              <i class="ti ti-alert-triangle"></i>
              <span>Einheit wählen:</span>
              <div class="plt-uom-select">
                <ClientOnly>
                  <SelectBox v-model="entry.uomId" :options="getOptions?.['units_'+index]?.records || [{id: entry.uomId, Name: entry.uom}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="filterOptions('c_uom', 'units_'+index)" @search="(val) => filterOptions('c_uom', 'units_'+index, string.urlEncode(`isActive eq true AND contains(Name,'${val}')`))" :loading="loading?.['units_'+index] ?? true" :up="true" placeholder="----" @change="save" />
                </ClientOnly>
              </div>
            </div>
            <!-- FBA relabel: mandatory alternative barcode per position -->
            <div v-if="info?.isRelabelByFulfillmentProvider && entry.productId" class="plt-relabel-code">
              <i class="ti ti-barcode"></i>
              <span>{{ t?.print_label_code || 'Etiketten-Code (FBA)' }} *</span>
              <span v-if="readonly" class="plt-text">{{ entry.printLabelCode }}</span>
              <input v-else type="text" v-model="entry.printLabelCode" @change="save" class="plt-inp" :class="{ 'plt-relabel-missing': !String(entry.printLabelCode || '').trim() }" />
            </div>
          </div>

          <!-- Quantity -->
          <div class="plt-cell plt-cell--qty">
            <span class="plt-mlabel">{{ labels.quantity }}</span>
            <span v-if="readonly" class="plt-text plt-text--num">{{ entry.qtyEntered }}<span v-if="entry.uom" class="plt-unit-suffix"> {{ entry.uom }}</span></span>
            <input v-else type="number" min="0" step="any" v-model="entry.qtyEntered" @input="recalc(entry, true)" class="plt-inp plt-inp--num plt-inp--qty" />
          </div>

          <!-- Price -->
          <div class="plt-cell plt-cell--price">
            <span class="plt-mlabel">{{ labels.price }}</span>
            <span v-if="readonly" class="plt-text plt-text--num">{{ fmtMoney.format(Number(entry.priceEntered) || 0) }} {{ curSymbol(entry.currency || info?.currency) }}</span>
            <div v-else class="plt-money">
              <input type="number" min="0" step="any" v-model="entry.priceEntered" @input="recalc(entry)" class="plt-inp plt-inp--num plt-inp--money" />
              <span class="plt-money-cur">{{ curSymbol(entry.currency || info?.currency) }}</span>
            </div>
          </div>

          <!-- Amount -->
          <div class="plt-cell plt-cell--amt">
            <span class="plt-mlabel">{{ labels.amount }}</span>
            <span class="plt-amt">{{ fmtMoney.format(Number(entry.lineNetAmt) || 0) }} {{ curSymbol(entry.currency || info?.currency) }}</span>
          </div>

          <!-- Actions -->
          <div v-if="editable" class="plt-cell plt-cell--actions">
            <button v-if="!isLimitedRole" type="button" class="plt-icon-btn" :class="{ 'plt-icon-btn--on': entry.show }" title="Details" @click="entry.show = !entry.show">
              <i class="ti" :class="entry.show ? 'ti-chevron-up' : 'ti-chevron-down'"></i>
            </button>
            <button v-if="!locked" type="button" class="plt-icon-btn plt-icon-btn--danger" title="Position entfernen" @click="destroy(entry, index)">
              <i class="ti ti-trash"></i>
            </button>
          </div>
        </div>

        <!-- Expanded line detail -->
        <div v-if="entry?.show" class="plt-detail">
          <AlertAction v-model='entry.alert.status' :alert="entry.alert" />
          <div class="columns">
            <div class="column is-two-thirds">
              <label class="label d-block">Line</label>
              <input type="text" v-model="entry.line" class="input" :readonly="readonly" />

              <div class="columns">
                <div class="column is-half">
                  <label class="label d-block mt-2">Product</label>
                  <div class="d-flex gap-10px">
                    <input type="text" v-model="entry.product" class="input flexGrow-1" readonly />
                    <button v-if="!readonly" type="button" class="button" @click="chooseLineIndex = index; chooseLineEntry = entry; showProductChooser = true;">Choose</button>
                  </div>

                  <label class="label d-block mt-2">Attribute Set Instance</label>
                  <input type="text" class="input" readonly />
                </div>
                <div class="column is-half">
                  <div :class="(hideProductCharge || readonly) ? 'pointerEvents-none opacity-0.6' : ''">
                    <label class="label d-block mt-2">Charge</label>
                    <ClientOnly>
                      <SelectBox v-model="entry.chargeId" :options="getOptions?.['charges_'+index]?.records || [{id: entry.chargeId, Name: entry.charge}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="filterOptions('c_charge', 'charges_'+index)" @search="(val) => filterOptions('c_charge', 'charges_'+index, string.urlEncode(`isActive eq true AND contains(Name,'${val}')`))" :loading="loading?.['charges_'+index] ?? true" />
                    </ClientOnly>
                  </div>

                  <label class="label d-block mt-2">Resource Assignment</label>
                  <input type="text" class="input" readonly />
                </div>
              </div>

              <label class="label d-block mt-2">Description</label>
              <textarea v-model="entry.description" class="input" :disabled="readonly"></textarea>

              <div class="columns">
                <div class="column is-half">
                  <label class="label d-block mt-2">Quantity *</label>
                  <input type="text" v-model="entry.qtyEntered" @input="recalc(entry, true)" class="input" :readonly="readonly" />

                  <label class="label d-block mt-2">Ordered Quantity *</label>
                  <input type="text" v-model="entry.qtyOrdered" class="input" readonly />

                  <label class="label d-block mt-2">Reserved Quantity *</label>
                  <input type="text" v-model="entry.qtyReserved" class="input" readonly />

                  <label class="label d-block mt-2">Shipper</label>
                  <input type="text" class="input" readonly />
                </div>
                <div class="column is-half">
                  <div :class="readonly ? 'pointerEvents-none opacity-0.6' : ''">
                    <label class="label d-block mt-2">UOM *</label>
                    <ClientOnly>
                      <SelectBox v-model="entry.uomId" :options="getOptions?.['units_'+index]?.records || [{id: entry.uomId, Name: entry.uom}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="filterOptions('c_uom', 'units_'+index)" @search="(val) => filterOptions('c_uom', 'units_'+index, string.urlEncode(`isActive eq true AND contains(Name,'${val}')`))" :loading="loading?.['units_'+index] ?? true" placeholder="----" />
                    </ClientOnly>
                  </div>

                  <label class="label d-block mt-2">Invoiced Quantity *</label>
                  <input type="text" v-model="entry.qtyInvoiced" class="input" readonly />

                  <label class="label d-block mt-2">Delivered Quantity *</label>
                  <input type="text" v-model="entry.qtyDelivered" class="input" readonly />
                </div>
              </div>

              <label class="label d-block mt-2">Price *</label>
              <input type="text" v-model="entry.priceEntered" @input="recalc(entry)" class="input" :readonly="readonly" />

              <div class="columns">
                <div class="column is-half">
                  <label class="label d-block mt-2">Unit Price *</label>
                  <input type="text" v-model="entry.priceActual" class="input" readonly />

                  <div :class="readonly ? 'pointerEvents-none opacity-0.6' : ''">
                    <label class="label d-block mt-2">Tax *</label>
                    <ClientOnly>
                      <SelectBox v-model="entry.taxId" :options="getOptions?.['taxes_'+index]?.records || [{id: entry.taxId, Name: entry.tax}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="filterOptions('c_tax', 'taxes_'+index)" @search="(val) => filterOptions('c_tax', 'taxes_'+index, string.urlEncode(`isActive eq true AND contains(Name,'${val}')`))" :loading="loading?.['taxes_'+index] ?? true" placeholder="----" />
                    </ClientOnly>
                  </div>

                  <div :class="readonly ? 'pointerEvents-none opacity-0.6' : ''">
                    <label class="label d-block mt-2">Project</label>
                    <ClientOnly>
                      <SelectBox v-model="entry.projectId" :options="getOptions?.['projects_'+index]?.records || [{id: entry.projectId, Name: entry.project}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="filterOptions('c_project', 'projects_'+index)" @search="(val) => filterOptions('c_project', 'projects_'+index, string.urlEncode(`isActive eq true AND contains(Name,'${val}')`))" :loading="loading?.['projects_'+index] ?? true" placeholder="----" :up="true" />
                    </ClientOnly>
                  </div>
                </div>
                <div class="column is-half">
                  <label class="label d-block mt-2">List Price *</label>
                  <input type="text" v-model="entry.priceList" class="input" readonly />

                  <label class="label d-block mt-2">Discount %</label>
                  <input type="text" v-model="entry.discount" class="input" :readonly="readonly" />

                  <div :class="readonly ? 'pointerEvents-none opacity-0.6' : ''">
                    <label class="label d-block mt-2">Campaign</label>
                    <ClientOnly>
                      <SelectBox v-model="entry.campaignId" :options="getOptions?.['campaigns_'+index]?.records || [{id: entry.campaignId, Name: entry.campaign}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="filterOptions('c_campaign', 'campaigns_'+index)" @search="(val) => filterOptions('c_campaign', 'campaigns_'+index, string.urlEncode(`isActive eq true AND contains(Name,'${val}')`))" :loading="loading?.['campaigns_'+index] ?? true" placeholder="----" :up="true" />
                    </ClientOnly>
                  </div>
                </div>
              </div>

              <label class="label d-block mt-2">Line Amount</label>
              <input type="text" v-model="entry.lineNetAmt" class="input" readonly />
            </div>
            <div class="column is-one-third">
              <label class="label d-block">Tenant</label>
              <input type="text" :value="entry.client" class="input" readonly />

              <label class="label d-block">Organization</label>
              <input type="text" :value="entry.organization" class="input" readonly />

              <label class="label d-block">Warehouse</label>
              <input type="text" :value="entry.warehouse" class="input" readonly />

              <label class="label d-block">Status</label>
              <div class="d-flex flexDirection-column">
                <div class="check">
                  <input type="checkbox" v-model="entry.isActive" class="checkInput" :id="'plt-detail-active-'+index" :checked="entry.isActive" :disabled="readonly">
                  <label class="checkLabel" :for="'plt-detail-active-'+index">Active</label>
                </div>
              </div>
            </div>
          </div>
        </div>
      </template>
    </div>

    <!-- Footer: add + totals -->
    <footer v-if="entries.length" class="plt-foot">
      <button v-if="editable && !locked" type="button" class="plt-add plt-add--dashed" @click="addLine">
        <i class="ti ti-plus"></i>
        <span>Position hinzufügen</span>
      </button>
      <span v-else></span>
      <div class="plt-total">
        <span class="plt-total-label">Zwischensumme (netto)</span>
        <span class="plt-total-value">{{ fmtMoney.format(netTotal) }} {{ displayCurrency }}</span>
      </div>
    </footer>
  </div>

  <Teleport to="body">
    <ProductChooser v-model="showProductChooser" :info="info" :exclude-bom="excludeBom" @handler="productChooserHandler" />
    <AttachmentViewer v-model="showAttachmentViewer" :type="showAttachmentViewerData?.mime ?? ''" :source="(showAttachmentViewerData?.formats?.large?.url ?? showAttachmentViewerData?.url) ? config.public.strapi+'/files-api/'+(showAttachmentViewerData?.formats?.large?.url ?? showAttachmentViewerData?.url).replace('/uploads/', '') : ''" :title="showAttachmentViewerData?.name ?? ''" :width="showAttachmentViewerData?.width ?? 1366" :height="showAttachmentViewerData?.height ?? 768" />
  </Teleport>
</template>

<style scoped>
.plt {
  background: var(--theme-bg-primary, #fff);
  border: 1px solid var(--theme-border, #e4e3e6);
  border-radius: 14px;
  margin-top: 18px;
  overflow: visible; /* dropdowns (uom SelectBox) must not be clipped */
}

/* ---- header ---- */
.plt-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px;
  padding: 14px 18px;
  border-bottom: 1px solid var(--theme-border, #eef0f4);
}
.plt-head-left { display: flex; align-items: center; gap: 12px; min-width: 0; }
.plt-head-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 38px; height: 38px; flex-shrink: 0;
  border-radius: 10px;
  background: linear-gradient(135deg, #10b981, #059669);
  color: #fff; font-size: 19px;
  box-shadow: 0 4px 10px rgba(16, 185, 129, 0.3);
}
.plt-title { margin: 0; font-size: 15.5px; font-weight: 700; color: var(--theme-text-primary, #0f172a); }
.plt-sub { margin: 1px 0 0; font-size: 12.5px; color: var(--theme-text-secondary, #64748b); }
.plt-count {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 26px; height: 24px; padding: 0 8px;
  border-radius: 999px;
  background: #ecfdf5; color: #047857;
  font-size: 12.5px; font-weight: 700;
}

/* ---- add buttons ---- */
.plt-add {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 8px 16px;
  border: 1.5px solid #10b981;
  border-radius: 10px;
  background: transparent;
  color: #059669;
  font-size: 13.5px; font-weight: 600;
  cursor: pointer;
  transition: all 0.15s;
  white-space: nowrap;
}
.plt-add:hover { background: #ecfdf5; }
.plt-add i { font-size: 16px; }
.plt-add--primary {
  background: linear-gradient(135deg, #10b981, #059669);
  border-color: transparent;
  color: #fff;
  box-shadow: 0 4px 12px rgba(16, 185, 129, 0.35);
}
.plt-add--primary:hover { background: linear-gradient(135deg, #059669, #047857); }
.plt-add--dashed { border-style: dashed; }

/* ---- empty state ---- */
.plt-empty {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  padding: 40px 20px 44px;
  text-align: center;
}
.plt-empty-icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 58px; height: 58px;
  border-radius: 16px;
  background: var(--theme-bg-secondary, #f1f5f9);
  color: #94a3b8; font-size: 28px;
  margin-bottom: 6px;
}
.plt-empty-title { margin: 0; font-size: 15px; font-weight: 700; color: var(--theme-text-primary, #1e293b); }
.plt-empty-sub { margin: 0 0 14px; font-size: 13px; color: var(--theme-text-secondary, #64748b); max-width: 380px; }

/* ---- grid ---- */
.plt-body { padding: 4px 14px 6px; }

.plt-cols {
  display: grid;
  grid-template-columns: 54px 50px minmax(280px, 1fr) 96px 132px 128px 82px;
  gap: 10px;
  align-items: center;
}
.plt--view .plt-cols {
  grid-template-columns: 54px 50px minmax(280px, 1fr) 96px 132px 128px;
}

.plt-cols--head { padding: 10px 4px 8px; }
.plt-h {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--theme-text-secondary, #8b95a5);
}
.plt-h--center { text-align: center; }
.plt-h--right { text-align: right; }

.plt-row {
  padding: 10px 4px;
  border-top: 1px solid var(--theme-border, #eef0f4);
  transition: background 0.12s;
}
.plt-row:hover { background: color-mix(in srgb, var(--theme-bg-secondary, #f8fafc) 60%, transparent); }
.plt-row--inactive { opacity: 0.55; }

.plt-cell { min-width: 0; }
.plt-mlabel { display: none; }

/* ---- cells ---- */
.plt-pos-badge {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 30px; height: 28px; padding: 0 6px;
  border-radius: 8px;
  background: var(--theme-bg-secondary, #f1f5f9);
  font-size: 12.5px; font-weight: 700;
  color: var(--theme-text-secondary, #475569);
  font-variant-numeric: tabular-nums;
}

.plt-thumb {
  width: 44px; height: 44px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 9px; overflow: hidden;
  background: var(--theme-bg-secondary, #f1f5f9);
  border: 1px solid var(--theme-border, #e8edf3);
  color: #b3bcc9; font-size: 18px;
  padding: 0;
}
.plt-thumb img { width: 100%; height: 100%; object-fit: cover; }
.plt-thumb--btn { cursor: zoom-in; transition: transform 0.12s, box-shadow 0.12s; }
.plt-thumb--btn:hover { transform: scale(1.05); box-shadow: 0 4px 10px rgba(15, 23, 42, 0.18); }

.plt-product-name { font-size: 14px; font-weight: 600; color: var(--theme-text-primary, #0f172a); }
.plt-unit-suffix { font-size: 12px; color: var(--theme-text-secondary, #94a3b8); font-weight: 500; }

/* Fallback UOM picker — only rendered when the chosen product has no unit */
.plt-uom-missing {
  display: flex; align-items: center; gap: 8px;
  margin-top: 8px;
  padding: 6px 10px;
  border: 1px solid #fcd34d;
  border-radius: 9px;
  background: #fffbeb;
  font-size: 12.5px; font-weight: 600;
  color: #b45309;
}
.plt-uom-missing i { font-size: 15px; }
.plt-uom-select { flex: 0 1 190px; min-width: 140px; }

/* FBA relabel code — mandatory alternative barcode per position */
.plt-relabel-code {
  display: flex; align-items: center; gap: 8px;
  margin-top: 8px;
  padding: 6px 10px;
  border: 1px solid #93c5fd;
  border-radius: 9px;
  background: #eff6ff;
  font-size: 12.5px; font-weight: 600;
  color: #1d4ed8;
}
.plt-relabel-code i { font-size: 15px; }
.plt-relabel-code input { flex: 0 1 220px; min-width: 150px; }
.plt-relabel-code input.plt-relabel-missing { border-color: #ef4444; }
.plt-text { font-size: 13.5px; color: var(--theme-text-primary, #1e293b); }
.plt-text--num { font-variant-numeric: tabular-nums; }
.plt-cell--qty, .plt-cell--price, .plt-cell--amt { text-align: right; }
.plt-cell--qty { text-align: center; }
.plt-cell--pos { text-align: center; }

/* ---- inputs ---- */
.plt-inp {
  width: 100%;
  height: 42px;
  padding: 0 10px;
  background: var(--theme-bg-primary, #fff);
  border: 1.5px solid var(--theme-border, #d8dde6);
  border-radius: 10px;
  font-size: 14px; font-weight: 500;
  color: var(--theme-text-primary, #0f172a);
  outline: none;
  transition: border-color 0.15s, box-shadow 0.15s;
}
.plt-inp:focus { border-color: #10b981; box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.14); }
.plt-inp--num { font-variant-numeric: tabular-nums; -moz-appearance: textfield; appearance: textfield; }
.plt-inp--num::-webkit-outer-spin-button,
.plt-inp--num::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.plt-inp--pos { text-align: center; padding: 0 6px; }
.plt-inp--qty { text-align: center; }
.plt-inp--money { text-align: right; padding-right: 30px; }

.plt-money { position: relative; }
.plt-money-cur {
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  font-size: 12.5px; font-weight: 600;
  color: var(--theme-text-secondary, #94a3b8);
  pointer-events: none;
}

.plt-amt {
  display: block;
  font-size: 14px; font-weight: 700;
  color: var(--theme-text-primary, #0f172a);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ---- action icon buttons ---- */
.plt-cell--actions { display: flex; justify-content: flex-end; gap: 6px; }
.plt-icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 34px; height: 34px;
  border: 1.5px solid var(--theme-border, #e2e8f0);
  border-radius: 9px;
  background: transparent;
  color: var(--theme-text-secondary, #64748b);
  font-size: 15px;
  cursor: pointer;
  transition: all 0.12s;
}
.plt-icon-btn:hover { border-color: #10b981; color: #059669; background: #ecfdf5; }
.plt-icon-btn--on { border-color: #10b981; color: #059669; background: #ecfdf5; }
.plt-icon-btn--danger:hover { border-color: #ef4444; color: #dc2626; background: #fef2f2; }

/* ---- expanded detail ---- */
.plt-detail {
  margin: 0 4px 12px;
  padding: 18px;
  border: 1px dashed var(--theme-border, #d8dde6);
  border-radius: 12px;
  background: var(--theme-bg-secondary, #f8fafc);
}

/* ---- footer ---- */
.plt-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 14px;
  padding: 12px 18px 14px;
  border-top: 1px solid var(--theme-border, #eef0f4);
}
.plt-total { display: flex; flex-direction: column; align-items: flex-end; }
.plt-total-label {
  font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--theme-text-secondary, #8b95a5);
}
.plt-total-value {
  font-size: 18px; font-weight: 800;
  color: var(--theme-text-primary, #0f172a);
  font-variant-numeric: tabular-nums;
}

/* ---- dark mode ---- */
[data-mode="dark"] .plt { background: #0f172a; border-color: #1e293b; }
[data-mode="dark"] .plt-head { border-bottom-color: #1e293b; }
[data-mode="dark"] .plt-title { color: #f1f5f9; }
[data-mode="dark"] .plt-count { background: rgba(16, 185, 129, 0.15); color: #34d399; }
[data-mode="dark"] .plt-add:hover { background: rgba(16, 185, 129, 0.12); }
[data-mode="dark"] .plt-empty-icon { background: #1e293b; }
[data-mode="dark"] .plt-empty-title { color: #f1f5f9; }
[data-mode="dark"] .plt-row { border-top-color: #1e293b; }
[data-mode="dark"] .plt-row:hover { background: rgba(30, 41, 59, 0.5); }
[data-mode="dark"] .plt-pos-badge { background: #1e293b; color: #cbd5e1; }
[data-mode="dark"] .plt-thumb { background: #1e293b; border-color: #334155; }
[data-mode="dark"] .plt-product-name, [data-mode="dark"] .plt-text, [data-mode="dark"] .plt-amt { color: #f1f5f9; }
[data-mode="dark"] .plt-inp { background: #0f172a; border-color: #334155; color: #f1f5f9; }
[data-mode="dark"] .plt-icon-btn { border-color: #334155; color: #cbd5e1; }
[data-mode="dark"] .plt-icon-btn:hover, [data-mode="dark"] .plt-icon-btn--on { background: rgba(16, 185, 129, 0.12); }
[data-mode="dark"] .plt-icon-btn--danger:hover { background: rgba(239, 68, 68, 0.12); }
[data-mode="dark"] .plt-switch-track { background: #334155; }
[data-mode="dark"] .plt-switch input:checked + .plt-switch-track { background: #10b981; }
[data-mode="dark"] .plt-uom-missing { background: rgba(245, 158, 11, 0.1); border-color: #92400e; color: #fbbf24; }
[data-mode="dark"] .plt-relabel-code { background: rgba(59, 130, 246, 0.1); border-color: #1e40af; color: #93c5fd; }
[data-mode="dark"] .plt-detail { background: #0b1220; border-color: #334155; }
[data-mode="dark"] .plt-foot { border-top-color: #1e293b; }
[data-mode="dark"] .plt-total-value { color: #f1f5f9; }

/* ---- responsive: stack rows into cards ---- */
@media (max-width: 980px) {
  .plt-cols--head { display: none; }

  .plt-cols {
    grid-template-columns: 1fr 1.3fr 1fr;
    grid-template-areas:
      "pos     img     actions"
      "product product product"
      "qty     price   amount";
    gap: 10px 12px;
  }
  .plt--view .plt-cols {
    grid-template-columns: 1fr 1.3fr 1fr;
    grid-template-areas:
      "pos     img     img"
      "product product product"
      "qty     price   amount";
  }

  .plt-cell--pos { grid-area: pos; text-align: left; }
  .plt-cell--img { grid-area: img; }
  .plt-cell--product { grid-area: product; }
  .plt-cell--qty { grid-area: qty; text-align: left; }
  .plt-cell--price { grid-area: price; text-align: left; }
  .plt-cell--amt { grid-area: amount; text-align: right; align-self: end; }
  .plt-cell--actions { grid-area: actions; align-self: start; }

  .plt-row { padding: 14px 4px; }

  .plt-mlabel {
    display: block;
    margin-bottom: 4px;
    font-size: 10.5px; font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.06em;
    color: var(--theme-text-secondary, #8b95a5);
  }
  .plt-cell--amt .plt-mlabel { text-align: right; }

  .plt-foot { flex-direction: column-reverse; align-items: stretch; }
  .plt-add { justify-content: center; }
  .plt-total { align-items: flex-end; }
}
</style>
