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

const props = defineProps({
  modelValue: Object
})

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

const organizations = useCookie('logship_organizations')
const organization = useCookie('logship_organization')
const organizationId = useCookie('logship_organization_id')
const warehouses = useCookie('logship_warehouses')
const warehouse = useCookie('logship_warehouse')
const warehouseId = useCookie('logship_warehouse_id')

const clientName = ref('')
onMounted(() => {
  const getClient = localStorage.getItem('logship_clients')
  if(getClient) {
    const clientObj = JSON.parse(getClient)
    clientName.value = clientObj[0].name
  }
})

const form = ref({
  id: props.modelValue.id || '',
  client: props.modelValue.client || '',
  clientId: props.modelValue.clientId || '',
  organization: props.modelValue.organization || organization.value?.Name || '',
  organizationId: props.modelValue.organizationId || organizationId.value || '',
  warehouse: props.modelValue.warehouse || warehouse.value?.Name || '',
  warehouseId: props.modelValue.warehouseId || warehouseId.value || '',
  name: props.modelValue.name || '',
  value: props.modelValue.value || '',
  description: props.modelValue.description || '',
  help: props.modelValue.help || '',
  isActive: props.modelValue.isActive ?? true,
  marketplaceId: props.modelValue.marketplaceId || '',
  marketplaceUrl: props.modelValue.marketplaceUrl || '',
  marketplaceKey: props.modelValue.marketplaceKey || '',
  marketplaceSecret: props.modelValue.marketplaceSecret || '',
  marketplaceToken: props.modelValue.marketplaceToken || '',
  startingOrderId: props.modelValue.startingOrderId || '',
  startingOrderDate: props.modelValue.startingOrderDate || date.now('date'),
  priceList: props.modelValue.priceList || '',
  priceListId: props.modelValue.priceListId || '',
  priceListVersion: props.modelValue.priceListVersion || '',
  priceListVersionId: props.modelValue.priceListVersionId || '',
  priceListProduct: props.modelValue.priceListProduct || '',
  priceListProductId: props.modelValue.priceListProductId || '',
  priceListVersionProduct: props.modelValue.priceListVersionProduct || '',
  priceListVersionProductId: props.modelValue.priceListVersionProductId || '',
  syncProductToIdempiere: props.modelValue.syncProductToIdempiere ?? false,
  syncProductFromIdempiere: props.modelValue.syncProductFromIdempiere ?? false,
  syncOrderToIdempiere: props.modelValue.syncOrderToIdempiere ?? false,
  syncOrderFromIdempiere: props.modelValue.syncOrderFromIdempiere ?? false,
  syncShipmentToIdempiere: props.modelValue.syncShipmentToIdempiere ?? false,
  syncShipmentFromIdempiere: props.modelValue.syncShipmentFromIdempiere ?? false,
  isExcludetrackingmail: props.modelValue.isExcludetrackingmail ?? false,
  isCustomTrackingMail: props.modelValue.isCustomTrackingMail ?? false,
  isSentCustomTrackingMail: props.modelValue.isSentCustomTrackingMail ?? false,
  isBoMConvert: props.modelValue.isBoMConvert ?? false,
  isJtlBomSkip: props.modelValue.isJtlBomSkip ?? false,
  includeBomSync: props.modelValue.includeBomSync ?? false,
  isTaxIncluded: props.modelValue.isTaxIncluded ?? true,
  isShopware6: props.modelValue.isShopware6 ?? false,
  isWebshopToIdempiere: props.modelValue.isWebshopToIdempiere ?? false,
  isIdempiereToWebshop: props.modelValue.isIdempiereToWebshop ?? false,
  isSkipConfirmation: props.modelValue.isSkipConfirmation ?? false,
  isShopifyUpdatedMin: props.modelValue.isShopifyUpdatedMin ?? false,
  isCheckShopifyFulfillmentService: props.modelValue.isCheckShopifyFulfillmentService ?? false,
  isCheckoutNoteKeepOrderOpen: props.modelValue.isCheckoutNoteKeepOrderOpen ?? false,
  isSyncQtyOnHandOnCommission: props.modelValue.isSyncQtyOnHandOnCommission ?? false,
  isSyncProductQtySchedule: props.modelValue.isSyncProductQtySchedule ?? false,
  amazonMerchantId: props.modelValue.amazonMerchantId ?? '',
  processing: props.modelValue.processing ?? false,
  plentyOneOrderFilterCode: props.modelValue.plentyOneOrderFilterCode ?? 5.1,
  plentyOneOrderImportCode: props.modelValue.plentyOneOrderImportCode ?? 8,
  plentyOneOrderCommissionCode: props.modelValue.plentyOneOrderCommissionCode ?? 6,
  plentyOneWarehouseId: props.modelValue.plentyOneWarehouseId ?? 102,
  DHLURL: props.modelValue.DHLURL ?? '',
  DHLKey: props.modelValue.DHLKey ?? '',
  DHLSecret: props.modelValue.DHLSecret ?? '',
  DHLUser: props.modelValue.DHLUser ?? '',
  DHLPass: props.modelValue.DHLPass ?? '',
  DHLUseCustom: props.modelValue.DHLUseCustom ?? false,
  DHLBillingNumberDE: props.modelValue.DHLBillingNumberDE ?? '',
  DHLBillingNumberINT: props.modelValue.DHLBillingNumberINT ?? '',
  DPDUseCustom: props.modelValue.DPDUseCustom ?? false,
  DPDURL: props.modelValue.DPDURL ?? '',
  DPDDelisId: props.modelValue.DPDDelisId ?? '',
  DPDPassword: props.modelValue.DPDPassword ?? '',
  DPDCustomerNumber: props.modelValue.DPDCustomerNumber ?? '',
  DPDDepot: props.modelValue.DPDDepot ?? '',
  shipperCustom1: props.modelValue.shipperCustom1 || '',
  shipperCustom1Id: props.modelValue.shipperCustom1Id || '',
  shipperCustom2: props.modelValue.shipperCustom2 || '',
  shipperCustom2Id: props.modelValue.shipperCustom2Id || '',
  shipperCustom3: props.modelValue.shipperCustom3 || '',
  shipperCustom3Id: props.modelValue.shipperCustom3Id || '',
  shipperCustom4: props.modelValue.shipperCustom4 || '',
  shipperCustom4Id: props.modelValue.shipperCustom4Id || '',
  shipperCustom5: props.modelValue.shipperCustom5 || '',
  shipperCustom5Id: props.modelValue.shipperCustom5Id || '',
  shipperCustom6: props.modelValue.shipperCustom6 || '',
  shipperCustom6Id: props.modelValue.shipperCustom6Id || '',
  shipperCustom7: props.modelValue.shipperCustom7 || '',
  shipperCustom7Id: props.modelValue.shipperCustom7Id || '',
  shipperCustom8: props.modelValue.shipperCustom8 || '',
  shipperCustom8Id: props.modelValue.shipperCustom8Id || '',
  externalShippingProfile1: props.modelValue.externalShippingProfile1 ?? '',
  externalShippingProfile2: props.modelValue.externalShippingProfile2 ?? '',
  externalShippingProfile3: props.modelValue.externalShippingProfile3 ?? '',
  externalShippingProfile4: props.modelValue.externalShippingProfile4 ?? '',
  externalShippingProfile5: props.modelValue.externalShippingProfile5 ?? '',
  externalShippingProfile6: props.modelValue.externalShippingProfile6 ?? '',
  externalShippingProfile7: props.modelValue.externalShippingProfile7 ?? '',
  externalShippingProfile8: props.modelValue.externalShippingProfile8 ?? '',
  removeArticleString: props.modelValue.removeArticleString ?? '',
  jtlMerchantId: props.modelValue.jtlMerchantId ?? '',
  ebayEnvironment: props.modelValue.ebayEnvironment || 'production',
  ebayRuName: props.modelValue.ebayRuName || '',
  invoiceRuleId: props.modelValue.invoiceRuleId || 'I',
  isSyncSelectedArticlesOnly: props.modelValue.isSyncSelectedArticlesOnly ?? false,
  selectedArticlesSync: props.modelValue.selectedArticlesSync ?? '',
  saveSkuBack: props.modelValue.saveSkuBack ?? false,
  shopifyIgnoreLocation: props.modelValue.shopifyIgnoreLocation ?? false,
  shopifyLocationId: props.modelValue.shopifyLocationId ?? '',
  isAmazonOrderReport: props.modelValue.isAmazonOrderReport ?? false,
  marketplaceIdentifier: props.modelValue.marketplaceIdentifier || '',
  uid: props.modelValue.uid || '',
  // Read-only status flag (server-computed) — round-tripped so save() doesn't drop it.
  amazonViaLogshipApp: props.modelValue.amazonViaLogshipApp ?? false,
  marketplace: props.modelValue.marketplace || '',
  alert: {
    status: props.modelValue.alert.status || '',
    message: props.modelValue.alert.message || '',
    processing: props.modelValue.alert.processing ?? false,
  }
})

const passwordSwitchers = ref({})
const loading = ref({})
const getOptions = ref({})
const setOptions = async (url, key) => {
  loading.value[key] = true
  const res = await $fetch('/api/'+url, {
    headers: useRequestHeaders(['cookie'])
  })
  if(res) {
    getOptions.value[key] = res

    if(key === 'warehouses' && res?.records?.[0]) {
      form.value.warehouseId = String(res.records[0].id || '')
      form.value.warehouse = res.records[0].Name || ''
    }
    if(key === 'price_list_versions' && res?.records?.[0]) {
      form.value.priceListVersionId = String(res.records[0].id || '')
      form.value.priceListVersion = res.records[0].Name || ''
    }
  }
  loading.value[key] = false
}

watch(() => props.modelValue, () => {
  form.value = props.modelValue
})

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

/* ---------- Marketplace detection ---------- */
const isShopware    = computed(() => String(form.value.marketplaceId) === '1')
const isShopify     = computed(() => String(form.value.marketplaceId) === '2')
const isShopifyNew  = computed(() => String(form.value.marketplaceId) === '10')
const isAnyShopify  = computed(() => isShopify.value || isShopifyNew.value)
const isAmazon      = computed(() => String(form.value.marketplaceId) === '4')
const isEbay        = computed(() =>
  String(form.value.marketplaceId) === '5' ||
  form.value.marketplaceIdentifier?.toLowerCase() === 'ebay' ||
  form.value.marketplace?.toLowerCase()?.includes('ebay')
)
const isPlenty      = computed(() => String(form.value.marketplaceId) === '6')
const isCsv         = computed(() => String(form.value.marketplaceId) === '7')
const isJtl         = computed(() => String(form.value.marketplaceId) === '8')
const isTemu        = computed(() => String(form.value.marketplaceId) === '9')
const isManual      = computed(() => String(form.value.marketplaceId) === '12')

// Amazon needs no per-shop URL — the SP-API base endpoint is the same for all EU
// sellers. Prefill it on selection so the source works without manual URL entry
// (the Laravel-side env fallback is the SANDBOX endpoint, so empty is a footgun).
const onMarketplaceChange = () => {
  if (isAmazon.value && !form.value.marketplaceUrl) {
    form.value.marketplaceUrl = 'https://sellingpartnerapi-eu.amazon.com'
  }
  save()
}

const marketplaceBadgeColor = computed(() => {
  if (isAmazon.value)    return '#ff9900'
  if (isEbay.value)      return '#e53238'
  if (isAnyShopify.value)return '#95bf47'
  if (isShopware.value)  return '#189eff'
  if (isPlenty.value)    return '#00b5ad'
  if (isJtl.value)       return '#6366f1'
  if (isTemu.value)      return '#ff7043'
  if (isCsv.value)       return '#64748b'
  if (isManual.value)    return '#4b5563'
  return '#6b7280'
})

/* ---------- Dynamic labels per marketplace ---------- */
const keyLabel = computed(() => {
  if (isPlenty.value)     return 'Username'
  if (isAmazon.value)     return 'Client ID (LWA)'
  if (isEbay.value)       return 'Client ID (App ID)'
  if (isShopifyNew.value) return 'Client ID'
  if (isShopware.value)   return 'Client ID'
  if (isTemu.value)       return 'App Key'
  return 'Client / Public Key'
})
const secretLabel = computed(() => {
  if (isPlenty.value) return 'Password'
  if (isTemu.value)   return 'App Secret'
  return 'Client Secret'
})
const tokenLabel = computed(() => {
  if (isAmazon.value || isEbay.value) return 'Refresh Token'
  if (isJtl.value)                    return 'API Key'
  if (isTemu.value)                   return 'Access Token'
  if (isAnyShopify.value)             return 'Permanent Access Token'
  return 'Access Token'
})

/* ---------- Section / Tab nav ---------- */
const sections = computed(() => {
  const arr = [
    { id: 'general',     label: 'General',          icon: 'ti-info-circle',       tone: 'blue'   },
    { id: 'credentials', label: 'Credentials',      icon: 'ti-key',               tone: 'orange' },
    { id: 'sync',        label: 'Sync Direction',   icon: 'ti-arrows-exchange-2', tone: 'green'  },
    { id: 'idempiere',   label: 'iDempiere Mapping',icon: 'ti-database-export',   tone: 'purple' },
    { id: 'processing',  label: 'Order Processing', icon: 'ti-adjustments-alt',   tone: 'yellow' }
  ]
  if (isPlenty.value) arr.push({ id: 'shipping', label: 'Shipping Profiles', icon: 'ti-truck-delivery', tone: 'teal' })
  arr.push({ id: 'dhl', label: 'DHL Custom', icon: 'ti-package', tone: 'gray' })
  arr.push({ id: 'dpd', label: 'DPD Custom', icon: 'ti-package', tone: 'red' })
  if (isAmazon.value || isAnyShopify.value || isPlenty.value || isJtl.value || isShopware.value) {
    arr.push({ id: 'marketplace', label: 'Marketplace Specific', icon: 'ti-building-store', tone: 'pink' })
  }
  arr.push({ id: 'filters', label: 'Product Filters',    icon: 'ti-filter',  tone: 'cyan'  })
  arr.push({ id: 'status',  label: 'Status & Sync Info', icon: 'ti-activity', tone: 'slate' })
  return arr
})
const activeSection = ref('general')
watch(sections, (list) => {
  if (!list.find(s => s.id === activeSection.value)) {
    activeSection.value = list[0]?.id || 'general'
  }
})

/* ---------- eBay / Amazon connection status ---------- */
const hasEbayCredentials = computed(() => {
  return !!(form.value.marketplaceKey && form.value.marketplaceSecret && form.value.ebayRuName)
})
const isEbayConnected = computed(() => {
  return isEbay.value && !!form.value.marketplaceToken
})
const ebayConnectionStatus = computed(() => {
  if (!isEbay.value) return null
  if (!hasEbayCredentials.value) return { status: 'not-configured', label: 'Not Configured', color: 'is-danger' }
  if (!isEbayConnected.value) return { status: 'not-authorized', label: 'Not Authorized', color: 'is-warning' }
  return { status: 'connected', label: 'Connected', color: 'is-success' }
})

const hasAmazonCredentials = computed(() => {
  return !!(form.value.marketplaceKey && form.value.marketplaceSecret && form.value.marketplaceToken)
})
// Connected through the central LogShip public SP-API app (flag computed server-side
// in order-sources/[id]/index.get.ts) — vs. the merchant's own SP-API app credentials.
const amazonViaLogshipApp = computed(() => props.modelValue?.amazonViaLogshipApp === true)
// Seller Central region the merchant authorizes from (selects the consent URL domain; not persisted).
const amazonRegion = ref('de')
const amazonConnectionStatus = computed(() => {
  if (!isAmazon.value) return null
  if (amazonViaLogshipApp.value && form.value.marketplaceToken) return { status: 'connected-logship', label: 'Connected via LogShip App', color: 'is-success' }
  if (!hasAmazonCredentials.value) return { status: 'not-configured', label: 'Not Configured', color: 'is-danger' }
  return { status: 'connected', label: 'Credentials Set', color: 'is-success' }
})

