<script setup>
// JTL BOM → iDempiere component sync ("Sync BOM components" button on the Order Source form).
// Lists the merchant's JTL BOM articles (Stücklisten) with their billOfMaterialsComponents and
// mirrors the structure of the selected ones to iDempiere PP_Product_BOM / PP_Product_BOMLine.
// BOM structure ONLY — master product fields (upc/ean/mpn/...) are never updated; masters that
// don't exist in iDempiere are skipped; MISSING component products are created (never updated);
// re-runs are idempotent (existing lines matched by component product, qty updated when JTL
// differs). See server/api/ffn/{bom-products-list,sync-bom-components}.post.ts +
// Laravel FulfillerController::listBomProducts / syncBomComponents.
const props = defineProps({
  open: { type: Boolean, default: false },
  orderSourceId: { type: [String, Number], default: '' },
  jtlMerchantId: { type: String, default: '' },
  orderSourceName: { type: String, default: '' },
})
const emit = defineEmits(['update:open'])

const MAX_VISIBLE = 300 // cap rendered rows; refine the search to narrow down

const loading = ref(false)
const articles = ref([])          // [{ name, merchantSku, jfsku, components[], exists_idempiere, idempiere_product_id, idempiere_value, is_bom_idempiere }]
const loadError = ref('')
const search = ref('')
const onlyExisting = ref(true)    // filter: only BOM articles that already exist in iDempiere (masters are never created)
const pick = reactive({})         // jfsku -> bool

const running = ref(false)
const runError = ref('')
const result = ref(null)          // { success, message, summary, results, dry_run }

const close = () => emit('update:open', false)

const filtered = computed(() => {
  const q = search.value.trim().toLowerCase()
  let list = articles.value
  if (onlyExisting.value) list = list.filter(p => p.exists_idempiere)
  if (q) {
    list = list.filter(p =>
      (p.name || '').toLowerCase().includes(q) ||
      (p.merchantSku || '').toLowerCase().includes(q) ||
      (p.jfsku || '').toLowerCase().includes(q) ||
      (p.components || []).some(c =>
        (c.jfsku || '').toLowerCase().includes(q) ||
        (c.merchantSku || '').toLowerCase().includes(q) ||
        (c.name || '').toLowerCase().includes(q)))
  }
  return list
})
const visible = computed(() => filtered.value.slice(0, MAX_VISIBLE))
const overflow = computed(() => Math.max(0, filtered.value.length - MAX_VISIBLE))
const hiddenNotExisting = computed(() => onlyExisting.value ? articles.value.filter(p => !p.exists_idempiere).length : 0)

const selectedJfskus = computed(() => Object.keys(pick).filter(k => pick[k]))
const selectedCount = computed(() => selectedJfskus.value.length)

const selectAllFiltered = () => { for (const p of filtered.value) if (p.jfsku && p.exists_idempiere) pick[p.jfsku] = true }
const clearSelection = () => { for (const k of Object.keys(pick)) pick[k] = false }

const loadArticles = async () => {
  if (!props.orderSourceId) { loadError.value = 'Save the Order Source first.'; return }
  loading.value = true; loadError.value = ''
  try {
    const res = await $fetch('/api/ffn/bom-products-list', {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { orderSourceId: props.orderSourceId },
    })
    if (res?.success) articles.value = res.products || []
    else loadError.value = res?.message || 'Could not load BOM articles'
  } catch (e) {
    loadError.value = e?.data?.message || e?.message || 'Could not load BOM articles'
  }
  loading.value = false
}

const submit = async (dryRun) => {
  runError.value = ''; result.value = null
  if (!selectedCount.value) { runError.value = 'Select at least one BOM article.'; return }
  if (!dryRun && !confirm(`Sync the BOM components of ${selectedCount.value} article(s) to iDempiere?\nCreates PP_Product_BOM headers/lines and missing component products; sets IsBOM + isJtlBom. No product fields are updated.`)) return

  running.value = true
  try {
    const res = await $fetch('/api/ffn/sync-bom-components', {
      method: 'POST',
      headers: useRequestHeaders(['cookie']),
      body: { orderSourceId: props.orderSourceId, jfskus: selectedJfskus.value, dryRun },
    })
    if (res?.success) result.value = res
    else runError.value = res?.message || 'BOM sync failed'
  } catch (e) {
    runError.value = e?.data?.message || e?.message || 'Request failed'
  }
  running.value = false
}

const statusClass = (s) => ({
  synced: 'is-success', preview: 'is-info',
  failed: 'is-danger', error: 'is-danger',
  not_found_idempiere: 'is-warning', not_in_jtl: 'is-warning', not_a_bom: 'is-warning', no_components: 'is-warning',
}[s] || 'is-light')

const compStatusClass = (s) => ({
  created: 'is-success', qty_updated: 'is-info', exists: 'is-light',
  failed: 'is-danger', error: 'is-danger',
  create_line: 'is-success', create_product_and_line: 'is-success', update_qty: 'is-info', unchanged: 'is-light', missing: 'is-danger',
}[s] || 'is-light')

