/* Quick Variable Products — admin Quick Add screen.
 * Dynamic N-axis variation matrix: pick any attributes, generate the cartesian
 * product in one click, set stock/SKU/price per row, save.
 */
(function () {
	'use strict';
	if (typeof ERENQVP === 'undefined') return;

	var I = ERENQVP.i18n || {};
	// Shared live registry (NOT a copy): the Pro add-on reads/pushes ERENQVP.attributes too, so
	// attributes created by either script stay visible to both.
	var ATTRS = ERENQVP.attributes = (ERENQVP.attributes || []);
	var termsCache = {};                           // taxonomy -> [ { slug, name } ]
	var dlSeq = 0;
	var imgAxis = null;                            // which axis drives per-variation images (e.g. pa_color)
	var imgByValue = {};                           // taxonomy -> { value -> { id, url } }
	var vimgFrame = null;                          // shared, reused wp.media frame for per-variation images
	var matrixAxesKey = '';                        // exact axes/value set used by the visible matrix

	function byId(id) { return document.getElementById(id); }
	function el(tag, cls) { var n = document.createElement(tag); if (cls) n.className = cls; return n; }
	function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
	function splitList(s) { return (s || '').split(',').map(function (x) { return x.trim(); }).filter(Boolean); }
	function fmt(tpl) { var a = arguments, i = 1; return String(tpl).replace(/%(\d+)\$[sd]|%[sd]/g, function (m, pos) { return pos ? a[+pos] : a[i++]; }); }
	function attrsKey(attrs) {
		return Object.keys(attrs || {}).sort().map(function (k) { return k + '=' + String(attrs[k]); }).join('|');
	}
	function axesKey(axes) {
		return JSON.stringify((axes || []).map(function (a) { return [a.taxonomy, a.values]; }));
	}

	function ajax(action, data) {
		var body = new URLSearchParams();
		body.set('action', action);
		body.set('nonce', ERENQVP.nonce);
		for (var k in data) { if (Object.prototype.hasOwnProperty.call(data, k)) body.set(k, data[k]); }
		return fetch(ERENQVP.ajaxurl, {
			method: 'POST', credentials: 'same-origin',
			headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: body
		}).then(function (r) { return r.json(); }).catch(function () { return { success: false, data: { msg: I.netErr } }; });
	}

	function toast(msg, type) {
		var box = byId('qvp-toast'); if (!box) return;
		var t = el('div', 't ' + (type || '')); t.textContent = msg;
		t.setAttribute('role', type === 'err' || type === 'warn' ? 'alert' : 'status');
		box.appendChild(t);
		setTimeout(function () { t.style.opacity = '0'; setTimeout(function () { t.remove(); }, 250); }, type === 'err' || type === 'warn' ? 10000 : 5000);
	}
	function formError(input, msg) {
		toast(msg, 'err');
		var result = byId('qvp-result');
		if (result) { result.setAttribute('role', 'alert'); result.innerHTML = '<div class="qvp-warn">' + esc(msg) + '</div>'; }
		if (input) {
			input.setAttribute('aria-invalid', 'true'); input.focus();
			var clear = function () { input.removeAttribute('aria-invalid'); input.removeEventListener('input', clear); input.removeEventListener('change', clear); input.removeEventListener('click', clear); };
			input.addEventListener('input', clear); input.addEventListener('change', clear); input.addEventListener('click', clear);
		}
	}

	/* ── Categories ── */
	(function () {
		var sel = byId('qvp-cats'); if (!sel) return;
		(ERENQVP.categories || []).forEach(function (c) {
			var o = el('option'); o.value = c.id; o.textContent = c.name; sel.appendChild(o);
		});
	})();

	/* ── Tax / stock / shipping selects + sticky last-used prefs ── */
	(function () {
		var P = ERENQVP.prefs || {};
		function fill(id, items, valKey, labelKey, selected) {
			var sel = byId(id); if (!sel) return;
			sel.innerHTML = '';
			items.forEach(function (it) {
				var o = el('option'); o.value = it[valKey]; o.textContent = it[labelKey];
				if (String(it[valKey]) === String(selected)) o.selected = true;
				sel.appendChild(o);
			});
		}
		fill('qvp-tax-status', ERENQVP.taxStatuses || [], 'v', 'label', P.tax_status);
		fill('qvp-tax-class', ERENQVP.taxClasses || [], 'slug', 'label', P.tax_class);
		fill('qvp-backorders', ERENQVP.backorders || [], 'v', 'label', P.backorders);

		var scSel = byId('qvp-ship-class');
		if (scSel) {
			scSel.innerHTML = '';
			var none = el('option'); none.value = '0'; none.textContent = '—'; scSel.appendChild(none);
			(ERENQVP.shippingClasses || []).forEach(function (s) {
				var o = el('option'); o.value = s.id; o.textContent = s.name;
				if (String(s.id) === String(P.shipping_class_id)) o.selected = true;
				scSel.appendChild(o);
			});
		}

		if (ERENQVP.brandSupported) {
			var bl = byId('qvp-brand-list');
			if (bl) { bl.innerHTML = ''; (ERENQVP.brands || []).forEach(function (b) { var o = el('option'); o.value = b.name; bl.appendChild(o); }); }
			var bi = byId('qvp-brand'); if (bi && P.brand) bi.value = P.brand;
		} else {
			var bw = byId('qvp-brand-wrap'); if (bw) bw.style.display = 'none';
		}

		var tl = byId('qvp-tag-list');
		if (tl) { tl.innerHTML = ''; (ERENQVP.tags || []).forEach(function (t) { var o = el('option'); o.value = t.name; tl.appendChild(o); }); }

		var ms = byId('qvp-manage-stock'); if (ms) ms.checked = (P.manage_stock !== false);
		var as = byId('qvp-auto-sku'); if (as) as.checked = !!P.auto_sku;
	})();

	/* ── Cover image (single) + gallery (multiple), via WP media ── */
	var gallery = []; // [ { id, url } ]
	function thumbUrl(a) { return (a.sizes && a.sizes.thumbnail) ? a.sizes.thumbnail.url : a.url; }

	(function () {
		var cover = byId('qvp-cover'), coverFrame = null;
		function setCover(a) {
			byId('qvp-img-id').value = a ? a.id : '';
			cover.classList.toggle('has', !!a);
			cover.innerHTML = a
				? '<img src="' + esc(thumbUrl(a)) + '" alt=""><button type="button" class="qvp-x" aria-label="' + esc(I.remove) + '">×</button>'
				: '<span class="qvp-cover-ph">＋</span>';
			if (a) cover.querySelector('.qvp-x').onclick = function (e) { e.stopPropagation(); setCover(null); };
		}
		cover.onclick = function () {
			if (typeof wp === 'undefined' || !wp.media) { toast(I.netErr, 'warn'); return; }
			if (coverFrame) { coverFrame.open(); return; }
			coverFrame = wp.media({ title: I.selectCover, button: { text: I.useImage }, multiple: false });
			coverFrame.on('select', function () { setCover(coverFrame.state().get('selection').first().toJSON()); });
			coverFrame.open();
		};
		cover.onkeydown = function (e) {
			if (e.target === cover && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); cover.click(); }
		};
		window.erenqvpResetCover = function () { setCover(null); };

		var galFrame = null;
		var dragFrom = null;
		function renderGallery() {
			var box = byId('qvp-gallery'); box.innerHTML = '';
			gallery.forEach(function (g, idx) {
				var cell = el('div', 'qvp-gthumb');
				cell.setAttribute('draggable', 'true');
				cell.setAttribute('role', 'listitem'); cell.tabIndex = 0;
				cell.setAttribute('aria-label', fmt(I.galleryPosition || 'Image %1$d of %2$d. Use Left and Right Arrow keys to reorder.', idx + 1, gallery.length));
				cell.innerHTML = '<img src="' + esc(g.url) + '" alt="" draggable="false"><button type="button" class="qvp-x" aria-label="' + esc(I.remove) + '">×</button>';
				cell.querySelector('.qvp-x').onclick = function () { gallery.splice(idx, 1); renderGallery(); };
				cell.addEventListener('keydown', function (e) {
					if (e.target !== cell || (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight')) return;
					var to = e.key === 'ArrowLeft' ? idx - 1 : idx + 1;
					if (to < 0 || to >= gallery.length) return;
					e.preventDefault(); var moved = gallery.splice(idx, 1)[0]; gallery.splice(to, 0, moved); renderGallery();
					var cells = byId('qvp-gallery').querySelectorAll('.qvp-gthumb'); if (cells[to]) cells[to].focus();
				});
				// Drag to reorder — the gallery array order is exactly what we save to the product.
				cell.addEventListener('dragstart', function (e) {
					dragFrom = idx; cell.classList.add('dragging');
					try { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(idx)); } catch (_) {}
				});
				cell.addEventListener('dragend', function () {
					dragFrom = null;
					Array.prototype.forEach.call(box.querySelectorAll('.qvp-gthumb'), function (c) { c.classList.remove('dragging', 'drop-target'); });
				});
				cell.addEventListener('dragover', function (e) { e.preventDefault(); try { e.dataTransfer.dropEffect = 'move'; } catch (_) {} cell.classList.add('drop-target'); });
				cell.addEventListener('dragleave', function () { cell.classList.remove('drop-target'); });
				cell.addEventListener('drop', function (e) {
					e.preventDefault();
					var from = dragFrom, to = idx;
					if (from === null || from === to) return;
					var moved = gallery.splice(from, 1)[0];
					gallery.splice(to, 0, moved);
					renderGallery();
				});
				box.appendChild(cell);
			});
		}
		byId('qvp-gallery-add').onclick = function () {
			if (typeof wp === 'undefined' || !wp.media) { toast(I.netErr, 'warn'); return; }
			if (galFrame) { galFrame.open(); return; }
			galFrame = wp.media({ title: I.selectGallery, button: { text: I.useImage }, multiple: true });
			galFrame.on('select', function () {
				galFrame.state().get('selection').toJSON().forEach(function (a) {
					if (!gallery.some(function (g) { return g.id === a.id; })) gallery.push({ id: a.id, url: thumbUrl(a) });
				});
				renderGallery();
			});
			galFrame.open();
		};
		window.erenqvpResetGallery = function () { gallery = []; renderGallery(); };
	})();

	/* ── Optional rich text editor for the description (loads TinyMCE on demand) ── */
	(function () {
		var btn = byId('qvp-rte-toggle'); if (!btn) return;
		var on = false;
		btn.onclick = function () {
			if (typeof wp === 'undefined' || !wp.editor || !wp.editor.initialize) { toast(I.netErr, 'warn'); return; }
			if (!on) {
				wp.editor.initialize('qvp-desc', {
					tinymce: { toolbar1: 'formatselect,bold,italic,bullist,numlist,link,removeformat,undo,redo', menubar: false, statusbar: false, height: 220 },
					quicktags: true, mediaButtons: false
				});
				on = true; btn.classList.add('is-on'); btn.textContent = '✕ ' + (I.plainEditor || 'Plain text');
			} else {
				if (window.tinymce) { try { window.tinymce.triggerSave(); } catch (e) {} }
				wp.editor.remove('qvp-desc');
				on = false; btn.classList.remove('is-on'); btn.textContent = '✏️ ' + (I.richEditor || 'Rich editor');
			}
		};
		window.erenqvpEditorSync = function () { if (on && window.tinymce) { try { window.tinymce.triggerSave(); } catch (e) {} } };
		window.erenqvpEditorClear = function () { if (on && window.tinymce) { var ed = window.tinymce.get('qvp-desc'); if (ed) ed.setContent(''); } };
	})();

	/* ── Axis management ── */
	function attrOptionsHtml(selectedTax) {
		var html = '<option value="">' + esc(I.chooseAttr) + '</option>';
		ATTRS.forEach(function (a) {
			html += '<option value="' + esc(a.taxonomy) + '"' + (a.taxonomy === selectedTax ? ' selected' : '') + '>' + esc(a.label) + '</option>';
		});
		html += '<option value="__new__">' + esc(I.newAttribute) + '</option>';
		return html;
	}

	function usedTaxonomies(exceptRow) {
		var used = {};
		document.querySelectorAll('.qvp-axis').forEach(function (row) {
			if (row === exceptRow) return;
			var t = row.querySelector('.qvp-axis-attr').value;
			if (t && t !== '__new__') used[t] = true;
		});
		return used;
	}

	function loadTerms(taxonomy, datalist) {
		function fill(terms) {
			datalist.innerHTML = '';
			terms.forEach(function (t) { var o = el('option'); o.value = t.name; datalist.appendChild(o); });
		}
		if (termsCache[taxonomy]) { fill(termsCache[taxonomy]); return; }
		ajax('erenqvp_attr_terms', { taxonomy: taxonomy }).then(function (j) {
			if (j.success) { termsCache[taxonomy] = j.data.terms || []; fill(termsCache[taxonomy]); }
		});
	}

	function refreshAllAxisSelects() {
		document.querySelectorAll('.qvp-axis').forEach(function (row) {
			var sel = row.querySelector('.qvp-axis-attr');
			var cur = sel.value;
			sel.innerHTML = attrOptionsHtml(cur);
		});
	}

	function addAxis() {
		var row = el('div', 'qvp-axis');
		var dlId = 'qvp-dl-' + (++dlSeq);

		var sel = el('select', 'qvp-axis-attr');
		sel.innerHTML = attrOptionsHtml('');
		sel.setAttribute('aria-label', I.chooseAttr || 'Choose attribute');

		var vals = el('input', 'qvp-axis-vals');
		vals.type = 'text';
		vals.setAttribute('list', dlId);
		vals.placeholder = I.axisValuesPh || 'red, blue, green';
		vals.setAttribute('aria-label', I.axisValuesPh || 'Attribute values');

		var dl = el('datalist'); dl.id = dlId;

		var del = el('button', 'button-link qvp-axis-del'); del.type = 'button'; del.textContent = I.removeAxis;
		del.onclick = function () { row.remove(); };

		sel.onchange = function () {
			var t = sel.value;
			if (t === '__new__') {
				sel.value = '';
				createAttribute(sel, dl);
				return;
			}
			if (t && usedTaxonomies(row)[t]) {
				toast(I.dupAttr, 'warn'); sel.value = ''; return;
			}
			// Real attribute SWITCH: clear values typed for the previous attribute so they are not
			// auto-created as terms of the wrong taxonomy. (First pick keeps pre-typed values.)
			if (t && sel.dataset.prev && sel.dataset.prev !== t) { vals.value = ''; }
			if (t) { sel.dataset.prev = t; }
			if (t) loadTerms(t, dl);
		};

		row.appendChild(sel); row.appendChild(vals); row.appendChild(dl); row.appendChild(del);
		byId('qvp-axes').appendChild(row);
	}

	function createAttribute(sel, datalist) {
		var label = window.prompt(I.newAttrPrompt, '');
		if (!label || !label.trim()) return;
		sel.disabled = true;
		if (I.creating) { toast(I.creating, ''); }
		ajax('erenqvp_create_attribute', { label: label.trim() }).then(function (j) {
			sel.disabled = false;
			if (!j.success) { toast((j.data && j.data.msg) || I.genFailed, 'err'); return; }
			var a = j.data; // { id, taxonomy, slug, label }
			if (!ATTRS.some(function (x) { return x.taxonomy === a.taxonomy; })) ATTRS.push(a);
			refreshAllAxisSelects();
			if (usedTaxonomies(sel.closest('.qvp-axis'))[a.taxonomy]) { toast(I.dupAttr, 'warn'); return; }
			sel.value = a.taxonomy;
			termsCache[a.taxonomy] = [];
			loadTerms(a.taxonomy, datalist);
		});
	}

	byId('qvp-add-axis').onclick = addAxis;
	addAxis(); // start with one axis

	/* ── Matrix generation (cartesian product of all axes that have values) ── */
	function collectAxes() {
		var axes = [];
		document.querySelectorAll('.qvp-axis').forEach(function (row) {
			var tax = row.querySelector('.qvp-axis-attr').value;
			var values = splitList(row.querySelector('.qvp-axis-vals').value);
			if (tax && tax !== '__new__' && values.length) {
				// de-dup values within an axis, preserve order
				var seen = {}, uniq = [];
				values.forEach(function (v) { var k = v.toLowerCase(); if (!seen[k]) { seen[k] = 1; uniq.push(v); } });
				axes.push({ taxonomy: tax, values: uniq });
			}
		});
		return axes;
	}

	function cartesian(axes) {
		// returns array of attrs objects: { taxonomy: value, ... }
		var out = [{}];
		axes.forEach(function (ax) {
			var next = [];
			out.forEach(function (combo) {
				ax.values.forEach(function (v) {
					var c = {}; for (var k in combo) c[k] = combo[k]; c[ax.taxonomy] = v; next.push(c);
				});
			});
			out = next;
		});
		return out;
	}

	function labelFor(attrs, axes) {
		return axes.map(function (ax) { return attrs[ax.taxonomy]; }).filter(Boolean).join(' / ');
	}

	/* ── Per-variation images: assign one image per value of a chosen axis (e.g. per Color) ── */
	function axisLabel(tax) { var a = ATTRS.filter(function (x) { return x.taxonomy === tax; })[0]; return a ? a.label : tax; }
	function fillVimgPanel(wrap, axes) {
		if (!imgAxis || !axes.some(function (a) { return a.taxonomy === imgAxis; })) imgAxis = axes[0].taxonomy;
		wrap.innerHTML = '';
		var head = el('div', 'qvp-vimg-head');
		var t = el('span', 'qvp-vimg-t'); t.textContent = '🎨 ' + (I.varImages || 'Variation images');
		var by = el('span', 'qvp-vimg-by'); by.textContent = (I.imagesBy || 'by') + ':';
		var sel = el('select');
		sel.setAttribute('aria-label', (I.varImages || 'Variation images') + ' ' + (I.imagesBy || 'by'));
		axes.forEach(function (a) { var o = el('option'); o.value = a.taxonomy; o.textContent = axisLabel(a.taxonomy) + ' (' + a.values.length + ')'; if (a.taxonomy === imgAxis) o.selected = true; sel.appendChild(o); });
		sel.onchange = function () { imgAxis = sel.value; fillVimgPanel(wrap, axes); };
		head.appendChild(t); head.appendChild(by); head.appendChild(sel);
		wrap.appendChild(head);

		var axis = axes.filter(function (a) { return a.taxonomy === imgAxis; })[0];
		var strip = el('div', 'qvp-vimg-strip');
		axis.values.forEach(function (v) {
			var cur = (imgByValue[imgAxis] && imgByValue[imgAxis][v]) ? imgByValue[imgAxis][v] : null;
			var cell = el('div', 'qvp-vimg-cell');
			var box = el('div', 'qvp-vimg-box' + (cur ? ' has' : ''));
			box.setAttribute('role', 'button'); box.tabIndex = 0;
			box.setAttribute('aria-label', fmt(I.variationImageFor || 'Select images for %s', v));
			var galBadge = (cur && cur.gallery && cur.gallery.length) ? '<span class="qvp-vimg-count">+' + cur.gallery.length + '</span>' : '';
			box.innerHTML = cur
				? '<img src="' + esc(cur.url) + '" alt="">' + galBadge + '<button type="button" class="qvp-x" aria-label="' + esc(I.remove) + '">×</button>'
				: '<span>＋</span>';
			box.onclick = function (e) {
				if (e.target.classList.contains('qvp-x')) { e.stopPropagation(); if (imgByValue[imgAxis]) delete imgByValue[imgAxis][v]; fillVimgPanel(wrap, axes); return; }
				if (typeof wp === 'undefined' || !wp.media) { toast(I.netErr, 'warn'); return; }
				if (!vimgFrame) { vimgFrame = wp.media({ title: I.selectCover, button: { text: I.useImage }, multiple: !!ERENQVP.proActive }); }
				// The frame is SHARED across every axis value, so clear the selection left over from the
				// previously-picked value (else it gets re-assigned here), then pre-select THIS value's image.
				vimgFrame.off('open');
				vimgFrame.on('open', function () {
					var selection = vimgFrame.state().get('selection');
					selection.reset();
					var existing = imgByValue[imgAxis] && imgByValue[imgAxis][v];
					if (existing && existing.id && wp.media && wp.media.attachment) {
						var att = wp.media.attachment(existing.id);
						att.fetch();
						selection.add(att);
					}
				});
				vimgFrame.off('select');
				vimgFrame.on('select', function () {
					// With Pro active the modal is multi-select: first image = the variation
					// image, the rest become the per-value GALLERY (written by the Pro adapter).
					var sel = vimgFrame.state().get('selection').toJSON();
					if (!sel || !sel.length) return;
					var a = sel[0];
					if (!imgByValue[imgAxis]) imgByValue[imgAxis] = {};
					imgByValue[imgAxis][v] = {
						id: a.id,
						url: (a.sizes && a.sizes.thumbnail) ? a.sizes.thumbnail.url : a.url,
						gallery: ERENQVP.proActive ? sel.slice(1).map(function (x) { return x.id; }) : []
					};
					fillVimgPanel(wrap, axes);
				});
				vimgFrame.open();
			};
			box.onkeydown = function (e) {
				if (e.target === box && (e.key === 'Enter' || e.key === ' ')) { e.preventDefault(); box.click(); }
			};
			var lbl = el('span', 'qvp-vimg-lbl'); lbl.textContent = v;
			cell.appendChild(box); cell.appendChild(lbl);
			strip.appendChild(cell);
		});
		wrap.appendChild(strip);
	}

	// Insert/remove the image strip based on the toggle, without rebuilding the matrix rows.
	// Driven by the axes SNAPSHOT taken at generation time (lastAxes) — the live inputs may have
	// been edited since, and images keyed to values not present in the generated rows would be
	// silently dropped on save.
	var lastAxes = null;
	function syncVimgStrip() {
		var on = byId('qvp-vimg-on') && byId('qvp-vimg-on').checked;
		var existing = byId('qvp-vimg-strip-wrap');
		var mwrap = byId('qvp-matrix-wrap');
		var matrixShown = mwrap && mwrap.style.display !== 'none';
		var axes = lastAxes || [];
		if (!on || !matrixShown || !axes.length) { if (existing) existing.remove(); return; }
		var vp = existing;
		if (!vp) {
			vp = el('div', 'qvp-vimg'); vp.id = 'qvp-vimg-strip-wrap';
			var grid = byId('qvp-matrix'); grid.insertBefore(vp, grid.firstChild);
		}
		fillVimgPanel(vp, axes);
	}

	function renderMatrix() {
		var axes = collectAxes();
		// Compute the projected combination COUNT cheaply first — never materialize the full cartesian
		// array (e.g. 3 axes × 300 values = 27M objects) just to discover it is too big and freeze the tab.
		var projected = axes.length ? axes.reduce(function (n, ax) { return n * ax.values.length; }, 1) : 1;
		if (projected > (Number(ERENQVP.maxVariations) || 1500)) {
			toast(fmt(I.matrixTooBig || 'Too many variations (%d) — reduce the number of values on one or more axes.', projected), 'err');
			return;
		}
		if (projected > 500 && !window.confirm(fmt(I.hugeMatrix || '%d variations will be generated — continue?', projected))) {
			return;
		}
		var combos = axes.length ? cartesian(axes) : [{}];
		lastAxes = axes;
		var grid = byId('qvp-matrix');
		// Keep values for combinations that survive a regenerate (for example adding one
		// colour must not wipe the stock/SKU/price already entered for every other row).
		var previous = {};
		grid.querySelectorAll('.qvp-row').forEach(function (r) {
			var attrs = {};
			try { attrs = JSON.parse(r.dataset.attrs || '{}'); } catch (e) { attrs = {}; }
			previous[attrsKey(attrs)] = {
				stock: r.querySelector('.v-stock') ? r.querySelector('.v-stock').value : '1',
				sku: r.querySelector('.v-sku') ? r.querySelector('.v-sku').value : '',
				price: r.querySelector('.v-price') ? r.querySelector('.v-price').value : ''
			};
		});
		grid.innerHTML = '';

		// "set all qty" helper (no axes = a single simple product, not "N variations")
		var bar = el('div', 'qvp-matrix-bar');
		var readyMsg = axes.length ? fmt(I.matrixReady, combos.length) : (I.simpleReady || fmt(I.matrixReady, combos.length));
		bar.innerHTML = '<span>' + esc(readyMsg) + '</span>';
		var qtyIn = el('input'); qtyIn.type = 'number'; qtyIn.min = '0'; qtyIn.value = '1'; qtyIn.className = 'qvp-setall-qty';
		qtyIn.setAttribute('aria-label', I.setAllQty || 'Set all quantities');
		var qtyBtn = el('button', 'button'); qtyBtn.type = 'button'; qtyBtn.textContent = '⤓';
		qtyBtn.title = I.setAllQty || 'Set all quantities';
		qtyBtn.setAttribute('aria-label', I.setAllQty || 'Set all quantities');
		qtyBtn.onclick = function () {
			var q = qtyIn.value;
			grid.querySelectorAll('.qvp-row .v-stock').forEach(function (i) { i.value = q; });
		};
		var skuBtn = el('button', 'button'); skuBtn.type = 'button'; skuBtn.textContent = I.genSku || '🔢 SKU';
		skuBtn.title = I.genSkuEmpty || 'Generate SKU for empty rows';
		skuBtn.onclick = genAllSku;
		bar.appendChild(qtyIn); bar.appendChild(qtyBtn); bar.appendChild(skuBtn);
		grid.appendChild(bar);

		combos.forEach(function (attrs) {
			var label = axes.length ? labelFor(attrs, axes) : (I.singleProduct || '— single product (no variations) —');
			var row = el('div', 'qvp-row');
			row.dataset.attrs = JSON.stringify(attrs);
			row.innerHTML =
				'<div class="vname">' + esc(label) + '</div>' +
				'<input class="v-stock" type="number" min="0" value="1" title="' + esc(I.qtyTitle || 'quantity') + '" aria-label="' + esc((I.qtyTitle || 'Quantity') + ': ' + label) + '">' +
				'<input class="v-sku" type="text" placeholder="' + esc(I.skuPh || 'SKU (optional)') + '" aria-label="' + esc((I.skuPh || 'SKU') + ': ' + label) + '">' +
				'<input class="v-price" type="number" min="0" step="0.01" placeholder="' + esc(I.pricePh || 'price override') + '" aria-label="' + esc((I.pricePh || 'Price') + ': ' + label) + '">' +
				'<button type="button" class="button-link v-gen">' + esc(I.generate) + '</button>';
			var old = previous[attrsKey(attrs)];
			if (old) {
				row.querySelector('.v-stock').value = old.stock;
				row.querySelector('.v-sku').value = old.sku;
				row.querySelector('.v-price').value = old.price;
			}
			row.querySelector('.v-gen').onclick = function () {
				ajax('erenqvp_gen_sku', {}).then(function (j) { if (j.success) row.querySelector('.v-sku').value = j.data.sku; });
			};
			// Enter in SKU box → jump to next row's SKU (sequential barcode scanning)
			row.querySelector('.v-sku').addEventListener('keydown', function (e) {
				if (e.key === 'Enter') {
					e.preventDefault();
					var all = grid.querySelectorAll('.v-sku');
					for (var i = 0; i < all.length; i++) { if (all[i] === e.target && all[i + 1]) { all[i + 1].focus(); break; } }
				}
			});
			grid.appendChild(row);
		});


		byId('qvp-matrix-wrap').style.display = 'block';
		matrixAxesKey = axesKey(axes);
		syncVimgStrip();
		if (window.erenqvpSharedApply) window.erenqvpSharedApply(); // pool mode disables per-row stock inputs
		// Integration seam: the Pro add-on augments the matrix toolbar on this event.
		document.dispatchEvent(new CustomEvent('erenqvp:matrix', { detail: { rows: combos.length, axes: axes } }));
		toast(axes.length ? fmt(I.matrixReady, combos.length) : (I.simpleReady || fmt(I.matrixReady, combos.length)), 'ok');
	}

	function genAllSku() {
		if (genAllSku.busy) return; // sequential AJAX chain: ignore re-clicks until it drains
		var inputs = Array.prototype.slice.call(byId('qvp-matrix').querySelectorAll('.v-sku')).filter(function (i) { return !i.value.trim(); });
		if (!inputs.length) return;
		genAllSku.busy = true;
		var p = Promise.resolve();
		inputs.forEach(function (inp) { p = p.then(function () { return ajax('erenqvp_gen_sku', {}).then(function (j) { if (j.success) inp.value = j.data.sku; }); }); });
		p.then(function () { genAllSku.busy = false; }, function () { genAllSku.busy = false; });
	}

	byId('qvp-genmatrix').onclick = renderMatrix;

	/* ── Shared stock pool (opt-in, confirm-guarded so it can't be enabled by accident) ── */
	(function () {
		var cb = byId('qvp-shared-stock'); if (!cb) return;
		function applyState() {
			var on = cb.checked;
			var qtyWrap = byId('qvp-shared-qty-wrap');
			if (qtyWrap) qtyWrap.style.display = on ? '' : 'none';
			document.querySelectorAll('#qvp-matrix .qvp-row .v-stock').forEach(function (i) { i.disabled = on; });
		}
		cb.onchange = function () {
			if (cb.checked && !window.confirm(I.sharedConfirm || 'All variations will share ONE stock pool. Continue?')) {
				cb.checked = false;
				return;
			}
			applyState();
		};
		window.erenqvpSharedApply = applyState;
	})();

	// Variation-images toggle: sticky (off by default). Live show/hide without rebuilding rows.
	(function () {
		var cb = byId('qvp-vimg-on'); if (!cb) return;
		cb.checked = !!(ERENQVP.prefs && ERENQVP.prefs.variation_images);
		cb.onchange = syncVimgStrip;
	})();

	/* ── Coach-mark tour: which confusing spots to spotlight on the Add screen ── */
	function addTourSteps() {
		return [
			{ target: '#qvp-name', title: I.tName_t, body: I.tName_b },
			// The two spots people get stuck on: WHERE to choose the attribute and
			// WHERE to type its values. Target the fields themselves (first axis row,
			// always present), not the "add axis" button — the engine scrolls each
			// into view. Values first, then the button that fans them into rows.
			{ target: '.qvp-axis .qvp-axis-attr', title: I.tAttr_t, body: I.tAttr_b },
			{ target: '.qvp-axis .qvp-axis-vals', title: I.tVals_t, body: I.tVals_b },
			{ target: '#qvp-add-axis', title: I.tAxis_t, body: I.tAxis_b },
			{ target: '#qvp-genmatrix', title: I.tGen_t, body: I.tGen_b },
			{ target: '#qvp-matrix-wrap', title: I.tMatrix_t, body: I.tMatrix_b }, // skipped until generated
			{
				target: '#qvp-shared-row', title: I.tPool_t, body: I.tPool_b,
				before: function () { // the shared-pool row lives in a collapsed <details>
					var row = byId('qvp-shared-row'), d = row && row.closest ? row.closest('details') : null;
					if (d) { d.open = true; }
				}
			},
			{ target: '#qvp-vimg-on', title: I.tVimg_t, body: I.tVimg_b },
			{ target: '#qvp-save', title: I.tSave_t, body: I.tSave_b }
		];
	}
	function launchAddTour() {
		if (window.ErenqvpTour) { ErenqvpTour.run(addTourSteps(), {}); }
	}

	/* ── First-run onboarding: guided tour + skip + one-click example ── */
	(function () {
		var UI = ERENQVP.tourUI || {};
		// Always-available "❓ Tips" pill in the hero (works even after the card is gone).
		if (window.ErenqvpTour) { ErenqvpTour.pill(byId('qvp-tips-slot'), UI.tips || 'Tips', launchAddTour); }

		var tour = byId('erenqvp-tour'); if (!tour) return;
		var startBtn = byId('qvp-tour-start');
		if (startBtn) { startBtn.onclick = launchAddTour; }
		byId('qvp-tour-skip').onclick = function () { ajax('erenqvp_dismiss_tour', {}); tour.remove(); };

		// Reuse an attribute whose label matches, otherwise create it on the fly (showcases the feature).
		function ensureAttr(label) {
			var hit = ATTRS.filter(function (a) { return (a.label || '').toLowerCase() === label.toLowerCase(); })[0];
			if (hit) return Promise.resolve(hit.taxonomy);
			return ajax('erenqvp_create_attribute', { label: label }).then(function (j) {
				if (!j.success) return null;
				var a = j.data;
				if (!ATTRS.some(function (x) { return x.taxonomy === a.taxonomy; })) ATTRS.push(a);
				refreshAllAxisSelects();
				return a.taxonomy;
			});
		}

		byId('qvp-tour-demo').onclick = function () {
			var btn = this; btn.disabled = true;
			byId('qvp-name').value = I.demoName || 'Sample T-Shirt';
			byId('qvp-price').value = '19.90';
			if (byId('qvp-draft')) byId('qvp-draft').checked = true; // the example saves as a draft, never live
			Promise.all([ensureAttr(I.demoColor || 'Color'), ensureAttr(I.demoSize || 'Size')]).then(function (taxes) {
				var axes = document.querySelectorAll('.qvp-axis');
				if (!axes.length) { addAxis(); axes = document.querySelectorAll('.qvp-axis'); }
				if (taxes[0]) {
					axes[0].querySelector('.qvp-axis-attr').value = taxes[0];
					axes[0].querySelector('.qvp-axis-vals').value = I.demoColors || 'Red, Blue';
				}
				if (taxes[1]) {
					if (axes.length < 2) { addAxis(); }
					axes = document.querySelectorAll('.qvp-axis');
					axes[1].querySelector('.qvp-axis-attr').value = taxes[1];
					axes[1].querySelector('.qvp-axis-vals').value = I.demoSizes || 'S, M, L';
				}
				renderMatrix();
				try { byId('qvp-matrix-wrap').scrollIntoView({ behavior: 'smooth' }); } catch (e) {}
				btn.disabled = false;
			});
		};
	})();

	/* ── One-time review invitation (server decides eligibility; shown at the moment of success) ── */
	function showReviewAsk() {
		if (byId('qvp-review')) return;
		var card = el('div', 'qvp-review'); card.id = 'qvp-review';
		card.innerHTML =
			'<span class="qvp-review-t">' + esc(I.reviewTitle || '') + '</span>' +
			'<span class="qvp-review-actions">' +
			'<a class="qvp-btn-secondary" href="' + esc(ERENQVP.reviewUrl || '#') + '" target="_blank" rel="noopener">⭐ ' + esc(I.reviewRate || 'Rate it') + '</a>' +
			'<button type="button" class="qvp-linkbtn" id="qvp-review-no">' + esc(I.reviewNo || 'No thanks') + '</button></span>';
		byId('qvp-result').appendChild(card);
		card.querySelector('#qvp-review-no').onclick = function () { card.remove(); };
		card.querySelector('a').addEventListener('click', function () { setTimeout(function () { card.remove(); }, 400); });
	}

	/* ── Save ── */
	byId('qvp-save').onclick = function () {
		if (window.erenqvpEditorSync) window.erenqvpEditorSync(); // flush TinyMCE → textarea before reading
		var name = byId('qvp-name').value.trim();
		var price = byId('qvp-price').value.trim();
		if (!name) { formError(byId('qvp-name'), I.needName); return; }
		if (!price || Number(price) <= 0) { formError(byId('qvp-price'), I.needPrice); return; }

		var rows = byId('qvp-matrix').querySelectorAll('.qvp-row');
		if (!rows.length) {
			// Axes have values but the matrix was never generated — block the silent simple-product path.
			if (collectAxes().length) { formError(byId('qvp-genmatrix'), I.noValues); return; }
			// No axes at all: create the single-product row and continue in this same click.
			renderMatrix();
			rows = byId('qvp-matrix').querySelectorAll('.qvp-row');
		}
		if ( matrixAxesKey !== axesKey(collectAxes()) ) {
			formError(byId('qvp-genmatrix'), I.matrixStale || 'Attributes or values changed after the matrix was generated. Generate the matrix again before saving.');
			return;
		}
		var vimgOn = byId('qvp-vimg-on') && byId('qvp-vimg-on').checked;
		var variations = [];
		Array.prototype.forEach.call(rows, function (row) {
			var attrs = {};
			try { attrs = JSON.parse(row.dataset.attrs || '{}'); } catch (e) { attrs = {}; }
			var vimg = 0, vgal = [];
			if (vimgOn && imgAxis && attrs[imgAxis] && imgByValue[imgAxis] && imgByValue[imgAxis][attrs[imgAxis]]) {
				var ventry = imgByValue[imgAxis][attrs[imgAxis]];
				vimg = ventry.id;
				vgal = (ERENQVP.proActive && ventry.gallery) ? ventry.gallery : [];
			}
			variations.push({
				attrs: attrs,
				stock: parseInt(row.querySelector('.v-stock').value, 10) || 0,
				sku: row.querySelector('.v-sku').value.trim(),
				regular_price: row.querySelector('.v-price').value.trim(),
				image_id: vimg,
				gallery_ids: vgal
			});
		});

		var cats = Array.prototype.slice.call(byId('qvp-cats').selectedOptions).map(function (o) { return Number(o.value) || 0; }).filter(Boolean);

		function val(id) { var n = byId(id); return n ? n.value.trim() : ''; }
		function list(id) { var n = byId(id); return n ? n.value.split(',').map(function (s) { return s.trim(); }).filter(Boolean) : []; }
		var payload = {
			name: name,
			regular_price: price,
			sale_price: byId('qvp-sale').value.trim(),
			cost: val('qvp-cost'),
			description: val('qvp-desc'),
			short_description: val('qvp-short-desc'),
			status: (byId('qvp-draft') && byId('qvp-draft').checked) ? 'draft' : 'publish',
			category_ids: cats,
			tags: list('qvp-tags'),
			brand: val('qvp-brand'),
			image_id: Number(byId('qvp-img-id').value) || 0,
			gallery_ids: gallery.map(function (g) { return g.id; }),
			manage_stock: byId('qvp-manage-stock').checked,
			shared_stock: !!(byId('qvp-shared-stock') && byId('qvp-shared-stock').checked),
			shared_pool_qty: parseInt((byId('qvp-shared-qty') || { value: 0 }).value, 10) || 0,
			auto_sku: byId('qvp-auto-sku').checked,
			variation_images: vimgOn,
			tax_status: val('qvp-tax-status'),
			tax_class: val('qvp-tax-class'),
			backorders: val('qvp-backorders'),
			shipping_class_id: Number(val('qvp-ship-class')) || 0,
			weight: val('qvp-weight'),
			length: val('qvp-length'),
			width: val('qvp-width'),
			height: val('qvp-height'),
			variations: variations
		};

		var btn = this, btnLabel = btn.textContent;
		btn.disabled = true;
		if (I.saving) { btn.textContent = I.saving; }
		ajax('erenqvp_create_product', { payload: JSON.stringify(payload) }).then(function (j) {
			btn.disabled = false; btn.textContent = btnLabel;
			if (!j.success) { formError(null, (j.data && j.data.msg) || I.genFailed); return; }
			var d = j.data;
			var msg = d.type === 'variable' ? fmt(I.created, name, d.variations) : fmt(I.createdSimple, name);
			toast(msg, 'ok');
			// A SKU the user typed was already taken → it was dropped, not silently lost.
			if (d.sku_conflicts && d.sku_conflicts.length && I.skuTaken) {
				toast(fmt(I.skuTaken, d.sku_conflicts.length) + ' ' + d.sku_conflicts.slice(0, 5).join(', '), 'warn');
			}
			// A sale price that wasn't below the regular price was ignored → tell the user, don't hide it.
			if (d.sale_dropped && I.saleDropped) {
				toast(fmt(I.saleDropped, d.sale_dropped), 'warn');
			}
			var tw = byId('erenqvp-tour'); if (tw) tw.remove(); // first product created — onboarding done (server marks it too)
			byId('qvp-result').setAttribute('role', 'status');
			byId('qvp-result').innerHTML = '<div class="qvp-ok">' + esc(msg) +
				' <a href="' + esc(d.edit) + '" target="_blank" rel="noopener">' + esc(I.openInWoo) + '</a></div>';
			if (d.review_ask) { showReviewAsk(); }
			// Integration seam: companion plugins react to the finished product here. The
			// detail still carries `labels` (name/variant/price/sku/stock per row) — the
			// standalone label-printing plugin consumes exactly that.
			document.dispatchEvent(new CustomEvent('erenqvp:created', { detail: d }));
			// reset per-product fields; keep sticky tax/stock/shipping/brand selections for the next item
			byId('qvp-name').value = ''; byId('qvp-price').value = ''; byId('qvp-sale').value = ''; byId('qvp-cost').value = '';
			byId('qvp-weight').value = ''; byId('qvp-length').value = ''; byId('qvp-width').value = ''; byId('qvp-height').value = '';
			byId('qvp-desc').value = ''; byId('qvp-short-desc').value = ''; byId('qvp-tags').value = ''; if (byId('qvp-draft')) byId('qvp-draft').checked = false;
			Array.prototype.forEach.call(byId('qvp-cats').options, function (o) { o.selected = false; }); // categories are per-product, like tags
			// Shared pool is per-product and NEVER sticky: reset so it can't leak into the next item.
			if (byId('qvp-shared-stock')) { byId('qvp-shared-stock').checked = false; byId('qvp-shared-qty').value = '0'; byId('qvp-shared-qty-wrap').style.display = 'none'; }
			if (window.erenqvpEditorClear) window.erenqvpEditorClear();
			if (window.erenqvpResetCover) window.erenqvpResetCover();
			if (window.erenqvpResetGallery) window.erenqvpResetGallery();
			byId('qvp-axes').innerHTML = ''; addAxis();
			byId('qvp-matrix').innerHTML = ''; byId('qvp-matrix-wrap').style.display = 'none'; matrixAxesKey = '';
			imgAxis = null; imgByValue = {};
			byId('qvp-name').focus();
		});
	};
})();