const amazonTestResult = ref({ status: '', message: '', details: null })
const amazonTesting = ref(false)
const testAmazonConnection = async () => {
  amazonTesting.value = true
  amazonTestResult.value = { status: '', message: '' }
  try {
    const res = await $fetch('/api/oauth/amazon/test-connection', {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { orderSourceId: form.value.id },
    })
    amazonTestResult.value = {
      status: res.success ? 'success' : 'error',
      message: res.message,
      details: res,
    }
  } catch (err) {
    amazonTestResult.value = { status: 'error', message: err?.data?.message || 'Request failed.' }
  }
  amazonTesting.value = false
}

const ebayTestResult = ref({ status: '', message: '', details: null })
const ebayTesting = ref(false)
const testEbayConnection = async () => {
  ebayTesting.value = true
  ebayTestResult.value = { status: '', message: '' }
  try {
    const res = await $fetch('/api/oauth/ebay/test-connection', {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: {
        orderSourceId: form.value.id,
        ebayEnvironment: form.value.ebayEnvironment,
      },
    })
    ebayTestResult.value = {
      status: res.success ? 'success' : 'error',
      message: res.message,
      details: res,
    }
  } catch (err) {
    ebayTestResult.value = { status: 'error', message: 'Request failed.' }
  }
  ebayTesting.value = false
}

/* ---------- JTL-FFN test connection + dry-run imports ---------- */
const jtlTesting = ref(false)            // any JTL test in-flight (disables the toolbar)
const jtlActive = ref('')                // which button is running (per-button spinner)
const jtlResult = ref({ status: '', message: '', summary: null })  // headline message + counts
const jtlDetail = ref(null)              // detailed payload: { kind: 'products'|'orders'|'list', products, orders }
const jtlListSearch = ref('')            // client-side filter for the full product list
const openSelectiveUpdate = ref(false)   // selective JTL→iDempiere product-update modal
const openBomSync = ref(false)           // JTL BOM (Stückliste) → iDempiere PP_Product_BOM component-sync modal
const jtlFilteredList = computed(() => {
  const list = jtlDetail.value?.kind === 'list' ? (jtlDetail.value.products || []) : []
  const q = jtlListSearch.value.trim().toLowerCase()
  if (!q) return list
  return list.filter(p =>
    (p.name || '').toLowerCase().includes(q) ||
    (p.merchantSku || '').toLowerCase().includes(q) ||
    (p.jfsku || '').toLowerCase().includes(q)
  )
})

// Quick check (light fulfiller endpoints): existence counts only.
const runJtlTest = async (endpoint, label, key) => {
  jtlTesting.value = true
  jtlActive.value = key
  jtlResult.value = { status: '', message: `${label}…`, summary: null }
  jtlDetail.value = null
  try {
    const res = await $fetch(`/api/ffn/${endpoint}`, {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { orderSourceId: form.value.id }
    })
    const ok = res?.success === true
    jtlResult.value = { status: ok ? 'success' : 'error', message: res?.message || (ok ? 'OK' : 'Failed'), summary: res?.summary || null }
  } catch (err) {
    jtlResult.value = { status: 'error', message: err?.data?.message || err?.message || 'Request failed', summary: null }
  }
  jtlTesting.value = false
  jtlActive.value = ''
}

// Detailed dry-run (rich endpoints): full iDempiere field mapping / order+bpartner+products preview.
const runJtlDetailed = async (endpoint, kind, label, key) => {
  jtlTesting.value = true
  jtlActive.value = key
  jtlResult.value = { status: '', message: `${label}…`, summary: null }
  jtlDetail.value = null
  try {
    const res = await $fetch(`/api/ffn/${endpoint}`, {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { orderSourceId: form.value.id }
    })
    const ok = res?.success === true
    jtlResult.value = { status: ok ? 'success' : 'error', message: res?.message || (ok ? 'OK' : 'Failed'), summary: res?.summary || null }
    if (ok) { jtlDetail.value = { kind, products: res?.products || [], orders: res?.orders || [] }; jtlListSearch.value = '' }
  } catch (err) {
    jtlResult.value = { status: 'error', message: err?.data?.message || err?.message || 'Request failed', summary: null }
  }
  jtlTesting.value = false
  jtlActive.value = ''
}

const testJtlConnection = () => runJtlTest('test-connection', 'Testing connection', 'conn')
const dryRunJtlProducts = () => runJtlTest('dry-run-products', 'Quick product check', 'qprod')
const dryRunJtlOrders   = () => runJtlTest('dry-run-orders', 'Quick order check', 'qord')
const detailJtlProducts = () => runJtlDetailed('dry-run-products-detailed', 'products', 'Detailed product dry-run', 'dprod')
const detailJtlOrders   = () => runJtlDetailed('dry-run-orders-detailed', 'orders', 'Detailed order dry-run', 'dord')
const listJtlProducts   = () => runJtlDetailed('products-list', 'list', 'Listing all products', 'lprod')
// Queue an image sync for THIS order source only (new/changed images; unchanged are skipped).
const syncJtlImages     = () => runJtlTest('sync-images', 'Queuing image sync', 'images')
const jtlClearResult    = () => { jtlResult.value = { status: '', message: '', summary: null }; jtlDetail.value = null; jtlListSearch.value = '' }

// Full shopify-new product sync: queues a background job in the Laravel middleware that
// imports ALL Shopify articles (the automatic sync only picks up recently created ones).
// The job takes a per-order-source lock, so it waits instead of interfering when the
// scheduled sync happens to be running.
const shopifySyncing = ref(false)
const shopifySyncResult = ref({ status: '', message: '' })
const startShopifyFullSync = async () => {
  if (shopifySyncing.value || !form.value.id) return
  shopifySyncing.value = true
  shopifySyncResult.value = { status: '', message: '' }
  try {
    const res = await $fetch(`/api/settings/order-sources/${form.value.id}/sync-shopify-products`, {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { full: true }
    })
    const ok = res?.success === true
    shopifySyncResult.value = { status: ok ? 'success' : 'error', message: res?.message || (ok ? 'Full product sync queued' : 'Sync failed') }
  } catch (err) {
    shopifySyncResult.value = { status: 'error', message: err?.data?.message || err?.message || 'Request failed' }
  }
  shopifySyncing.value = false
}
const shopifySyncClearResult = () => { shopifySyncResult.value = { status: '', message: '' } }