const compTag = (c) => {
  // Applied runs carry `status`; previews carry the planned `action`.
  const s = c.status || c.action || '—'
  return {
    create_line: 'will create line',
    create_product_and_line: 'will create product + line',
    update_qty: `will set qty ${c.qty_before} → ${c.qty}`,
    unchanged: 'up to date',
    missing: 'missing',
    qty_updated: `qty ${c.qty_before} → ${c.qty}`,
  }[s] || s
}

// New search / filter = new scope: drop the previous selection and run result so
// Preview/Apply always acts on the CURRENT filter + picks, never on leftovers from
// an earlier search (e.g. an article selected + synced before searching the next one).
watch([search, onlyExisting], () => {
  clearSelection()
  result.value = null
  runError.value = ''
})

// Load the article list the first time the modal opens.
watch(() => props.open, (v) => {
  if (v && !articles.value.length && !loading.value) loadArticles()
})
</script>

<template>
  <div class="modal" :class="{ 'is-active': open }">
    <div class="modal-background" @click="close"></div>
    <div class="modal-card jbs-card">
      <header class="modal-card-head">
        <p class="modal-card-title">
          <i class="ti ti-sitemap me-2"></i>
          Sync JTL BOM components → iDempiere
          <span v-if="orderSourceName" class="has-text-grey is-size-6">· {{ orderSourceName }}</span>
        </p>
        <button class="delete" aria-label="close" @click="close"></button>
      </header>

      <section class="modal-card-body">
        <p class="is-size-7 has-text-grey mb-4">
          <i class="ti ti-info-circle me-1"></i>
          Mirrors the JTL Stückliste structure to iDempiere: creates the <strong>PP_Product_BOM</strong> header and one
          <strong>component line per JTL component</strong>, creates <strong>missing component products</strong>, and sets
          <strong>IsBOM + isJtlBom</strong> on the master. No product fields (UPC/EAN/MPN/…) are updated. Masters not in
          iDempiere are skipped — run the product sync first. Re-runs are safe: existing lines are matched, quantities
          updated when JTL differs. Use <strong>Preview</strong> to see every planned action before applying.
        </p>

        <div class="jbs-section-head">
          <span><i class="ti ti-packages me-1"></i> BOM articles <span class="tag is-light ml-1">{{ selectedCount }} selected</span></span>
          <button type="button" class="button is-small is-light" :disabled="loading" @click="loadArticles">
            <i class="ti ti-refresh me-1"></i> Reload
          </button>
        </div>

        <div class="field jbs-search">
          <p class="control has-icons-left">
            <input v-model="search" class="input is-small" type="text" placeholder="Search name, SKU, JFSKU or component…" />
            <span class="icon is-small is-left"><i class="ti ti-search"></i></span>
          </p>
          <label class="checkbox is-size-7 jbs-onlyexisting" title="Masters are never created by this sync — articles missing in iDempiere would be skipped anyway.">
            <input type="checkbox" v-model="onlyExisting" />
            Only articles existing in iDempiere
          </label>
          <div class="buttons are-small mb-0">
            <button type="button" class="button is-link is-light" :disabled="!filtered.length" @click="selectAllFiltered">Select all (filtered)</button>
            <button type="button" class="button is-light" :disabled="!selectedCount" @click="clearSelection">Clear</button>
          </div>
        </div>

        <div v-if="loading" class="has-text-centered p-5 has-text-grey">
          <i class="ti ti-loader-2 jbs-spin" style="font-size:1.5rem"></i>
          <div class="mt-2 is-size-7">Loading BOM articles from JTL…</div>
        </div>
        <div v-else-if="loadError" class="notification is-danger is-light is-size-7">{{ loadError }}</div>
        <div v-else class="jbs-list">
          <table class="table is-narrow is-fullwidth is-hoverable is-size-7">
            <thead><tr><th style="width:36px"></th><th>Name</th><th>SKU</th><th>JFSKU</th><th>iDempiere</th><th>Components</th></tr></thead>
            <tbody>
              <tr v-for="p in visible" :key="p.jfsku" @click="p.jfsku && p.exists_idempiere && (pick[p.jfsku] = !pick[p.jfsku])" :style="{ cursor: p.exists_idempiere ? 'pointer' : 'not-allowed' }">
                <td><input type="checkbox" v-model="pick[p.jfsku]" :disabled="!p.jfsku || !p.exists_idempiere" @click.stop /></td>
                <td>{{ p.name || '—' }}</td>
                <td>{{ p.merchantSku || '—' }}</td>
                <td>{{ p.jfsku || '—' }}</td>
                <td>
                  <span v-if="p.exists_idempiere" class="tag is-success is-light" :title="p.idempiere_value || ''">#{{ p.idempiere_product_id }}{{ p.is_bom_idempiere ? ' · IsBOM' : '' }}</span>
                  <span v-else class="tag is-warning is-light" title="Master not in iDempiere — run the product sync first; this article would be skipped.">not found</span>
                </td>
                <td>
                  <span v-for="c in p.components" :key="c.jfsku" class="tag is-light mr-1 mb-1" :class="{ 'has-text-danger': !c.in_jtl }" :title="(c.name || c.jfsku) + (c.in_jtl ? '' : ' — not returned by JTL')">
                    {{ c.merchantSku || c.jfsku }} × {{ c.quantity }}
                  </span>
                  <span v-if="!(p.components || []).length" class="has-text-grey">—</span>
                </td>
              </tr>
            </tbody>
          </table>
          <p v-if="!articles.length" class="has-text-grey is-size-7 p-3">No BOM articles. Click Reload.</p>
          <p v-else-if="!filtered.length" class="has-text-grey is-size-7 p-3">No BOM articles match the filter.</p>
          <p v-if="overflow" class="has-text-grey is-size-7 p-2">…and {{ overflow }} more — refine the search to see them.</p>
          <p v-if="hiddenNotExisting" class="has-text-grey is-size-7 p-2">{{ hiddenNotExisting }} BOM article(s) hidden (not in iDempiere) — untick the filter to show them.</p>
        </div>

        <!-- Result -->
        <div v-if="runError" class="notification is-danger is-light is-size-7 mt-4">{{ runError }}</div>
        <div v-if="result" class="jbs-result">
          <div class="notification is-size-7 mb-2" :class="result.dry_run ? 'is-info is-light' : (result.summary && result.summary.errors ? 'is-warning is-light' : 'is-success is-light')">
            <div>{{ result.message }}</div>
            <div v-if="result.summary" class="tags mt-2 mb-0">
              <span v-for="(v, k) in result.summary" :key="k" class="tag is-white"><strong class="me-1">{{ k }}:</strong> {{ v }}</span>
            </div>
          </div>
          <div class="jbs-list">
            <table class="table is-narrow is-fullwidth is-size-7">
              <thead><tr><th>JFSKU</th><th>Name</th><th>Status</th><th>BOM header</th><th>Flags</th><th>Components</th></tr></thead>
              <tbody>
                <tr v-for="(r, i) in result.results" :key="r.jfsku || i">
                  <td>{{ r.jfsku }}</td>
                  <td>{{ r.name || '—' }}</td>
                  <td><span class="tag" :class="statusClass(r.status)">{{ r.status }}</span></td>
                  <td>
                    <span v-if="r.bom_header" class="tag is-light">{{ r.bom_header }}<template v-if="r.bom_id"> · #{{ r.bom_id }}</template></span>
                    <span v-else class="has-text-grey">—</span>
                  </td>
                  <td><span v-if="r.flags" class="tag is-light">{{ r.flags }}</span><span v-else class="has-text-grey">—</span></td>
                  <td>
                    <template v-if="(r.components || []).length">
                      <span v-for="(c, k) in r.components" :key="c.jfsku || k" class="tag mr-1 mb-1" :class="compStatusClass(c.status || c.action)" :title="c.message || c.name || ''">
                        {{ c.merchantSku || c.jfsku }} × {{ c.qty }}<template v-if="c.component_created"> (product created)</template> — {{ compTag(c) }}
                      </span>
                    </template>
                    <span v-if="r.message" class="has-text-grey">{{ r.message }}</span>
                  </td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
      </section>

      <footer class="modal-card-foot is-justify-content-space-between">
        <span class="is-size-7 has-text-grey">{{ selectedCount }} BOM article(s) selected</span>
        <div class="buttons mb-0">
          <button type="button" class="button" @click="close">Close</button>
          <button type="button" class="button is-info" :disabled="running || !selectedCount" @click="submit(true)">
            <i :class="running ? 'ti ti-loader-2 jbs-spin' : 'ti ti-eye'" class="me-1"></i> Preview
          </button>
          <button type="button" class="button is-primary" :disabled="running || !selectedCount" @click="submit(false)">
            <i :class="running ? 'ti ti-loader-2 jbs-spin' : 'ti ti-cloud-upload'" class="me-1"></i> Apply sync
          </button>
        </div>
      </footer>
    </div>
  </div>
</template>

<style scoped>
.jbs-card { width: 1100px; max-width: 96vw; }
.jbs-section-head { display: flex; align-items: center; justify-content: space-between; font-weight: 600; margin-bottom: .5rem; }
.jbs-search { display: flex; align-items: center; gap: .75rem; flex-wrap: wrap; margin-bottom: .5rem; }
.jbs-search .control { flex: 1 1 220px; margin-bottom: 0; }
.jbs-onlyexisting { display: flex; align-items: center; gap: .35rem; white-space: nowrap; }
.jbs-list { max-height: 320px; overflow: auto; border: 1px solid var(--theme-border, #e5e7eb); border-radius: 6px; }
.jbs-list table { margin-bottom: 0; }
.jbs-result { margin-top: 1rem; }
.jbs-spin { animation: jbs-spin 1s linear infinite; }
@keyframes jbs-spin { from { transform: rotate(0); } to { transform: rotate(360deg); } }
</style>