// Print the (filtered) product list as a RETURN SLIP (Retourenschein) PDF — meant to be placed
// INSIDE the shipment. The "return to" address is the fulfiller warehouse (from /api/ffn/warehouses).
const printJtlProductsPdf = async () => {
  const rows = jtlFilteredList.value || []
  if (!rows.length) return
  const esc = (s) => String(s ?? '').replace(/[&<>"]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]))

  // Return-to address = the fulfiller warehouse (returns ship back here). Fetched live; optional.
  let addrLines = []
  try {
    const wres = await $fetch('/api/ffn/warehouses', { headers: useRequestHeaders(['cookie']) })
    const wh = (wres?.items || wres?.records || [])[0]
    const a = wh?.address || {}
    addrLines = [wh?.name, a.company, a.street, [a.zip, a.city].filter(Boolean).join(' '), a.country].filter(Boolean)
  } catch (e) { /* no address available — slip prints without it */ }
  const addrHtml = addrLines.length
    ? '<div class="ret"><div class="ret__lbl">Rücksendeadresse / Return address<br>(bitte auf das Paket schreiben / write on your parcel):</div>' + addrLines.map((l) => '<div>' + esc(l) + '</div>').join('') + '</div>'
    : ''

  const title = 'Retourenschein / Return Slip'
  const sub = (form.value.name || '') + (form.value.jtlMerchantId ? ' (' + form.value.jtlMerchantId + ')' : '')
  const when = new Date().toLocaleDateString('de-DE')
  const rowsHtml = rows.map((p, i) => `<tr><td>${i + 1}</td><td>${esc(p.name)}</td><td>${esc(p.merchantSku)}</td><td>${esc(p.jfsku)}</td><td class="qty"></td></tr>`).join('')
  const html = '<!DOCTYPE html><html><head><meta charset="utf-8"><title>' + esc(title) + '</title><style>'
    + 'body{font-family:Arial,Helvetica,sans-serif;font-size:11px;color:#111;margin:24px}'
    + '.head{display:flex;justify-content:space-between;align-items:flex-start;gap:24px;margin-bottom:14px}'
    + 'h1{font-size:16px;margin:0}.sub{color:#555;font-size:10px;margin-top:3px}'
    + '.ret{font-size:9px;color:#333;text-align:right;line-height:1.35;white-space:nowrap}'
    + '.ret__lbl{font-weight:bold;color:#666;margin-bottom:2px}'
    + '.note{border:1px solid #999;background:#f6f6f6;padding:8px 10px;margin:0 0 14px;font-weight:bold}'
    + 'table{width:100%;border-collapse:collapse}th,td{border:1px solid #ccc;padding:4px 6px;text-align:left;vertical-align:top}'
    + 'th{background:#f0f0f0}tr:nth-child(even) td{background:#fafafa}'
    + 'td:first-child,th:first-child{width:30px;text-align:right;color:#888}'
    + 'th.qty,td.qty{width:80px;text-align:center}'
    + '@media print{body{margin:0}thead{display:table-header-group}}'
    + '</style></head><body>'
    + '<div class="head"><div><h1>' + esc(title) + '</h1><div class="sub">' + esc(sub) + ' · ' + esc(when) + ' · ' + rows.length + ' Artikel</div></div>' + addrHtml + '</div>'
    + '<div class="note">Kein Retourenlabel enthalten — bitte frankieren Sie die Rücksendung selbst und schreiben Sie die oben genannte Rücksendeadresse auf das Paket. Legen Sie diesen Schein IN die Sendung. · No return label included — please pay the postage yourself and write the return address above on your parcel. Place this slip INSIDE the shipment.</div>'
    + '<table><thead><tr><th>#</th><th>Produkt / Product</th><th>SKU (merchantSku)</th><th>JFSKU</th><th class="qty">Menge / Qty</th></tr></thead><tbody>' + rowsHtml + '</tbody></table>'
    + '</body></html>'
  const w = window.open('', '_blank')
  if (!w) { alert('Please allow popups to print the return slip.'); return }
  w.document.open(); w.document.write(html); w.document.close(); w.focus()
  setTimeout(() => { try { w.print() } catch (e) {} }, 350)
}

const prettyKey = (k) => String(k).replace(/_/g, ' ')
// Flatten the iDempiere product payload for display (extract nested price, stringify other objects).
const flattenPayload = (payload) => {
  const out = {}
  for (const [k, v] of Object.entries(payload || {})) {
    if (k === 'M_ProductPrice' && Array.isArray(v) && v[0]) {
      const ps = Number(v[0].priceStd)
      out['M_ProductPrice (priceStd)'] = Number.isFinite(ps) ? ps.toFixed(2) : v[0].priceStd
    } else if (v !== null && typeof v === 'object') {
      out[k] = JSON.stringify(v)
    } else {
      out[k] = v
    }
  }
  return out
}

/* ---------- Validation helpers ---------- */
const needsKeyPair     = computed(() => isShopware.value || isAmazon.value || isEbay.value || isPlenty.value || isShopifyNew.value || isTemu.value)
const needsTokenOnly   = computed(() => isShopify.value)
// JTL-FFN inherits global fulfiller credentials (URL + token are env-level), so per-OrderSource
// credentials are optional — only the JTL Merchant ID is required.
const credentialsOk    = computed(() =>
  isJtl.value ||
  (form.value.marketplaceKey !== '' && form.value.marketplaceSecret !== '') || form.value.marketplaceToken !== ''
)

const sectionDone = computed(() => ({
  general:     !!form.value.name && !!form.value.organizationId && !!form.value.warehouseId,
  credentials: !!form.value.marketplaceId && (isJtl.value || (!!form.value.marketplaceUrl && credentialsOk.value)),
  sync:        true,
  idempiere:   !!form.value.priceListId,
  processing:  true,
  shipping:    true,
  dhl:         !form.value.DHLUseCustom || !!form.value.DHLURL,
  dpd:         !form.value.DPDUseCustom || (!!form.value.DPDDelisId && !!form.value.DPDPassword),
  marketplace: true,
  filters:     !form.value.isSyncSelectedArticlesOnly || !!form.value.selectedArticlesSync,
  status:      true
}))

/* ---------- Help modal (for deep-dive docs per section) ---------- */
const helpModal = ref({ open: false, title: '', body: '' })
const openHelp = (title, body) => { helpModal.value = { open: true, title, body } }
const closeHelp = () => { helpModal.value.open = false }
</script>

<template>
  <div @mouseleave="save" class="os-form">
    <div class="my-5 mx-5">
      <AlertAction v-model="form.alert.status" :alert="form.alert" />

      <!-- ===== Status Banner ===== -->
      <div class="os-status-banner" :style="{ '--accent': marketplaceBadgeColor }">
        <div class="os-status-banner__badge">
          <i class="ti ti-building-store"></i>
        </div>
        <div class="os-status-banner__body">
          <div class="os-status-banner__title">
            {{ form.name || 'New Order Source' }}
            <span v-if="form.marketplace" class="os-status-banner__chip">{{ form.marketplace }}</span>
            <span v-else class="os-status-banner__chip os-status-banner__chip--muted">no marketplace</span>
            <span class="os-status-banner__chip" :class="form.isActive ? 'os-status-banner__chip--ok' : 'os-status-banner__chip--off'">
              <i class="ti" :class="form.isActive ? 'ti-circle-check' : 'ti-circle-x'"></i>
              {{ form.isActive ? 'Active' : 'Inactive' }}
            </span>
          </div>
          <div class="os-status-banner__sub">
            <span v-if="form.organization"><i class="ti ti-building"></i> {{ form.organization }}</span>
            <span v-if="form.warehouse"><i class="ti ti-building-warehouse"></i> {{ form.warehouse }}</span>
            <span v-if="form.value"><i class="ti ti-key-off"></i> {{ form.value }}</span>
          </div>
        </div>
        <div class="os-status-banner__actions">
          <span v-if="isEbay && ebayConnectionStatus" class="tag" :class="ebayConnectionStatus.color">
            <i class="ti ti-plug-connected me-1"></i>eBay: {{ ebayConnectionStatus.label }}
          </span>
          <span v-if="isAmazon && amazonConnectionStatus" class="tag" :class="amazonConnectionStatus.color">
            <i class="ti ti-plug-connected me-1"></i>Amazon: {{ amazonConnectionStatus.label }}
          </span>
        </div>
      </div>

      <!-- ===== Tab Navigation ===== -->
      <nav class="os-tabs">
        <button
          v-for="s in sections" :key="s.id" type="button"
          class="os-tab" :class="[{'is-active': activeSection === s.id}, `os-tab--${s.tone}`, {'is-warning': sectionDone[s.id] === false}]"
          @click="activeSection = s.id"
        >
          <i class="ti" :class="s.icon"></i>
          <span class="os-tab__label">{{ s.label }}</span>
          <span v-if="sectionDone[s.id] === false" class="os-tab__dot" title="needs attention">!</span>
        </button>
      </nav>

      <!-- ============================================================= -->
      <!-- 1. GENERAL                                                     -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'general'" class="ff-section ff-section--blue">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-info-circle"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">General</h3>
            <p class="ff-section__sub">Human-readable identity + tenant/organization binding</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('General Section', 'Core identification of this order source.\n\nName is a label shown in lists/logs — cosmetic only.\nSearch Key (Value) is a short business code used in lookups.\nUID is the unique routing identifier — do not change after creation.\n\nOrganization + Warehouse drive access control, accounting dimensions, and default stock reservation warehouse for imported orders.')">
            <i class="ti ti-help-circle"></i> Learn more
          </button>
        </div>

        <div class="columns is-desktop">
          <div class="column is-one-quarter">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-building"></i> Tenant
                <span class="ff-info" data-tip="AD_Client_ID — the top-level iDempiere tenant. Auto-resolved from the current session."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" :value="clientName" class="input ff-input" readonly />
            </div>
          </div>
          <div class="column is-one-quarter">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-building-community"></i> Organization *
                <span class="ff-info" data-tip="AD_Org_ID — the iDempiere organization under which imported orders, partners and documents are created. Wrong org = orders land in the wrong company/branch."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-model="form.organizationId" :options="getOptions?.['orgs']?.records || [{id: form.organizationId, Name: form.organization}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="setOptions('models/ad_org', 'orgs')" @change="(val, opt) => {form.organization = opt.Name; setOptions('filters/m_warehouse/'+string.urlEncode(`ad_org_id eq ${opt.id}`), 'warehouses'); save();}" :loading="loading?.['orgs'] ?? true" />
              </ClientOnly>
            </div>
          </div>
          <div class="column is-one-quarter">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-building-warehouse"></i> Warehouse *
                <span class="ff-info" data-tip="M_Warehouse_ID — which warehouse fulfils orders and from which stock is reserved. Wrong warehouse = wrong reservation and failed picks."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-if="form.organizationId" v-model="form.warehouseId" :options="getOptions?.['warehouses']?.records || [{id: form.warehouseId, Name: form.warehouse}]" prop="Name" dataprop="id" datatype="string" :size="5" @load="setOptions('filters/m_warehouse/'+string.urlEncode(`ad_org_id eq ${form.organizationId}`), 'warehouses')" @change="save" :loading="loading?.['warehouses'] ?? true" />
                <SelectBox v-else v-model="form.warehouseId" :options="warehouses" @change="(val, opt) => {form.warehouse = opt.name; save();}" prop="name" dataprop="id" datatype="string" :size="5" />
              </ClientOnly>
            </div>
          </div>
          <div class="column is-one-quarter">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-key"></i> Search Key
                <span class="ff-info ff-info--right" data-tip="Internal business code (Value). Used in lookups, logs and references. Auto-generated on create — read-only."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.value" class="input ff-input" readonly />
            </div>
          </div>
        </div>

        <div class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-tag"></i> Name *
                <span class="ff-info" data-tip="Human-readable label shown in the order source list and logs. Purely cosmetic. Convention: &quot;Amazon DE – Main&quot; or &quot;eBay Store #1&quot;."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.name" class="input ff-input" />
            </div>
          </div>
          <div class="column is-half">
            <div class="ff-field" v-if="form.uid">
              <label class="ff-label">
                <i class="ti ti-fingerprint"></i> UID
                <span class="ff-info ff-info--right" data-tip="Unique routing identifier used in OAuth callbacks (/order-sources/{uid}). Read-only after creation — changing it breaks existing integrations."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" :value="form.uid" class="input ff-input" readonly />
            </div>
            <div class="ff-field" v-else>
              <label class="ff-label"><i class="ti ti-toggle-right"></i> Status</label>
              <label class="ff-toggle-card" :class="{'is-on': form.isActive}">
                <input type="checkbox" v-model="form.isActive" class="ff-toggle-card__input" @change="save">
                <span class="ff-toggle-card__icon"><i class="ti ti-plug"></i></span>
                <span class="ff-toggle-card__body">
                  <span class="ff-toggle-card__title">{{ form.isActive ? 'Active' : 'Inactive' }}</span>
                  <span class="ff-toggle-card__desc">Deactivated order sources are skipped by every sync job.</span>
                </span>
                <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
              </label>
            </div>
          </div>
        </div>

        <div v-if="form.uid" class="columns is-desktop">
          <div class="column is-full">
            <label class="ff-label"><i class="ti ti-toggle-right"></i> Status</label>
            <label class="ff-toggle-card" :class="{'is-on': form.isActive}">
              <input type="checkbox" v-model="form.isActive" class="ff-toggle-card__input" @change="save">
              <span class="ff-toggle-card__icon"><i class="ti ti-plug"></i></span>
              <span class="ff-toggle-card__body">
                <span class="ff-toggle-card__title">{{ form.isActive ? 'Active' : 'Inactive' }}</span>
                <span class="ff-toggle-card__desc">Deactivated order sources are skipped by every sync job.</span>
              </span>
              <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
            </label>
          </div>
        </div>

        <div class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-file-text"></i> Description
                <span class="ff-info" data-tip="Free-text description. Often used to store the admin URL of the shop for quick reference."><i class="ti ti-info-circle"></i></span>
              </label>
              <textarea v-model="form.description" class="input ff-input" rows="3"></textarea>
            </div>
          </div>
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-notes"></i> Comment / Help
                <span class="ff-info ff-info--right" data-tip="Internal notes visible to operators. Not shared with the marketplace."><i class="ti ti-info-circle"></i></span>
              </label>
              <textarea v-model="form.help" class="input ff-input" rows="3"></textarea>
            </div>
          </div>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 2. CREDENTIALS                                                 -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'credentials'" class="ff-section ff-section--orange">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-key"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Credentials</h3>
            <p class="ff-section__sub">API endpoint + secret material. Labels adapt to the selected marketplace.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('Credentials', 'The meaning of Key / Secret / Token depends on the marketplace:\n\n• Plenty  — Username / Password\n• Amazon  — Client ID / Client Secret / Refresh Token (LWA)\n• eBay    — Client ID / Client Secret / Refresh Token\n• Shopify — API key / Secret (new) or Access Token (REST)\n• Shopware — Client ID / Client Secret\n• JTL     — API Key (token only)\n• Temu    — App Key / App Secret / Access Token\n\nFor Amazon & eBay the token is auto-refreshed from the refresh token. If API calls fail with 401, re-run the OAuth flow from the marketplace-specific tab.')">
            <i class="ti ti-help-circle"></i> Label guide
          </button>
        </div>

        <div class="columns is-desktop">
          <div class="column is-one-third">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-building-store"></i> Marketplace *
                <span class="ff-info" data-tip="Identifies which marketplace adapter and API client to use. Locking this after orders exist prevents sync mismatches."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-model="form.marketplaceId" :options="getOptions?.['marketplaces']?.reflist || [{value: form.marketplaceId, name: form.marketplace}]" prop="name" dataprop="value" datatype="string" :size="5" @click="setOptions('references/marketplace', 'marketplaces')" @change="onMarketplaceChange" :loading="loading?.['marketplaces'] ?? true" />
              </ClientOnly>
              <p v-if="form.id && form.marketplaceId" class="ff-hint"><i class="ti ti-lock"></i> Be careful — changing the marketplace after imports will break existing links.</p>
            </div>
          </div>
          <div v-if="form.marketplaceId && !isJtl" class="column is-two-thirds">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-world-www"></i> Marketplace URL *
                <span class="ff-info ff-info--right" data-tip="Base URL the adapter calls for every request.\n• Shopify: https://your-shop.myshopify.com\n• Shopware: storefront URL\n• Plenty: REST base URL\n• JTL / Xentral: API endpoint\n\nWrong URL = every call fails."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.marketplaceUrl" class="input ff-input" />
            </div>
          </div>
        </div>

        <!-- Key + Secret pair -->
        <div v-if="needsKeyPair" class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-id"></i> {{ keyLabel }} *
                <span class="ff-info" data-tip="Public half of the credential pair. Wrong value = authentication fails."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.marketplaceKey" class="input ff-input" />
            </div>
          </div>
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-lock"></i> {{ secretLabel }} *
                <span class="ff-info ff-info--right" data-tip="Private counterpart to the key. Stored encrypted — rotate it if you suspect a leak."><i class="ti ti-info-circle"></i></span>
              </label>
              <div class="group w-100pct gap-5px">
                <input :type="passwordSwitchers?.['marketplaceSecret'] ? 'text' : 'password'" v-model="form.marketplaceSecret" class="input flexGrow-1" />
                <button type="button" class="button groupItem" @click="passwordSwitchers['marketplaceSecret'] = !passwordSwitchers['marketplaceSecret']">
                  <i class="ti" :class="passwordSwitchers?.['marketplaceSecret'] ? 'ti-eye-off' : 'ti-eye'"></i>
                </button>
              </div>
            </div>
          </div>
        </div>

        <!-- Token -->
        <div v-if="isShopify || isAnyShopify || isJtl || isTemu || isAmazon || isEbay" class="columns is-desktop">
          <div v-if="!isJtl" class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-ticket"></i> {{ tokenLabel }} <span v-if="needsTokenOnly">*</span>
                <span class="ff-info" data-tip="Long-lived token used at runtime. For Amazon / eBay this is the refresh token — short-lived access tokens are derived automatically. If API operations fail with 401, the token has likely expired."><i class="ti ti-info-circle"></i></span>
              </label>
              <div class="group w-100pct gap-5px">
                <input :type="passwordSwitchers?.['token'] ? 'text' : 'password'" v-model="form.marketplaceToken" class="input flexGrow-1" />
                <button type="button" class="button groupItem" @click="passwordSwitchers['token'] = !passwordSwitchers['token']">
                  <i class="ti" :class="passwordSwitchers?.['token'] ? 'ti-eye-off' : 'ti-eye'"></i>
                </button>
              </div>
              <p v-if="isShopify" class="ff-hint"><i class="ti ti-alert-triangle"></i> Shopify permanent access token must be issued by a human install of the app (not system/robot).</p>
            </div>
          </div>
          <div v-if="isJtl" class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-hash"></i> JTL Merchant ID
                <span class="ff-info ff-info--right" data-tip="Scopes API calls to a specific JTL merchant/store."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.jtlMerchantId" class="input ff-input" />
              <p class="ff-hint"><i class="ti ti-info-circle"></i> FFN URL &amp; API Key are inherited from the global fulfiller credentials — only the Merchant ID is needed here.</p>
            </div>
          </div>
          <div v-if="isAmazon" class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-hash"></i> Amazon Merchant ID
                <span class="ff-info ff-info--right" data-tip="Amazon seller/merchant ID (SP-API). Required for all calls — wrong ID returns 403."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.amazonMerchantId" class="input ff-input" />
            </div>
          </div>
        </div>

        <div v-if="!credentialsOk && form.marketplaceId" class="ff-warn-banner mt-3">
          <i class="ti ti-alert-triangle"></i>
          <template v-if="isAmazon">No credentials yet — save the order source, then use "Connect Amazon Account" below (or enter the merchant's own SP-API credentials manually).</template>
          <template v-else>Credentials incomplete — either a Key+Secret pair or an Access Token is required for this marketplace.</template>
        </div>

        <!-- eBay: environment + RuName + connection status / reconnect / test -->
        <div v-if="isEbay" class="os-mp-block mt-4">
          <div class="ff-subhead"><i class="ti ti-brand-ebay"></i> eBay Connection</div>

          <div class="columns is-desktop">
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-test-pipe"></i> Environment *
                  <span class="ff-info" data-tip="Sandbox = test marketplace (no real listings/orders). Production = live."><i class="ti ti-info-circle"></i></span>
                </label>
                <div class="select is-fullwidth">
                  <select v-model="form.ebayEnvironment" class="input ff-input" @change="save">
                    <option value="sandbox">Sandbox</option>
                    <option value="production">Production</option>
                  </select>
                </div>
              </div>
            </div>
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-link"></i> RuName *
                  <span class="ff-info ff-info--right" data-tip="eBay OAuth RuName (Redirect URL Name) registered on your eBay developer account. Wrong value = OAuth redirect fails."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.ebayRuName" class="input ff-input" placeholder="Your-App-RuName" @change="save" />
              </div>
            </div>
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label">Connection Status</label>
                <span class="tag" :class="ebayConnectionStatus?.color">
                  <i class="ti ti-plug-connected me-1"></i>{{ ebayConnectionStatus?.label }}
                </span>
              </div>
            </div>
          </div>

          <div v-if="form.id && form.uid && hasEbayCredentials" class="d-flex gap-10px alignItems-center mt-3 flexWrap-wrap">
            <a :href="`/api/oauth/ebay/authorize/${form.uid}`" class="button is-primary">
              <i class="ti ti-plug me-1"></i>
              {{ isEbayConnected ? 'Reconnect eBay Account' : 'Connect eBay Account' }}
            </a>
            <button v-if="isEbayConnected" class="button is-info is-outlined" :class="{ 'is-loading': ebayTesting }" :disabled="ebayTesting" @click="testEbayConnection">
              <i class="ti ti-plug-connected me-1"></i> Test Connection
            </button>
          </div>
          <div v-else-if="form.id && !hasEbayCredentials" class="ff-warn-banner mt-3">
            <i class="ti ti-alert-triangle"></i> Save the order source with Client ID, Client Secret, and RuName to enable the Connect button.
          </div>
          <p v-else-if="!form.id" class="ff-hint mt-2"><i class="ti ti-info-circle"></i> Save this order source first, then return to connect eBay.</p>

          <div v-if="ebayTestResult.status === 'success'" class="notification is-light mt-3 p-3">
            <p class="help has-text-success"><strong>{{ ebayTestResult.message }}</strong></p>
            <table v-if="ebayTestResult.details" class="table is-narrow is-fullwidth is-size-7 mb-0">
              <tbody>
                <tr v-if="ebayTestResult.details.username"><td class="has-text-weight-semibold" style="width: 160px;">Username</td><td>{{ ebayTestResult.details.username }}</td></tr>
                <tr v-if="ebayTestResult.details.email"><td class="has-text-weight-semibold">Email</td><td>{{ ebayTestResult.details.email }}</td></tr>
                <tr v-if="ebayTestResult.details.accountType"><td class="has-text-weight-semibold">Account Type</td><td>{{ ebayTestResult.details.accountType }}</td></tr>
                <tr v-if="ebayTestResult.details.registrationMarketplaceId"><td class="has-text-weight-semibold">Marketplace</td><td>{{ ebayTestResult.details.registrationMarketplaceId }}</td></tr>
                <tr v-if="ebayTestResult.details.businessAccount?.name"><td class="has-text-weight-semibold">Business Name</td><td>{{ ebayTestResult.details.businessAccount.name }}</td></tr>
                <tr v-if="ebayTestResult.details.status"><td class="has-text-weight-semibold">Status</td><td>{{ ebayTestResult.details.status }}</td></tr>
              </tbody>
            </table>
          </div>
          <p v-else-if="ebayTestResult.status === 'error'" class="help has-text-danger mt-2">{{ ebayTestResult.message }}</p>
        </div>

        <!-- Amazon: connect via LogShip app (OAuth) / manual credentials / test -->
        <div v-if="isAmazon" class="os-mp-block mt-4">
          <div class="ff-subhead"><i class="ti ti-brand-amazon"></i> Amazon Connection</div>
          <div class="columns is-desktop">
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-world"></i> Seller Central Region
                  <span class="ff-info" data-tip="Which Seller Central the merchant logs into to authorize LogShip. Pick the merchant's home region — Germany for most sellers."><i class="ti ti-info-circle"></i></span>
                </label>
                <div class="select is-fullwidth">
                  <select v-model="amazonRegion" class="input ff-input">
                    <option value="de">Germany (sellercentral.amazon.de)</option>
                    <option value="eu">Europe (sellercentral-europe.amazon.com)</option>
                    <option value="uk">United Kingdom</option>
                    <option value="us">North America</option>
                  </select>
                </div>
              </div>
            </div>
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label">Connection Status</label>
                <span class="tag" :class="amazonConnectionStatus?.color">
                  <i class="ti ti-plug-connected me-1"></i>{{ amazonConnectionStatus?.label }}
                </span>
              </div>
            </div>
          </div>

          <div v-if="form.id && form.uid" class="d-flex gap-10px alignItems-center mt-3 flexWrap-wrap">
            <a :href="`/api/oauth/amazon/authorize/${form.uid}?region=${amazonRegion}`" class="button is-primary">
              <i class="ti ti-plug me-1"></i>
              {{ form.marketplaceToken ? 'Reconnect Amazon Account' : 'Connect Amazon Account' }}
            </a>
            <button v-if="hasAmazonCredentials" class="button is-info is-outlined" :class="{ 'is-loading': amazonTesting }" :disabled="amazonTesting" @click="testAmazonConnection">
              <i class="ti ti-plug-connected me-1"></i> Test Connection & Check Access
            </button>
          </div>
          <p v-else class="ff-hint mt-2"><i class="ti ti-info-circle"></i> Save this order source first, then return to connect Amazon.</p>
          <p class="ff-hint mt-2"><i class="ti ti-info-circle"></i> Two ways to connect: <strong>Connect Amazon Account</strong> lets the merchant authorize their seller account through the LogShip app (no Amazon developer account needed) — alternatively enter the merchant's own SP-API app credentials (Client ID / Secret / Refresh Token) above.</p>

          <div v-if="form.id" class="mt-3">
            <p v-if="amazonTestResult.status === 'success'" class="help has-text-success mt-2">{{ amazonTestResult.message }}</p>
            <p v-else-if="amazonTestResult.status === 'error'" class="help has-text-danger mt-2">{{ amazonTestResult.message }}</p>

            <div v-if="amazonTestResult.details?.summary" class="mt-3 box" style="padding: 1rem;">
              <p class="has-text-weight-bold mb-2">Access Summary</p>
              <table class="table is-narrow is-fullwidth is-size-7">
                <tbody>
                  <tr><td>Authentication</td><td><span class="tag is-success is-small">OK</span></td></tr>
                  <tr><td>Marketplaces</td><td>{{ amazonTestResult.details.summary.marketplaceCount }} marketplace(s)</td></tr>
                  <tr><td>Invoice Upload (UPLOAD_VAT_INVOICE)</td><td>
                    <span v-if="amazonTestResult.details.summary.invoiceUploadReady" class="tag is-success is-small">Accessible</span>
                    <span v-else class="tag is-danger is-small">Denied - needs "Tax Invoicing (Restricted)" role</span>
                  </td></tr>
                </tbody>
              </table>

              <p class="has-text-weight-bold mb-2 mt-3">API Access</p>
              <table class="table is-narrow is-fullwidth is-size-7">
                <thead><tr><th>API</th><th>Status</th></tr></thead>
                <tbody>
                  <tr v-for="api in amazonTestResult.details.apiAccess" :key="api.name">
                    <td>{{ api.name }}</td>
                    <td>
                      <span v-if="api.accessible" class="tag is-success is-small">OK</span>
                      <span v-else class="tag is-danger is-small">{{ api.error }}</span>
                    </td>
                  </tr>
                </tbody>
              </table>

              <div v-if="amazonTestResult.details.marketplaces?.length > 0" class="mt-3">
                <p class="has-text-weight-bold mb-2">Marketplaces</p>
                <table class="table is-narrow is-fullwidth is-size-7">
                  <thead><tr><th>Marketplace</th><th>Country</th><th>ID</th><th>Active</th></tr></thead>
                  <tbody>
                    <tr v-for="mp in amazonTestResult.details.marketplaces" :key="mp.marketplaceId">
                      <td>{{ mp.marketplace }}</td>
                      <td>{{ mp.country }}</td>
                      <td>{{ mp.marketplaceId }}</td>
                      <td>
                        <span v-if="mp.isParticipating" class="tag is-success is-small">Yes</span>
                        <span v-else class="tag is-warning is-small">No</span>
                      </td>
                    </tr>
                  </tbody>
                </table>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 3. SYNC DIRECTION                                              -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'sync'" class="ff-section ff-section--green">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-arrows-exchange-2"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Sync Direction</h3>
            <p class="ff-section__sub">Master on/off switches for every data flow between iDempiere and the marketplace.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('Sync Direction', 'Each toggle controls ONE direction of ONE entity. If both directions are on for the same entity, the system uses update timestamps to avoid loops.\n\n• Product TO — push products from iDempiere → marketplace (listings, prices, stock).\n• Product FROM — import products marketplace → iDempiere (typically only for onboarding).\n• Order TO — push order status updates outward.\n• Order FROM — import new orders from the marketplace. This is the CRITICAL flag — if off, no orders come in.\n• Shipment TO — push tracking to the marketplace so the buyer gets updates.\n• Shipment FROM — import shipment data inward (rarely used).\n• Webshop TO / FROM — Shopware-specific legacy flags that gate the webshop adapter.')">
            <i class="ti ti-help-circle"></i> How it works
          </button>
        </div>

        <!-- Visual sync matrix: iDempiere ↔ Marketplace -->
        <div class="os-sync-matrix">
          <div class="os-sync-col os-sync-col--left">
            <div class="os-sync-col__head">
              <i class="ti ti-database"></i>
              <span>iDempiere</span>
            </div>
          </div>
          <div class="os-sync-col os-sync-col--center">
            <div class="os-sync-row">
              <span class="os-sync-entity"><i class="ti ti-box"></i> Product</span>
              <div class="os-sync-arrows">
                <button type="button" class="os-arrow os-arrow--left" :class="{'is-on': form.syncProductToIdempiere}" @click="form.syncProductToIdempiere = !form.syncProductToIdempiere; save()" title="Marketplace → iDempiere">
                  <i class="ti ti-arrow-left"></i>
                </button>
                <button type="button" class="os-arrow os-arrow--right" :class="{'is-on': form.syncProductFromIdempiere}" @click="form.syncProductFromIdempiere = !form.syncProductFromIdempiere; save()" title="iDempiere → Marketplace">
                  <i class="ti ti-arrow-right"></i>
                </button>
              </div>
            </div>
            <div class="os-sync-row">
              <span class="os-sync-entity"><i class="ti ti-receipt-2"></i> Order</span>
              <div class="os-sync-arrows">
                <button type="button" class="os-arrow os-arrow--left" :class="{'is-on': form.syncOrderToIdempiere}" @click="form.syncOrderToIdempiere = !form.syncOrderToIdempiere; save()" title="Marketplace → iDempiere (CRITICAL)">
                  <i class="ti ti-arrow-left"></i>
                </button>
                <button type="button" class="os-arrow os-arrow--right" :class="{'is-on': form.syncOrderFromIdempiere}" @click="form.syncOrderFromIdempiere = !form.syncOrderFromIdempiere; save()" title="iDempiere → Marketplace">
                  <i class="ti ti-arrow-right"></i>
                </button>
              </div>
            </div>
            <div class="os-sync-row">
              <span class="os-sync-entity"><i class="ti ti-truck-delivery"></i> Shipment</span>
              <div class="os-sync-arrows">
                <button type="button" class="os-arrow os-arrow--left" :class="{'is-on': form.syncShipmentToIdempiere}" @click="form.syncShipmentToIdempiere = !form.syncShipmentToIdempiere; save()" title="Marketplace → iDempiere">
                  <i class="ti ti-arrow-left"></i>
                </button>
                <button type="button" class="os-arrow os-arrow--right" :class="{'is-on': form.syncShipmentFromIdempiere}" @click="form.syncShipmentFromIdempiere = !form.syncShipmentFromIdempiere; save()" title="iDempiere → Marketplace">
                  <i class="ti ti-arrow-right"></i>
                </button>
              </div>
            </div>
            <div v-if="isShopware" class="os-sync-row">
              <span class="os-sync-entity"><i class="ti ti-shopping-cart"></i> Webshop</span>
              <div class="os-sync-arrows">
                <button type="button" class="os-arrow os-arrow--left" :class="{'is-on': form.isWebshopToIdempiere}" @click="form.isWebshopToIdempiere = !form.isWebshopToIdempiere; save()" title="Webshop → iDempiere">
                  <i class="ti ti-arrow-left"></i>
                </button>
                <button type="button" class="os-arrow os-arrow--right" :class="{'is-on': form.isIdempiereToWebshop}" @click="form.isIdempiereToWebshop = !form.isIdempiereToWebshop; save()" title="iDempiere → Webshop">
                  <i class="ti ti-arrow-right"></i>
                </button>
              </div>
            </div>
          </div>
          <div class="os-sync-col os-sync-col--right">
            <div class="os-sync-col__head">
              <i class="ti ti-building-store"></i>
              <span>{{ form.marketplace || 'Marketplace' }}</span>
            </div>
          </div>
        </div>

        <div v-if="!form.syncOrderFromIdempiere" class="ff-warn-banner mt-3">
          <i class="ti ti-alert-triangle"></i>
          <strong>Order FROM marketplace is OFF</strong> — no new orders will be imported. This is usually the critical flag you want enabled.
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 4. iDEMPIERE MAPPING                                           -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'idempiere'" class="ff-section ff-section--purple">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-database-export"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">iDempiere Mapping</h3>
            <p class="ff-section__sub">How imported orders are constructed inside iDempiere.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('iDempiere Mapping', 'Price List drives the prices, currency, and whether tax is already included at iDempiere level.\n\nPrice List Version pins orders to a specific version — useful when prices are rolling over but existing orders must keep the old price.\n\nInvoice Rule determines WHEN an invoice is generated from the order:\n  • Immediate — invoice at order creation (classic e-commerce default)\n  • After Delivery — invoice once the shipment is posted\n  • After Order Delivered — invoice only when the full order is delivered\n  • Customer Schedule — invoice on a recurring calendar (B2B)\n\nTax Included declares whether marketplace prices are gross (true) or net (false). Wrong value = tax double-counted or missing on the iDempiere invoice.')">
            <i class="ti ti-help-circle"></i> Impact guide
          </button>
        </div>

        <div class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-tag"></i> Price List <small class="has-text-grey">(order import)</small>
                <span class="ff-info" data-tip="M_PriceList_ID — price list applied to imported ORDER lines. Determines currency and tax-inclusive/exclusive pricing."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-model="form.priceListId" :options="getOptions?.['price_lists']?.records || [{id: form.priceListId, Name: form.priceList}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="setOptions('models/m_pricelist', 'price_lists')" @change="(val, opt) => {form.priceList = opt.Name; setOptions('filters/m_pricelist_version/'+string.urlEncode(`m_pricelist_id eq ${opt.id}`), 'price_list_versions'); save();}" :loading="loading?.['price_lists'] ?? true" />
              </ClientOnly>
            </div>
          </div>
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-versions"></i> Price List Version <small class="has-text-grey">(order import)</small>
                <span class="ff-info ff-info--right" data-tip="Specific version of the order price list. Usually auto-selected to the latest active version."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-if="form.priceListId" v-model="form.priceListVersionId" :options="getOptions?.['price_list_versions']?.records || [{id: form.priceListVersionId, Name: form.priceListVersion}]" prop="Name" dataprop="id" datatype="string" :size="5" @load="setOptions('filters/m_pricelist_version/'+string.urlEncode(`m_pricelist_id eq ${form.priceListId}`), 'price_list_versions')" @change="(val, opt) => {form.priceListVersion = opt.Name; save();}" :loading="loading?.['price_list_versions'] ?? true" />
                <input v-else :value="form.priceListVersion" class="input ff-input" readonly />
              </ClientOnly>
            </div>
          </div>
        </div>

        <!-- Product-sync price list (separate from the order price list above) -->
        <div class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-tag"></i> Price List <small class="has-text-grey">(product sync)</small>
                <span class="ff-info" data-tip="M_PriceList_Product_ID — used only to pick the version below. Products created during PRODUCT sync/import are priced into the chosen version (separate from the order price list)."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-model="form.priceListProductId" :options="getOptions?.['price_lists']?.records || [{id: form.priceListProductId, Name: form.priceListProduct}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="setOptions('models/m_pricelist', 'price_lists')" @change="(val, opt) => {form.priceListProduct = opt.Name; setOptions('filters/m_pricelist_version/'+string.urlEncode(`m_pricelist_id eq ${opt.id}`), 'price_list_versions_product'); save();}" :loading="loading?.['price_lists'] ?? true" />
              </ClientOnly>
            </div>
          </div>
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-versions"></i> Price List Version <small class="has-text-grey">(product sync)</small>
                <span class="ff-info ff-info--right" data-tip="M_PriceList_Version_Product_ID — the version products are priced into during product sync/import. This is the value the importer actually sends."><i class="ti ti-info-circle"></i></span>
              </label>
              <ClientOnly>
                <SelectBox v-if="form.priceListProductId" v-model="form.priceListVersionProductId" :options="getOptions?.['price_list_versions_product']?.records || [{id: form.priceListVersionProductId, Name: form.priceListVersionProduct}]" prop="Name" dataprop="id" datatype="string" :size="5" @load="setOptions('filters/m_pricelist_version/'+string.urlEncode(`m_pricelist_id eq ${form.priceListProductId}`), 'price_list_versions_product')" @change="(val, opt) => {form.priceListVersionProduct = opt.Name; save();}" :loading="loading?.['price_list_versions_product'] ?? true" />
                <input v-else :value="form.priceListVersionProduct" class="input ff-input" readonly />
              </ClientOnly>
            </div>
          </div>
        </div>

        <div class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-file-invoice"></i> Invoice Rule
                <span class="ff-info" data-tip="When to generate the invoice. Major impact on accounting and cash flow timing. Default = Immediate."><i class="ti ti-info-circle"></i></span>
              </label>
              <div class="select is-fullwidth">
                <select v-model="form.invoiceRuleId" class="input ff-input" @change="save">
                  <option value="I">Immediate (at order creation)</option>
                  <option value="D">After Delivery</option>
                  <option value="O">After Order Delivered</option>
                  <option value="S">Customer Schedule After Delivery</option>
                </select>
              </div>
            </div>
          </div>
          <div class="column is-half">
            <label class="ff-label"><i class="ti ti-percentage"></i> Tax Handling</label>
            <label class="ff-toggle-card" :class="{'is-on': form.isTaxIncluded}">
              <input type="checkbox" v-model="form.isTaxIncluded" class="ff-toggle-card__input" @change="save">
              <span class="ff-toggle-card__icon"><i class="ti ti-receipt-tax"></i></span>
              <span class="ff-toggle-card__body">
                <span class="ff-toggle-card__title">Tax Included (gross prices)</span>
                <span class="ff-toggle-card__desc">Marketplace sends gross prices (tax already in). Turn OFF for net-priced B2B marketplaces.</span>
              </span>
              <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
            </label>
          </div>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 5. ORDER PROCESSING                                            -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'processing'" class="ff-section ff-section--yellow">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-adjustments-alt"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Order Processing</h3>
            <p class="ff-section__sub">Rules applied when imported orders are processed inside iDempiere.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('Order Processing', 'Starting Order Date acts as a cursor — orders older than this are ignored by the importer. Setting it too far back will re-import huge history.\n\nRemove Article String lets you strip a marker prefix/suffix from marketplace SKUs before lookup in iDempiere.\n\nBoM Convert expands bundle SKUs into components — stock is reserved at component level instead of the bundle parent.\n\nSkip Confirmation processes orders straight through, removing the manual iDempiere checkpoint.\n\nExclude Tracking Mail prevents iDempiere from sending tracking emails (useful when the marketplace already notifies the buyer — avoids duplicates).\n\nCustom Tracking Mail + Sent Custom Tracking Mail control the custom tracking email workflow.')">
            <i class="ti ti-help-circle"></i> Rule details
          </button>
        </div>

        <div class="columns is-desktop">
          <div class="column is-one-third">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-number"></i> Starting Order ID
                <span class="ff-info" data-tip="Cursor — only import orders with an ID greater than this. Leave blank to use the date cursor."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.startingOrderId" class="input ff-input" />
            </div>
          </div>
          <div class="column is-one-third">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-calendar-event"></i> Starting Order Date
                <span class="ff-info" data-tip="Date cursor — orders older than this are ignored. Too low = re-imports history; too high = skips orders."><i class="ti ti-info-circle"></i></span>
              </label>
              <DatePicker v-model="form.startingOrderDate" />
            </div>
          </div>
          <div class="column is-one-third">
            <div class="ff-field">
              <label class="ff-label">
                <i class="ti ti-eraser"></i> Remove Article String
                <span class="ff-info ff-info--right" data-tip="Substring stripped from marketplace SKUs before matching iDempiere products. Useful when the marketplace adds a prefix/suffix."><i class="ti ti-info-circle"></i></span>
              </label>
              <input type="text" v-model="form.removeArticleString" class="input ff-input" />
            </div>
          </div>
        </div>

        <div class="ff-subhead mt-4"><i class="ti ti-settings"></i> Behavior Flags</div>
        <div class="os-flag-grid">
          <label class="ff-toggle-card" :class="{'is-on': form.isBoMConvert}">
            <input type="checkbox" v-model="form.isBoMConvert" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-components"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">BoM Convert</span>
              <span class="ff-toggle-card__desc">Expand bundle SKUs into components; stock is reserved per component.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label v-if="isJtl" class="ff-toggle-card" :class="{'is-on': form.isJtlBomSkip}">
            <input type="checkbox" v-model="form.isJtlBomSkip" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-arrows-split"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">JTL BOM → components (flatten)</span>
              <span class="ff-toggle-card__desc">On import, drop the JTL bundle master and move its price onto the child items already in the order — no iDempiere BOM needed. Mutually exclusive with BoM Convert.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label v-if="isJtl" class="ff-toggle-card" :class="{'is-on': form.includeBomSync}">
            <input type="checkbox" v-model="form.includeBomSync" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-stack-2"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Include BOM articles in product sync (JTL)</span>
              <span class="ff-toggle-card__desc">ON: the product sync imports ALL articles received from JTL-FFN, including BOM articles (Stücklisten). OFF: BOM articles are excluded — only regular articles are imported and pushed to iDempiere.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label class="ff-toggle-card" :class="{'is-on': form.isSkipConfirmation}">
            <input type="checkbox" v-model="form.isSkipConfirmation" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-rocket"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Skip Confirmation</span>
              <span class="ff-toggle-card__desc">Auto-advance imported orders — removes the manual iDempiere checkpoint.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label class="ff-toggle-card" :class="{'is-on': form.isExcludetrackingmail}">
            <input type="checkbox" v-model="form.isExcludetrackingmail" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-mail-off"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Exclude Tracking Mail</span>
              <span class="ff-toggle-card__desc">Suppress tracking emails — use when the marketplace notifies the buyer itself.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label class="ff-toggle-card" :class="{'is-on': form.isCustomTrackingMail}">
            <input type="checkbox" v-model="form.isCustomTrackingMail" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-mail-star"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Custom Tracking Mail</span>
              <span class="ff-toggle-card__desc">Enable custom tracking email template for this source.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label class="ff-toggle-card" :class="{'is-on': form.isSentCustomTrackingMail}" v-if="form.isCustomTrackingMail">
            <input type="checkbox" v-model="form.isSentCustomTrackingMail" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-mail-check"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Custom Tracking Mail Sent (flag)</span>
              <span class="ff-toggle-card__desc">Marks that the custom tracking email has already been dispatched.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>

          <label v-if="isEbay" class="ff-toggle-card" :class="{'is-on': form.isCheckoutNoteKeepOrderOpen}">
            <input type="checkbox" v-model="form.isCheckoutNoteKeepOrderOpen" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-message-circle-exclamation"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Keep order open when buyer added checkout notes (eBay)</span>
              <span class="ff-toggle-card__desc">Import as Drafted (DR) instead of Completed when the buyer left a checkout note; the note is written to the order description for review. Currently applied to eBay orders only.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 6. SHIPPING PROFILES (Plenty only)                             -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'shipping'" class="ff-section ff-section--teal">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-truck-delivery"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Shipping Profile Mapping</h3>
            <p class="ff-section__sub">Translate marketplace shipping codes into iDempiere shippers.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('Shipping Profiles', 'When an order arrives with an external shipping code X, the middleware scans the 8 slots LAST → FIRST and picks the first matching slot, then sets that M_Shipper_ID on the iDempiere order. If nothing matches, it falls back to DHL (shipper ID 1000000).\n\nTip: put your most specific codes in the higher-numbered slots — they are evaluated first.')">
            <i class="ti ti-help-circle"></i> Match logic
          </button>
        </div>

        <div class="os-profile-grid">
          <div v-for="i in 8" :key="i" class="os-profile-row">
            <div class="os-profile-row__idx">{{ i }}</div>
            <div class="ff-field ff-field--inline">
              <label class="ff-label ff-label--sm">External Code</label>
              <input type="text" v-model="form[`externalShippingProfile${i}`]" class="input ff-input" placeholder="e.g. DHL_PAKET" />
            </div>
            <div class="os-profile-row__arrow"><i class="ti ti-arrow-narrow-right"></i></div>
            <div class="ff-field ff-field--inline" :class="{'ff-disabled-area': !form[`externalShippingProfile${i}`]}">
              <label class="ff-label ff-label--sm">iDempiere Shipper</label>
              <ClientOnly>
                <SelectBox v-model="form[`shipperCustom${i}Id`]" :options="getOptions?.[`shipper_custom_${i}`]?.records || [{id: form[`shipperCustom${i}Id`], Name: form[`shipperCustom${i}`]}]" prop="Name" dataprop="id" datatype="string" :size="5" @click="setOptions('models/m_shipper', `shipper_custom_${i}`)" @change="save" :loading="loading?.[`shipper_custom_${i}`] ?? true" />
              </ClientOnly>
            </div>
          </div>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 7. DHL CUSTOM SHIPPING                                         -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'dhl'" class="ff-section ff-section--gray">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-package"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">DHL Custom Shipping</h3>
            <p class="ff-section__sub">Use a dedicated DHL account for label generation instead of the tenant default.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('DHL Custom', 'When enabled, this order source generates DHL labels using the credentials entered below instead of the tenant-level DHL account. Leave OFF to use the shared DHL account configured globally.')">
            <i class="ti ti-help-circle"></i> When to use
          </button>
        </div>

        <label class="ff-toggle-card" :class="{'is-on': form.DHLUseCustom}">
          <input type="checkbox" v-model="form.DHLUseCustom" class="ff-toggle-card__input" @change="save">
          <span class="ff-toggle-card__icon"><i class="ti ti-truck"></i></span>
          <span class="ff-toggle-card__body">
            <span class="ff-toggle-card__title">Use Custom DHL Account</span>
            <span class="ff-toggle-card__desc">Overrides tenant defaults with the credentials below.</span>
          </span>
          <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
        </label>

        <template v-if="form.DHLUseCustom">
          <div class="columns is-desktop mt-3">
            <div class="column is-full">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-world-www"></i> DHL URL</label>
                <input type="text" v-model="form.DHLURL" class="input ff-input" placeholder="https://api-eu.dhl.com/parcel/de" />
              </div>
            </div>
          </div>
          <div class="columns is-desktop">
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-id"></i> DHL Key</label>
                <input type="text" v-model="form.DHLKey" class="input ff-input" />
              </div>
            </div>
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-lock"></i> DHL Secret</label>
                <div class="group w-100pct gap-5px">
                  <input :type="passwordSwitchers?.['DHLSecret'] ? 'text' : 'password'" v-model="form.DHLSecret" class="input flexGrow-1" />
                  <button type="button" class="button groupItem" @click="passwordSwitchers['DHLSecret'] = !passwordSwitchers['DHLSecret']">
                    <i class="ti" :class="passwordSwitchers?.['DHLSecret'] ? 'ti-eye-off' : 'ti-eye'"></i>
                  </button>
                </div>
              </div>
            </div>
          </div>
          <div class="columns is-desktop">
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-user"></i> DHL User</label>
                <input type="text" v-model="form.DHLUser" class="input ff-input" />
              </div>
            </div>
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-password"></i> DHL Password</label>
                <div class="group w-100pct gap-5px">
                  <input :type="passwordSwitchers?.['DHLPass'] ? 'text' : 'password'" v-model="form.DHLPass" class="input flexGrow-1" />
                  <button type="button" class="button groupItem" @click="passwordSwitchers['DHLPass'] = !passwordSwitchers['DHLPass']">
                    <i class="ti" :class="passwordSwitchers?.['DHLPass'] ? 'ti-eye-off' : 'ti-eye'"></i>
                  </button>
                </div>
              </div>
            </div>
          </div>
          <div class="columns is-desktop">
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-receipt"></i> DHL Billing Number (Germany)
                  <span class="ff-info" data-tip="EKP/Abrechnungsnummer used for domestic DE parcels."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.DHLBillingNumberDE" class="input ff-input" />
              </div>
            </div>
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-world"></i> DHL Billing Number (International)
                  <span class="ff-info ff-info--right" data-tip="EKP/Abrechnungsnummer used for international parcels."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.DHLBillingNumberINT" class="input ff-input" />
              </div>
            </div>
          </div>
        </template>
      </div>

      <!-- ============================================================= -->
      <!-- 7b. DPD CUSTOM SHIPPING                                        -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'dpd'" class="ff-section ff-section--gray">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-package"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">DPD Custom Shipping</h3>
            <p class="ff-section__sub">Use a dedicated DPD WebConnect account for label generation instead of the tenant default.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('DPD Custom', 'When enabled, this order source generates DPD labels using the DELIS-Id and password entered below instead of the tenant-level DPD account. Leave OFF to use the shared DPD account configured globally via .env. DPD and DHL custom toggles are independent — both can be active at the same time, and the carrier is selected per shipment based on M_Shipper.')">
            <i class="ti ti-help-circle"></i> When to use
          </button>
        </div>

        <label class="ff-toggle-card" :class="{'is-on': form.DPDUseCustom}">
          <input type="checkbox" v-model="form.DPDUseCustom" class="ff-toggle-card__input" @change="save">
          <span class="ff-toggle-card__icon"><i class="ti ti-truck"></i></span>
          <span class="ff-toggle-card__body">
            <span class="ff-toggle-card__title">Use Custom DPD Account</span>
            <span class="ff-toggle-card__desc">Overrides tenant defaults with the credentials below.</span>
          </span>
          <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
        </label>

        <template v-if="form.DPDUseCustom">
          <!-- DPD URL is hidden: defaults to https://public-ws.dpd.com/services for all prod accounts -->
          <div class="columns is-desktop mt-3">
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-id"></i> DELIS-Id
                  <span class="ff-info" data-tip="DPD WebConnect username (8–10 digits)."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.DPDDelisId" class="input ff-input" placeholder="2406117511" />
              </div>
            </div>
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-password"></i> DPD Password</label>
                <div class="group w-100pct gap-5px">
                  <input :type="passwordSwitchers?.['DPDPassword'] ? 'text' : 'password'" v-model="form.DPDPassword" class="input flexGrow-1" />
                  <button type="button" class="button groupItem" @click="passwordSwitchers['DPDPassword'] = !passwordSwitchers['DPDPassword']">
                    <i class="ti" :class="passwordSwitchers?.['DPDPassword'] ? 'ti-eye-off' : 'ti-eye'"></i>
                  </button>
                </div>
              </div>
            </div>
          </div>
          <div class="columns is-desktop">
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-receipt"></i> DPD Customer Number
                  <span class="ff-info" data-tip="Kundennummer for sender's address on the label (max 17 chars)."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.DPDCustomerNumber" class="input ff-input" placeholder="90000002406117511" />
              </div>
            </div>
            <div class="column is-half">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-building-warehouse"></i> Sending Depot
                  <span class="ff-info ff-info--right" data-tip="Optional 4-digit depot override (e.g. 0163). Leave blank to use the depot returned by DPD's login response."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.DPDDepot" class="input ff-input" maxlength="4" placeholder="auto" />
              </div>
            </div>
          </div>
        </template>
      </div>

      <!-- ============================================================= -->
      <!-- 8. MARKETPLACE SPECIFIC                                        -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'marketplace'" class="ff-section ff-section--pink">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-building-store"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Marketplace-Specific Settings</h3>
            <p class="ff-section__sub">Only the block matching the selected marketplace is shown.</p>
          </div>
        </div>

        <!-- Shopware -->
        <div v-if="isShopware" class="os-mp-block">
          <div class="ff-subhead"><i class="ti ti-brand-shopware"></i> Shopware</div>
          <label class="ff-toggle-card" :class="{'is-on': form.isShopware6}">
            <input type="checkbox" v-model="form.isShopware6" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-versions"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Shopware 6</span>
              <span class="ff-toggle-card__desc">Use Shopware 6 admin API instead of legacy Shopware 5.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>
        </div>

        <!-- Shopify -->
        <div v-if="isAnyShopify" class="os-mp-block">
          <div class="ff-subhead"><i class="ti ti-brand-shopify"></i> Shopify</div>
          <div class="columns is-desktop">
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-map-pin"></i> Shopify Location ID
                  <span class="ff-info" data-tip="Inventory location in Shopify. Wrong ID = stock updates go to the wrong warehouse or fail silently."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.shopifyLocationId" class="input ff-input" placeholder="e.g. 106246734090" />
              </div>
            </div>
          </div>
          <div class="os-flag-grid">
            <label class="ff-toggle-card" :class="{'is-on': form.isShopifyUpdatedMin}">
              <input type="checkbox" v-model="form.isShopifyUpdatedMin" class="ff-toggle-card__input" @change="save">
              <span class="ff-toggle-card__icon"><i class="ti ti-clock"></i></span>
              <span class="ff-toggle-card__body">
                <span class="ff-toggle-card__title">Updated-Min Cursor</span>
                <span class="ff-toggle-card__desc">Use the Shopify <code>updated_at_min</code> cursor for incremental sync.</span>
              </span>
              <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
            </label>
            <label class="ff-toggle-card" :class="{'is-on': form.isCheckShopifyFulfillmentService}">
              <input type="checkbox" v-model="form.isCheckShopifyFulfillmentService" class="ff-toggle-card__input" @change="save">
              <span class="ff-toggle-card__icon"><i class="ti ti-truck-loading"></i></span>
              <span class="ff-toggle-card__body">
                <span class="ff-toggle-card__title">Check Fulfillment Service</span>
                <span class="ff-toggle-card__desc">Require the order to be fulfilled by our service before importing.</span>
              </span>
              <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
            </label>
            <label class="ff-toggle-card" :class="{'is-on': form.saveSkuBack}">
              <input type="checkbox" v-model="form.saveSkuBack" class="ff-toggle-card__input" @change="save">
              <span class="ff-toggle-card__icon"><i class="ti ti-arrow-back-up"></i></span>
              <span class="ff-toggle-card__body">
                <span class="ff-toggle-card__title">Save SKU Back</span>
                <span class="ff-toggle-card__desc">After product sync, write the iDempiere SKU back onto the Shopify variant.</span>
              </span>
              <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
            </label>
            <label class="ff-toggle-card" :class="{'is-on': form.shopifyIgnoreLocation}">
              <input type="checkbox" v-model="form.shopifyIgnoreLocation" class="ff-toggle-card__input" @change="save">
              <span class="ff-toggle-card__icon"><i class="ti ti-map-off"></i></span>
              <span class="ff-toggle-card__body">
                <span class="ff-toggle-card__title">Ignore Location</span>
                <span class="ff-toggle-card__desc">Skip location validation when pushing inventory — use only if Shopify side is inconsistent.</span>
              </span>
              <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
            </label>
          </div>

          <!-- Product sync (shopify-new only): the automatic sync imports newly created
               articles; this queues a background FULL sync over all articles. -->
          <div v-if="isShopifyNew && form.id" class="os-testpanel">
            <div class="os-testpanel__head">
              <span class="os-testpanel__title"><i class="ti ti-packages"></i> Product Sync</span>
              <span v-if="shopifySyncing" class="os-testpanel__status">Queuing…</span>
              <button v-else-if="shopifySyncResult.status" type="button" class="os-tbtn os-tbtn--ghost os-tbtn--xs" @click="shopifySyncClearResult">
                <i class="ti ti-x"></i><span>Clear</span>
              </button>
            </div>
            <div class="os-testbtns">
              <button type="button" class="os-tbtn os-tbtn--primary" :disabled="shopifySyncing" @click="startShopifyFullSync" title="Queue a FULL product sync: every Shopify article is imported/updated in iDempiere, not only recently created ones. Runs in the background — if the scheduled sync is currently running, it waits instead of interfering.">
                <i :class="shopifySyncing ? 'ti ti-loader-2 os-spin' : 'ti ti-refresh'"></i><span>Full sync (all articles)</span>
              </button>
            </div>
            <p class="os-testpanel__hint">
              <i class="ti ti-info-circle"></i>
              <span>The automatic sync only imports <strong>newly created</strong> articles. Use this to (re)import <strong>all</strong> articles once — it runs in the background and never at the same time as the scheduled sync. Progress appears in the middleware logs.</span>
            </p>
            <div v-if="shopifySyncResult.status" class="os-testresult">
              <div class="notification" :class="shopifySyncResult.status === 'success' ? 'is-success is-light' : 'is-danger is-light'">{{ shopifySyncResult.message }}</div>
            </div>
          </div>
        </div>

        <!-- Amazon -->
        <div v-if="isAmazon" class="os-mp-block">
          <div class="ff-subhead"><i class="ti ti-brand-amazon"></i> Amazon SP-API</div>
          <div class="columns is-desktop">
            <div class="column is-full">
              <label class="ff-toggle-card" :class="{'is-on': form.isAmazonOrderReport}">
                <input type="checkbox" v-model="form.isAmazonOrderReport" class="ff-toggle-card__input" @change="save">
                <span class="ff-toggle-card__icon"><i class="ti ti-report"></i></span>
                <span class="ff-toggle-card__body">
                  <span class="ff-toggle-card__title">Use Order Reports (bulk)</span>
                  <span class="ff-toggle-card__desc">Switch from real-time API to bulk Amazon Order Reports. Better for historical / high-volume imports; worse for low-latency fulfilment.</span>
                </span>
                <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
              </label>
            </div>
          </div>

          <div class="ff-info-banner ff-info-banner--blue mt-3">
            <span class="ff-info-banner__icon"><i class="ti ti-info-circle"></i></span>
            <div class="ff-info-banner__body">
              <div class="ff-info-banner__title">Credential mapping</div>
              <div class="ff-info-banner__text">
                Client ID → <strong>Key</strong> &nbsp;•&nbsp;
                Client Secret → <strong>Secret</strong> &nbsp;•&nbsp;
                Refresh Token → <strong>Access Token</strong>
                (all on the Credentials tab; sourced from Seller Central → Developer Profile → LWA credentials).
              </div>
            </div>
          </div>
        </div>

        <!-- Plenty -->
        <div v-if="isPlenty" class="os-mp-block">
          <div class="ff-subhead"><i class="ti ti-cloud-data-connection"></i> PlentyOne</div>
          <div class="columns is-desktop">
            <div class="column is-one-quarter">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-filter"></i> Order Filter Code
                  <span class="ff-info" data-tip="Plenty API filter used to pick which orders to import. Wrong code = no orders appear."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.plentyOneOrderFilterCode" class="input ff-input" />
              </div>
            </div>
            <div class="column is-one-quarter">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-download"></i> Import Code
                  <span class="ff-info" data-tip="Plenty status code applied when an order has been imported successfully."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.plentyOneOrderImportCode" class="input ff-input" />
              </div>
            </div>
            <div class="column is-one-quarter">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-coins"></i> Commission Code
                  <span class="ff-info" data-tip="Plenty status code used when commission is tracked/booked."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.plentyOneOrderCommissionCode" class="input ff-input" />
              </div>
            </div>
            <div class="column is-one-quarter">
              <div class="ff-field">
                <label class="ff-label">
                  <i class="ti ti-building-warehouse"></i> Plenty Warehouse ID
                  <span class="ff-info ff-info--right" data-tip="Plenty-side warehouse ID used when syncing stock levels."><i class="ti ti-info-circle"></i></span>
                </label>
                <input type="text" v-model="form.plentyOneWarehouseId" class="input ff-input" />
              </div>
            </div>
          </div>
        </div>

        <!-- JTL -->
        <div v-if="isJtl" class="os-mp-block">
          <div class="ff-subhead"><i class="ti ti-cloud-computing"></i> JTL</div>
          <div class="columns is-desktop">
            <div class="column is-one-third">
              <div class="ff-field">
                <label class="ff-label"><i class="ti ti-hash"></i> JTL Merchant ID</label>
                <input type="text" v-model="form.jtlMerchantId" class="input ff-input" />
              </div>
            </div>
          </div>

          <!-- Test & Diagnostics: grouped toolbar (quick check + detailed dry-run) + results panel -->
          <div class="os-testpanel">
            <div class="os-testpanel__head">
              <span class="os-testpanel__title"><i class="ti ti-stethoscope"></i> Test &amp; Diagnostics</span>
              <span v-if="jtlTesting" class="os-testpanel__status">Running…</span>
              <button v-else-if="jtlResult.status || jtlDetail" type="button" class="os-tbtn os-tbtn--ghost os-tbtn--xs" @click="jtlClearResult">
                <i class="ti ti-x"></i><span>Clear</span>
              </button>
            </div>

            <div class="os-testpanel__groups">
              <div class="os-testgroup">
                <span class="os-testgroup__label">Quick check</span>
                <div class="os-testbtns">
                  <button type="button" class="os-tbtn os-tbtn--info" :disabled="jtlTesting" @click="testJtlConnection">
                    <i :class="jtlActive === 'conn' ? 'ti ti-loader-2 os-spin' : 'ti ti-plug-connected'"></i><span>Connection</span>
                  </button>
                  <button type="button" class="os-tbtn os-tbtn--soft" :disabled="jtlTesting || !form.jtlMerchantId" @click="dryRunJtlProducts">
                    <i :class="jtlActive === 'qprod' ? 'ti ti-loader-2 os-spin' : 'ti ti-package'"></i><span>Products</span>
                  </button>
                  <button type="button" class="os-tbtn os-tbtn--soft" :disabled="jtlTesting || !form.jtlMerchantId" @click="dryRunJtlOrders">
                    <i :class="jtlActive === 'qord' ? 'ti ti-loader-2 os-spin' : 'ti ti-shopping-cart'"></i><span>Orders</span>
                  </button>
                  <button type="button" class="os-tbtn os-tbtn--soft" :disabled="jtlTesting || !form.jtlMerchantId" @click="listJtlProducts">
                    <i :class="jtlActive === 'lprod' ? 'ti ti-loader-2 os-spin' : 'ti ti-list-details'"></i><span>List all products</span>
                  </button>
                </div>
              </div>

              <div class="os-testgroup">
                <span class="os-testgroup__label">Detailed dry-run</span>
                <div class="os-testbtns">
                  <button type="button" class="os-tbtn os-tbtn--primary" :disabled="jtlTesting || !form.jtlMerchantId" @click="detailJtlProducts">
                    <i :class="jtlActive === 'dprod' ? 'ti ti-loader-2 os-spin' : 'ti ti-package'"></i><span>Product mapping</span>
                  </button>
                  <button type="button" class="os-tbtn os-tbtn--primary" :disabled="jtlTesting || !form.jtlMerchantId" @click="detailJtlOrders">
                    <i :class="jtlActive === 'dord' ? 'ti ti-loader-2 os-spin' : 'ti ti-shopping-cart'"></i><span>Order preview</span>
                  </button>
                </div>
              </div>

              <div class="os-testgroup">
                <span class="os-testgroup__label">Images</span>
                <div class="os-testbtns">
                  <button type="button" class="os-tbtn os-tbtn--soft" :disabled="jtlTesting || !form.jtlMerchantId || !form.id" @click="syncJtlImages" title="Queue an image sync for this Order Source. New/changed images are pushed; already-imported unchanged ones are skipped.">
                    <i :class="jtlActive === 'images' ? 'ti ti-loader-2 os-spin' : 'ti ti-photo'"></i><span>Sync images</span>
                  </button>
                </div>
              </div>

              <div class="os-testgroup">
                <span class="os-testgroup__label">Selective update</span>
                <div class="os-testbtns">
                  <button type="button" class="os-tbtn os-tbtn--primary" :disabled="jtlTesting || !form.jtlMerchantId || !form.id" @click="openSelectiveUpdate = true" title="Pick specific products and specific fields, then push only those fields from JTL to the matching iDempiere products (preview before applying). Save the Order Source first.">
                    <i class="ti ti-edit"></i><span>Update products → iDempiere</span>
                  </button>
                </div>
              </div>

              <div class="os-testgroup">
                <span class="os-testgroup__label">BOM sync</span>
                <div class="os-testbtns">
                  <button type="button" class="os-tbtn os-tbtn--primary" :disabled="jtlTesting || !form.jtlMerchantId || !form.id" @click="openBomSync = true" title="Mirror JTL Stücklisten to iDempiere BOMs: creates the PP_Product_BOM header + component lines (and missing component products) and sets IsBOM/isJtlBom on the master. No product fields are updated. Preview before applying. Save the Order Source first.">
                    <i class="ti ti-sitemap"></i><span>Sync BOM components</span>
                  </button>
                </div>
              </div>
            </div>

            <p class="os-testpanel__hint">
              <i class="ti ti-info-circle"></i>
              <span><strong>Quick</strong> = existence counts only. <strong>Detailed</strong> = exactly which JTL fields map to which iDempiere fields (products), and the order + customer + products that would import (orders). All read-only — nothing is saved.</span>
            </p>

            <!-- Results -->
            <div v-if="jtlResult.status" class="os-testresult">
              <div class="notification" :class="jtlResult.status === 'success' ? 'is-success is-light' : 'is-danger is-light'">
                <div>{{ jtlResult.message }}</div>
                <div v-if="jtlResult.summary" class="tags mt-2 mb-0">
                  <span v-for="(v, k) in jtlResult.summary" :key="k" class="tag is-white"><strong class="me-1">{{ prettyKey(k) }}:</strong> {{ v }}</span>
                </div>
              </div>

              <!-- Detailed: PRODUCTS -> per-product iDempiere field mapping -->
              <div v-if="jtlDetail && jtlDetail.kind === 'products'" class="os-detail">
                <article v-for="(p, i) in jtlDetail.products" :key="p.jfsku || i" class="os-detail__card">
                  <header class="os-detail__head">
                    <span class="tag" :class="p.action === 'create' ? 'is-success' : 'is-info'">{{ p.action === 'create' ? 'CREATE' : 'UPDATE' }}</span>
                    <span class="os-detail__name">{{ p.name || '—' }}</span>
                    <span class="os-detail__meta">JFSKU {{ p.jfsku }}</span>
                    <span class="tag is-light" :class="p.exists_idempiere ? 'is-warning' : ''">{{ p.exists_idempiere ? 'in iDempiere #' + p.idempiere_product_id : 'new to iDempiere' }}</span>
                  </header>
                  <table class="table is-narrow is-fullwidth is-size-7 os-detail__table">
                    <thead><tr><th style="width:42%">iDempiere field</th><th>Value</th></tr></thead>
                    <tbody>
                      <tr v-for="(val, key) in flattenPayload(p.payload)" :key="key">
                        <td><code>{{ key }}</code></td>
                        <td>{{ val }}</td>
                      </tr>
                    </tbody>
                  </table>
                  <div v-if="Object.keys(p.local_only || {}).length" class="os-detail__localonly">
                    <span class="os-detail__localonly-label"><i class="ti ti-database me-1"></i>Captured locally only (no iDempiere column yet):</span>
                    <span class="tags mb-0">
                      <span v-for="(val, key) in p.local_only" :key="key" class="tag is-light">{{ key }}: {{ String(val) }}</span>
                    </span>
                  </div>
                </article>
                <p v-if="!jtlDetail.products.length" class="os-detail__empty">No products returned.</p>
              </div>

              <!-- Detailed: ORDERS -> per-order bpartner + products preview -->
              <div v-if="jtlDetail && jtlDetail.kind === 'orders'" class="os-detail">
                <article v-for="(o, i) in jtlDetail.orders" :key="o.outboundId || i" class="os-detail__card">
                  <header class="os-detail__head">
                    <span class="tag" :class="o.action === 'import' ? 'is-success' : 'is-light'">{{ o.action === 'import' ? 'IMPORT' : 'SKIP' }}</span>
                    <span class="os-detail__name">Order {{ o.merchantOutboundNumber || o.outboundId }}</span>
                    <span class="os-detail__meta">{{ o.status }} · {{ (o.created_at || '').split('T')[0] }} · net {{ o.amount_net }} · vat {{ o.tax_total }}</span>
                  </header>
                  <div class="os-detail__cols">
                    <div class="os-detail__col">
                      <div class="os-detail__subhead">
                        <i class="ti ti-user me-1"></i> Business partner
                        <span class="tag is-small ml-1" :class="o.bpartner.action === 'create' ? 'is-success' : 'is-info'">{{ o.bpartner.action }}</span>
                      </div>
                      <table class="table is-narrow is-fullwidth is-size-7">
                        <tbody>
                          <tr><td>Name</td><td>{{ o.bpartner.full_name }}</td></tr>
                          <tr><td>Email</td><td>{{ o.bpartner.email }} <span class="has-text-grey">({{ o.bpartner.email_source }})</span></td></tr>
                          <tr v-if="o.bpartner.company"><td>Company</td><td>{{ o.bpartner.company }}</td></tr>
                          <tr v-if="o.bpartner.phone"><td>Phone</td><td>{{ o.bpartner.phone }}</td></tr>
                          <tr><td>Address</td><td>{{ [o.bpartner.address.street, o.bpartner.address.zip, o.bpartner.address.city, o.bpartner.address.country].filter(Boolean).join(', ') }}</td></tr>
                        </tbody>
                      </table>
                    </div>
                    <div class="os-detail__col">
                      <div class="os-detail__subhead"><i class="ti ti-package me-1"></i> Products ({{ o.line_items.length }})</div>
                      <table class="table is-narrow is-fullwidth is-size-7">
                        <thead><tr><th>JFSKU</th><th>Merchant SKU</th><th>Qty</th><th>Price</th><th>Found</th></tr></thead>
                        <tbody>
                          <tr v-for="(li, k) in o.line_items" :key="k">
                            <td>{{ li.jfsku }}</td>
                            <td>{{ li.merchantSku }}</td>
                            <td>{{ li.quantity }}</td>
                            <td>{{ li.price }}</td>
                            <td><span class="tag is-small" :class="li.product_found_local ? 'is-success' : 'is-warning'">{{ li.product_found_local ? 'yes' : 'no' }}</span></td>
                          </tr>
                        </tbody>
                      </table>
                    </div>
                  </div>
                </article>
                <p v-if="!jtlDetail.orders.length" class="os-detail__empty">No orders returned.</p>
              </div>

              <!-- List: all products (minimal), with client-side search -->
              <div v-if="jtlDetail && jtlDetail.kind === 'list'" class="os-detail">
                <div class="os-listsearch">
                  <i class="ti ti-search"></i>
                  <input v-model="jtlListSearch" type="text" class="input ff-input os-listsearch__input" placeholder="Search name, SKU or JFSKU…" />
                  <span class="os-listsearch__count">{{ jtlFilteredList.length }} / {{ jtlDetail.products.length }}</span>
                  <button type="button" class="button is-small is-link is-light" :disabled="!jtlFilteredList.length" @click="printJtlProductsPdf" title="Print as return slip (to place inside the shipment)">
                    <i class="ti ti-file-type-pdf me-1"></i> Return slip (PDF)
                  </button>
                </div>
                <div class="os-detail__scroll">
                  <table class="table is-narrow is-fullwidth is-size-7 is-hoverable">
                    <thead><tr><th style="width:46px">#</th><th>Product name</th><th>SKU (merchantSku)</th><th>JFSKU</th></tr></thead>
                    <tbody>
                      <tr v-for="(p, i) in jtlFilteredList" :key="p.jfsku || i">
                        <td>{{ i + 1 }}</td>
                        <td>{{ p.name || '—' }}</td>
                        <td>{{ p.merchantSku || '—' }}</td>
                        <td>{{ p.jfsku || '—' }}</td>
                      </tr>
                    </tbody>
                  </table>
                </div>
                <p v-if="!jtlDetail.products.length" class="os-detail__empty">No products returned.</p>
                <p v-else-if="!jtlFilteredList.length" class="os-detail__empty">No products match “{{ jtlListSearch }}”.</p>
              </div>
            </div>
          </div>

          <SettingsJtlSelectiveUpdateModal
            v-model:open="openSelectiveUpdate"
            :order-source-id="form.id"
            :jtl-merchant-id="form.jtlMerchantId"
            :order-source-name="form.name"
          />

          <SettingsJtlBomSyncModal
            v-model:open="openBomSync"
            :order-source-id="form.id"
            :jtl-merchant-id="form.jtlMerchantId"
            :order-source-name="form.name"
          />
        </div>

        <div v-if="!isShopware && !isAnyShopify && !isEbay && !isAmazon && !isPlenty && !isJtl" class="ff-info-banner ff-info-banner--blue">
          <span class="ff-info-banner__icon"><i class="ti ti-info-circle"></i></span>
          <div class="ff-info-banner__body">
            <div class="ff-info-banner__title">No marketplace-specific settings</div>
            <div class="ff-info-banner__text">The selected marketplace does not expose extra configuration.</div>
          </div>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 9. PRODUCT FILTERS                                             -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'filters'" class="ff-section ff-section--cyan">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-filter"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Product Filters</h3>
            <p class="ff-section__sub">Whitelisting + quantity-sync behavior.</p>
          </div>
          <button type="button" class="ff-help-pill" @click="openHelp('Product Filters', 'Selected Articles Only — master switch for whitelist mode. When ON, only the SKUs listed below are synced; everything else is silently skipped.\n\nQty-on-Hand on Commission — (Plenty) base inventory sync on commission status rather than raw on-hand quantity.\n\nProduct Qty Schedule — switch stock sync from event-driven real-time to scheduled batch. Reduces API calls but makes stock less current.')">
            <i class="ti ti-help-circle"></i> Mode details
          </button>
        </div>

        <div class="os-flag-grid">
          <label class="ff-toggle-card" :class="{'is-on': form.isSyncSelectedArticlesOnly}">
            <input type="checkbox" v-model="form.isSyncSelectedArticlesOnly" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-list-check"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Sync Selected Articles Only</span>
              <span class="ff-toggle-card__desc">Whitelist mode — only the SKUs below are synced.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>
          <label class="ff-toggle-card" :class="{'is-on': form.isSyncQtyOnHandOnCommission}">
            <input type="checkbox" v-model="form.isSyncQtyOnHandOnCommission" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-box-seam"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Qty on Hand on Commission</span>
              <span class="ff-toggle-card__desc">Plenty: base "available" qty on commission status rather than raw on-hand.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>
          <label class="ff-toggle-card" :class="{'is-on': form.isSyncProductQtySchedule}">
            <input type="checkbox" v-model="form.isSyncProductQtySchedule" class="ff-toggle-card__input" @change="save">
            <span class="ff-toggle-card__icon"><i class="ti ti-clock-hour-4"></i></span>
            <span class="ff-toggle-card__body">
              <span class="ff-toggle-card__title">Scheduled Qty Sync</span>
              <span class="ff-toggle-card__desc">Batch stock sync on a schedule instead of event-driven realtime.</span>
            </span>
            <span class="ff-toggle-card__switch"><span class="ff-toggle-card__knob"></span></span>
          </label>
        </div>

        <div v-if="form.isSyncSelectedArticlesOnly" class="ff-field mt-3">
          <label class="ff-label">
            <i class="ti ti-list"></i> Selected Articles (comma-separated SKUs)
            <span class="ff-info" data-tip="Only SKUs in this list are synced. Typos here = silently-skipped products. No spaces around commas."><i class="ti ti-info-circle"></i></span>
          </label>
          <textarea v-model="form.selectedArticlesSync" class="input ff-input" rows="4" placeholder="DEH-01,SEH02-H,ABC-123"></textarea>
          <p class="ff-hint">
            <i class="ti ti-hash"></i>
            {{ form.selectedArticlesSync ? form.selectedArticlesSync.split(',').filter(s => s.trim()).length : 0 }} SKU(s) configured
          </p>
        </div>
      </div>

      <!-- ============================================================= -->
      <!-- 10. STATUS & SYNC INFO                                         -->
      <!-- ============================================================= -->
      <div v-show="activeSection === 'status'" class="ff-section ff-section--slate">
        <div class="ff-section__head">
          <div class="ff-section__icon"><i class="ti ti-activity"></i></div>
          <div class="ff-section__title-wrap">
            <h3 class="ff-section__title">Status & Sync Info</h3>
            <p class="ff-section__sub">Read-only diagnostics populated by the sync worker.</p>
          </div>
        </div>

        <div class="columns is-desktop">
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label"><i class="ti ti-fingerprint"></i> UID</label>
              <input type="text" :value="form.uid || '—'" class="input ff-input" readonly />
            </div>
          </div>
          <div class="column is-half">
            <div class="ff-field">
              <label class="ff-label"><i class="ti ti-hash"></i> iDempiere ID</label>
              <input type="text" :value="form.id || '—'" class="input ff-input" readonly />
            </div>
          </div>
        </div>

        <div class="ff-info-banner ff-info-banner--blue">
          <span class="ff-info-banner__icon"><i class="ti ti-clock"></i></span>
          <div class="ff-info-banner__body">
            <div class="ff-info-banner__title">Last sync telemetry</div>
            <div class="ff-info-banner__text">
              Last-sync timestamps are written by the Laravel middleware and will appear here once the worker runs against this source.
              Stale timestamps typically indicate broken credentials or a sync toggle turned off on the Sync Direction tab.
            </div>
          </div>
        </div>
      </div>

      <!-- ===== Help Modal ===== -->
      <div v-if="helpModal.open" class="os-help-backdrop" @click.self="closeHelp">
        <div class="os-help-modal">
          <div class="os-help-modal__head">
            <h3><i class="ti ti-help-circle"></i> {{ helpModal.title }}</h3>
            <button type="button" class="os-help-modal__close" @click="closeHelp"><i class="ti ti-x"></i></button>
          </div>
          <div class="os-help-modal__body">{{ helpModal.body }}</div>
          <div class="os-help-modal__foot">
            <button type="button" class="button is-primary" @click="closeHelp">Got it</button>
          </div>
        </div>
      </div>

    </div>
  </div>
</template>

<style scoped>
/* ===== Layout wrapper ===== */
.os-form { padding-bottom: 40px; }

/* ===== Status banner ===== */
.os-status-banner {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 18px 20px;
  border-radius: 14px;
  background: linear-gradient(135deg, color-mix(in srgb, var(--accent, #3b82f6) 12%, transparent), color-mix(in srgb, var(--accent, #3b82f6) 2%, transparent));
  border: 1px solid color-mix(in srgb, var(--accent, #3b82f6) 25%, transparent);
  margin-bottom: 16px;
}
.os-status-banner__badge {
  width: 52px; height: 52px;
  border-radius: 14px;
  background: var(--accent, #3b82f6);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 26px;
  flex-shrink: 0;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--accent, #3b82f6) 40%, transparent);
}
.os-status-banner__body { flex-grow: 1; min-width: 0; }
.os-status-banner__title {
  font-size: 18px; font-weight: 800;
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  color: var(--theme-text-primary, #111827);
}
.os-status-banner__chip {
  font-size: 11px; font-weight: 700;
  padding: 3px 10px; border-radius: 999px;
  background: rgba(255,255,255,0.7);
  border: 1px solid var(--theme-border, #e5e7eb);
  color: var(--theme-text-primary, #374151);
  display: inline-flex; align-items: center; gap: 4px;
  text-transform: uppercase; letter-spacing: 0.5px;
}
.os-status-banner__chip--ok    { background: #10b981; color: #fff; border-color: transparent; }
.os-status-banner__chip--off   { background: #ef4444; color: #fff; border-color: transparent; }
.os-status-banner__chip--muted { opacity: 0.6; }
.os-status-banner__sub {
  font-size: 12px; opacity: 0.75; margin-top: 4px;
  display: flex; gap: 16px; flex-wrap: wrap;
}
.os-status-banner__sub span { display: inline-flex; align-items: center; gap: 4px; }
.os-status-banner__actions { display: flex; gap: 8px; flex-shrink: 0; }

/* ===== Tabs ===== */
.os-tabs {
  display: flex; flex-wrap: wrap; gap: 6px;
  padding: 6px;
  border-radius: 12px;
  background: var(--theme-bg-tertiary, #f3f4f6);
  border: 1px solid var(--theme-border, #e5e7eb);
  margin-bottom: 18px;
  position: sticky; top: 0; z-index: 5;
}
.os-tab {
  position: relative;
  display: inline-flex; align-items: center; gap: 8px;
  padding: 8px 14px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: transparent;
  font-size: 13px; font-weight: 600;
  color: var(--theme-text-muted, #6b7280);
  cursor: pointer;
  transition: all 0.15s;
}
.os-tab i { font-size: 16px; }
.os-tab:hover { background: var(--theme-surface, #fff); color: var(--theme-text-primary, #111827); }
.os-tab.is-active {
  background: var(--theme-surface, #fff);
  color: var(--theme-text-primary, #111827);
  box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}
.os-tab--blue.is-active   { border-color: rgba(59,130,246,0.35);  color: #1d4ed8; }
.os-tab--orange.is-active { border-color: rgba(249,115,22,0.35);  color: #c2410c; }
.os-tab--green.is-active  { border-color: rgba(16,185,129,0.35);  color: #047857; }
.os-tab--purple.is-active { border-color: rgba(139,92,246,0.35);  color: #6d28d9; }
.os-tab--yellow.is-active { border-color: rgba(234,179,8,0.4);    color: #a16207; }
.os-tab--teal.is-active   { border-color: rgba(20,184,166,0.35);  color: #0f766e; }
.os-tab--gray.is-active   { border-color: rgba(107,114,128,0.4);  color: #374151; }
.os-tab--pink.is-active   { border-color: rgba(236,72,153,0.35);  color: #be185d; }
.os-tab--cyan.is-active   { border-color: rgba(6,182,212,0.35);   color: #0e7490; }
.os-tab--slate.is-active  { border-color: rgba(71,85,105,0.35);   color: #334155; }
.os-tab.is-warning::after {
  content: '';
  width: 8px; height: 8px;
  border-radius: 50%;
  background: #ef4444;
  display: inline-block;
}
.os-tab__dot {
  width: 16px; height: 16px; border-radius: 50%;
  background: #ef4444; color: #fff;
  font-size: 11px; font-weight: 800;
  display: inline-flex; align-items: center; justify-content: center;
}

/* ===== Sections (reuse ff-* design system) ===== */
.ff-section {
  border-radius: 14px;
  padding: 22px 20px 20px;
  margin-bottom: 18px;
  border: 1px solid var(--theme-border, #e5e7eb);
  background: var(--theme-surface, #fff);
  box-shadow: 0 1px 3px rgba(0,0,0,0.04);
  position: relative;
}
.ff-section::before {
  content: '';
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 4px;
  border-radius: 14px 14px 0 0;
}
.ff-section--blue::before   { background: linear-gradient(90deg, #3b82f6, #60a5fa); }
.ff-section--orange::before { background: linear-gradient(90deg, #f97316, #fb923c); }
.ff-section--green::before  { background: linear-gradient(90deg, #10b981, #34d399); }
.ff-section--purple::before { background: linear-gradient(90deg, #8b5cf6, #a78bfa); }
.ff-section--yellow::before { background: linear-gradient(90deg, #eab308, #facc15); }
.ff-section--teal::before   { background: linear-gradient(90deg, #14b8a6, #2dd4bf); }
.ff-section--gray::before   { background: linear-gradient(90deg, #6b7280, #9ca3af); }
.ff-section--pink::before   { background: linear-gradient(90deg, #ec4899, #f472b6); }
.ff-section--cyan::before   { background: linear-gradient(90deg, #06b6d4, #67e8f9); }
.ff-section--slate::before  { background: linear-gradient(90deg, #475569, #64748b); }

.ff-section__head {
  display: flex; align-items: center; gap: 14px;
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px dashed var(--theme-border, #e5e7eb);
}
.ff-section__icon {
  width: 44px; height: 44px;
  border-radius: 12px;
  display: flex; align-items: center; justify-content: center;
  font-size: 22px;
  flex-shrink: 0;
}
.ff-section--blue   .ff-section__icon { background: rgba(59,130,246,0.12);  color: #1d4ed8; }
.ff-section--orange .ff-section__icon { background: rgba(249,115,22,0.12);  color: #ea580c; }
.ff-section--green  .ff-section__icon { background: rgba(16,185,129,0.12);  color: #059669; }
.ff-section--purple .ff-section__icon { background: rgba(139,92,246,0.12);  color: #7c3aed; }
.ff-section--yellow .ff-section__icon { background: rgba(234,179,8,0.14);   color: #ca8a04; }
.ff-section--teal   .ff-section__icon { background: rgba(20,184,166,0.12);  color: #0d9488; }
.ff-section--gray   .ff-section__icon { background: rgba(107,114,128,0.12); color: #4b5563; }
.ff-section--pink   .ff-section__icon { background: rgba(236,72,153,0.12);  color: #db2777; }
.ff-section--cyan   .ff-section__icon { background: rgba(6,182,212,0.12);   color: #0891b2; }
.ff-section--slate  .ff-section__icon { background: rgba(71,85,105,0.12);   color: #475569; }

.ff-section__title-wrap { flex-grow: 1; min-width: 0; }
.ff-section__title { font-size: 16px; font-weight: 700; margin: 0; line-height: 1.2; color: var(--theme-text-primary, #111827); }
.ff-section__sub   { font-size: 12px; opacity: 0.7; margin: 2px 0 0; }

.ff-help-pill {
  background: var(--theme-bg-tertiary, #f3f4f6);
  color: var(--theme-text-primary, #374151);
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 999px;
  padding: 6px 12px;
  font-size: 12px; font-weight: 600;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 4px;
  transition: all 0.2s;
  flex-shrink: 0;
}
.ff-help-pill:hover { background: var(--theme-bg-hover, #e5e7eb); transform: translateY(-1px); }

/* ===== Fields ===== */
.ff-field { margin-bottom: 14px; }
.ff-label {
  font-size: 13px; font-weight: 600;
  margin-bottom: 6px;
  color: var(--theme-text-primary, #374151);
  display: flex; align-items: center; gap: 6px;
}
.ff-label--sm { font-size: 11px; margin-bottom: 4px; }
.ff-label i { color: var(--theme-text-muted, #9ca3af); }
.ff-input { width: 100%; }
.ff-hint { font-size: 11px; color: var(--theme-text-muted, #9ca3af); margin-top: 4px; font-style: italic; }
.ff-subhead {
  font-size: 13px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
  color: var(--theme-text-muted, #6b7280);
  margin: 12px 0 8px;
  display: flex; align-items: center; gap: 6px;
}

/* ===== Toggle cards ===== */
.ff-toggle-card {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 14px;
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s;
  background: var(--theme-surface, #fff);
  margin-bottom: 8px;
}
.ff-toggle-card:hover { border-color: #facc15; }
.ff-toggle-card.is-on {
  border-color: #10b981;
  background: linear-gradient(135deg, rgba(16,185,129,0.08), rgba(16,185,129,0.02));
}
.ff-toggle-card__input { position: absolute; opacity: 0; pointer-events: none; }
.ff-toggle-card__icon {
  width: 36px; height: 36px; border-radius: 10px;
  background: var(--theme-bg-tertiary, #f3f4f6);
  display: flex; align-items: center; justify-content: center;
  font-size: 18px;
  color: var(--theme-text-muted, #6b7280);
  flex-shrink: 0;
}
.ff-toggle-card.is-on .ff-toggle-card__icon { background: rgba(16,185,129,0.18); color: #059669; }
.ff-toggle-card__body { flex-grow: 1; min-width: 0; }
.ff-toggle-card__title { font-weight: 700; font-size: 13px; }
.ff-toggle-card__desc  { font-size: 11px; opacity: 0.7; line-height: 1.35; margin-top: 2px; }
.ff-toggle-card__switch {
  width: 40px; height: 22px; border-radius: 999px;
  background: var(--theme-border, #d1d5db);
  position: relative; transition: background 0.2s;
  flex-shrink: 0;
}
.ff-toggle-card__knob {
  position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px; border-radius: 50%;
  background: #fff; transition: transform 0.2s;
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.ff-toggle-card.is-on .ff-toggle-card__switch { background: #10b981; }
.ff-toggle-card.is-on .ff-toggle-card__knob   { transform: translateX(18px); }

.os-flag-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 10px;
  margin-top: 8px;
}
.ff-disabled-area { opacity: 0.5; pointer-events: none; filter: grayscale(0.4); }

/* ===== Warning banner ===== */
.ff-warn-banner {
  padding: 10px 14px;
  border-radius: 10px;
  background: rgba(249,115,22,0.1);
  border: 1px solid rgba(249,115,22,0.3);
  color: #c2410c;
  font-size: 13px; font-weight: 500;
  display: flex; align-items: center; gap: 8px;
}
.ff-warn-banner i { font-size: 18px; }

/* ===== Info banner ===== */
.ff-info-banner {
  display: flex; align-items: flex-start; gap: 12px;
  padding: 14px 16px;
  border-radius: 12px;
  border: 1px solid;
  margin-top: 8px;
}
.ff-info-banner--blue {
  background: linear-gradient(135deg, rgba(59,130,246,0.08), rgba(59,130,246,0.02));
  border-color: rgba(59,130,246,0.25);
  color: var(--theme-text-primary, #1f2937);
}
.ff-info-banner__icon { font-size: 22px; color: #3b82f6; line-height: 1; flex-shrink: 0; margin-top: 2px; }
.ff-info-banner__body { flex-grow: 1; }
.ff-info-banner__title { font-weight: 700; font-size: 14px; margin-bottom: 2px; }
.ff-info-banner__text { font-size: 13px; opacity: 0.85; line-height: 1.4; }

/* ===== CSS-only tooltip ===== */
.ff-info {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--theme-bg-tertiary, #f3f4f6);
  color: var(--theme-text-muted, #9ca3af);
  font-size: 12px;
  cursor: help;
  position: relative;
  margin-left: 4px;
  transition: all 0.15s;
}
.ff-info:hover { background: #3b82f6; color: #fff; transform: scale(1.1); }
.ff-info::before, .ff-info::after {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%);
  opacity: 0; pointer-events: none;
  transition: opacity 0.15s, transform 0.15s;
  z-index: 1000;
}
.ff-info::before {
  content: attr(data-tip);
  background: #1f2937; color: #fff;
  padding: 10px 14px; border-radius: 8px;
  font-size: 13px; font-weight: 500; line-height: 1.45;
  white-space: pre-line;
  width: max-content; max-width: 320px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.18);
  text-align: left;
}
.ff-info::after {
  content: '';
  border: 6px solid transparent;
  border-top-color: #1f2937;
  bottom: calc(100% + 2px);
  width: 0; height: 0;
}
.ff-info:hover::before, .ff-info:hover::after { opacity: 1; transform: translateX(-50%) translateY(-2px); }
.ff-info--right::before { left: auto; right: 0; transform: none; }
.ff-info--right::after  { left: auto; right: 6px; transform: none; }
.ff-info--right:hover::before { transform: translateY(-2px); }
.ff-info--right:hover::after  { transform: translateY(-2px); }

/* ===== Sync matrix (big arrows) ===== */
.os-sync-matrix {
  display: grid;
  grid-template-columns: 140px 1fr 140px;
  gap: 12px;
  padding: 16px;
  background: linear-gradient(135deg, rgba(16,185,129,0.04), rgba(16,185,129,0.01));
  border: 1px dashed rgba(16,185,129,0.3);
  border-radius: 14px;
  align-items: center;
  margin-bottom: 8px;
}
.os-sync-col__head {
  display: flex; flex-direction: column; align-items: center; gap: 8px;
  padding: 18px 12px;
  border-radius: 12px;
  background: var(--theme-surface, #fff);
  border: 2px solid var(--theme-border, #e5e7eb);
  font-weight: 800; font-size: 14px;
  color: var(--theme-text-primary, #111827);
  text-align: center;
}
.os-sync-col__head i { font-size: 28px; color: #059669; }
.os-sync-col--left .os-sync-col__head { border-color: rgba(139,92,246,0.35); }
.os-sync-col--left .os-sync-col__head i { color: #7c3aed; }
.os-sync-col--right .os-sync-col__head { border-color: rgba(236,72,153,0.35); }
.os-sync-col--right .os-sync-col__head i { color: #db2777; }
.os-sync-col--center { display: flex; flex-direction: column; gap: 10px; }

.os-sync-row {
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--theme-surface, #fff);
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 10px;
}
.os-sync-entity {
  font-weight: 700; font-size: 13px;
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--theme-text-primary, #111827);
}
.os-sync-entity i { font-size: 18px; color: #6b7280; }

.os-sync-arrows {
  display: flex; flex-direction: column; gap: 6px;
}
.os-arrow {
  display: inline-flex; align-items: center; justify-content: center;
  width: 72px; height: 30px;
  border: 2px solid var(--theme-border, #e5e7eb);
  border-radius: 8px;
  background: #fff;
  color: #9ca3af;
  cursor: pointer;
  font-size: 18px;
  transition: all 0.15s;
}
.os-arrow:hover { border-color: #10b981; color: #10b981; }
.os-arrow.is-on.os-arrow--right {
  background: linear-gradient(90deg, rgba(139,92,246,0.15), rgba(236,72,153,0.15));
  border-color: #ec4899;
  color: #be185d;
  box-shadow: 0 2px 6px rgba(236,72,153,0.25);
}
.os-arrow.is-on.os-arrow--left {
  background: linear-gradient(90deg, rgba(236,72,153,0.15), rgba(139,92,246,0.15));
  border-color: #8b5cf6;
  color: #6d28d9;
  box-shadow: 0 2px 6px rgba(139,92,246,0.25);
}

/* ===== Shipping profile grid ===== */
.os-profile-grid { display: flex; flex-direction: column; gap: 10px; }
.os-profile-row {
  display: grid;
  grid-template-columns: 36px 1fr 28px 1fr;
  align-items: end;
  gap: 10px;
  padding: 10px 12px;
  background: var(--theme-surface, #fff);
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 10px;
}
.os-profile-row__idx {
  width: 32px; height: 32px;
  border-radius: 50%;
  background: linear-gradient(135deg, #14b8a6, #06b6d4);
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-weight: 800; font-size: 13px;
  align-self: center;
}
.os-profile-row__arrow {
  align-self: center;
  display: flex; align-items: center; justify-content: center;
  color: #14b8a6;
  font-size: 24px;
}
.ff-field--inline { margin-bottom: 0; }

/* ===== Marketplace block ===== */
.os-mp-block {
  padding: 16px;
  border-radius: 12px;
  background: var(--theme-bg-tertiary, #f9fafb);
  border: 1px dashed var(--theme-border, #e5e7eb);
  margin-bottom: 14px;
}

/* ===== Help modal ===== */
.os-help-backdrop {
  position: fixed; inset: 0;
  background: rgba(17,24,39,0.55);
  backdrop-filter: blur(2px);
  z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: 20px;
}
.os-help-modal {
  background: var(--theme-surface, #fff);
  border-radius: 16px;
  width: 560px; max-width: 100%;
  max-height: 85vh;
  display: flex; flex-direction: column;
  box-shadow: 0 24px 48px rgba(0,0,0,0.3);
}
.os-help-modal__head {
  display: flex; justify-content: space-between; align-items: center;
  padding: 16px 20px;
  border-bottom: 1px solid var(--theme-border, #e5e7eb);
}
.os-help-modal__head h3 {
  font-size: 16px; font-weight: 700; margin: 0;
  display: flex; align-items: center; gap: 8px;
  color: var(--theme-text-primary, #111827);
}
.os-help-modal__head h3 i { color: #3b82f6; }
.os-help-modal__close {
  background: transparent; border: none;
  font-size: 20px; cursor: pointer;
  color: var(--theme-text-muted, #6b7280);
  padding: 4px; border-radius: 6px;
}
.os-help-modal__close:hover { background: var(--theme-bg-hover, #f3f4f6); }
.os-help-modal__body {
  padding: 20px;
  overflow-y: auto;
  font-size: 13px; line-height: 1.6;
  color: var(--theme-text-primary, #374151);
  white-space: pre-line;
}
.os-help-modal__foot {
  padding: 14px 20px;
  border-top: 1px solid var(--theme-border, #e5e7eb);
  display: flex; justify-content: flex-end;
}

/* ---------- Test & Diagnostics panel (JTL; reusable for other marketplaces) ---------- */
.os-testpanel {
  margin-top: 14px;
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 10px;
  background: var(--theme-surface, #fff);
  overflow: hidden;
}
.os-testpanel__head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px;
  padding: 9px 14px;
  background: var(--theme-surface-2, #f9fafb);
  border-bottom: 1px solid var(--theme-border, #e5e7eb);
}
.os-testpanel__title { font-weight: 600; font-size: 13px; }
.os-testpanel__title i { margin-right: 6px; }
.os-testpanel__status { font-size: 12px; color: var(--theme-text-secondary, #6b7280); }
.os-testpanel__clear {
  border: 0; background: transparent; cursor: pointer;
  font-size: 12px; color: var(--theme-text-secondary, #6b7280);
}
.os-testpanel__clear:hover { color: var(--theme-text-primary, #374151); }
.os-testpanel__groups {
  display: flex; flex-wrap: wrap; gap: 18px;
  padding: 12px 14px 4px;
}
.os-testgroup { display: flex; flex-direction: column; gap: 6px; }
.os-testgroup__label {
  font-size: 11px; text-transform: uppercase; letter-spacing: .04em;
  color: var(--theme-text-secondary, #6b7280);
}
.os-testbtns { display: flex; flex-wrap: wrap; gap: 6px; }

/* self-contained button styling (no dependency on Bulma/Nuxt UI theming) */
.os-tbtn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 13px;
  font-size: 12.5px; font-weight: 600; line-height: 1;
  border-radius: 7px; border: 1px solid transparent;
  cursor: pointer; white-space: nowrap;
  transition: background .15s ease, border-color .15s ease, color .15s ease, opacity .15s ease;
}
.os-tbtn i { font-size: 15px; }
.os-tbtn:disabled { opacity: .5; cursor: not-allowed; }
.os-tbtn--soft { background: #f1f5f9; color: #334155; border-color: #e2e8f0; }
.os-tbtn--soft:hover:not(:disabled) { background: #e2e8f0; }
.os-tbtn--info { background: #e0f2fe; color: #0369a1; border-color: #bae6fd; }
.os-tbtn--info:hover:not(:disabled) { background: #bae6fd; }
.os-tbtn--primary { background: #2563eb; color: #fff; border-color: #2563eb; }
.os-tbtn--primary:hover:not(:disabled) { background: #1d4ed8; border-color: #1d4ed8; }
.os-tbtn--ghost { background: transparent; color: #6b7280; border-color: transparent; padding: 4px 8px; font-weight: 500; }
.os-tbtn--ghost:hover:not(:disabled) { color: #374151; background: #f3f4f6; }
.os-tbtn--xs { font-size: 11.5px; }
.os-spin { animation: os-spin .7s linear infinite; }
@keyframes os-spin { to { transform: rotate(360deg); } }
.os-testpanel__hint {
  display: flex; gap: 7px; align-items: flex-start;
  padding: 4px 14px 12px;
  font-size: 11.5px; line-height: 1.5;
  color: var(--theme-text-secondary, #6b7280);
}
.os-testpanel__hint i { margin-top: 2px; }
.os-testresult { padding: 0 14px 14px; }

/* per-record detail cards */
.os-detail { display: flex; flex-direction: column; gap: 10px; }
.os-detail__card {
  border: 1px solid var(--theme-border, #e5e7eb);
  border-radius: 8px;
  padding: 10px 12px;
  background: var(--theme-surface, #fff);
}
.os-detail__head {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  margin-bottom: 8px;
}
.os-detail__name { font-weight: 600; font-size: 13px; }
.os-detail__meta { font-size: 11.5px; color: var(--theme-text-secondary, #6b7280); }
.os-detail__table { margin-bottom: 0; }
.os-detail__table code { font-size: 11px; }
.os-detail__localonly {
  margin-top: 8px; padding-top: 8px;
  border-top: 1px dashed var(--theme-border, #e5e7eb);
}
.os-detail__localonly-label {
  display: block; font-size: 11.5px; margin-bottom: 5px;
  color: var(--theme-text-secondary, #6b7280);
}
.os-detail__cols {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
@media (max-width: 768px) { .os-detail__cols { grid-template-columns: 1fr; } }
.os-detail__subhead { font-weight: 600; font-size: 12px; margin-bottom: 5px; }
.os-detail__empty { font-size: 12px; color: var(--theme-text-secondary, #6b7280); padding: 6px 2px; }
.os-listsearch { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.os-listsearch__input { max-width: 340px; }
.os-listsearch__count { font-size: 11.5px; color: var(--theme-text-secondary, #6b7280); white-space: nowrap; }
.os-detail__scroll { max-height: 460px; overflow: auto; border: 1px solid var(--theme-border, #e5e7eb); border-radius: 8px; }
.os-detail__scroll thead th { position: sticky; top: 0; background: var(--theme-surface, #fff); z-index: 1; }
</style>
