// Theme-native card swap utility (Phase C, SCRUM-267).
//
// Clones a merchant-harvested cardTemplateHtml, swaps product data into the
// theme's own slots (image, title, all product links, price, add-to-cart form),
// and returns the swapped outerHTML for injection into the search results grid.
//
// v1 scope: image/title/link/price/form only. No swatches, no variant-image
// switching. The theme's native <form action="/cart/add"> + custom elements
// (quick-add-component, product-card, etc.) are preserved verbatim — the
// browser upgrades them on DOM insertion, giving native cart + animations.
/** Extract the numeric variant id from a Shopify GID. /cart/add.js needs the integer. */
function parseVariantId(gid) {
    const m = String(gid).match(/(\d+)\s*$/);
    return m ? Number(m[1]) : null;
}
/**
 * Format a price as a locale-aware currency string.
 * Uses Intl.NumberFormat so the output matches the merchant's locale,
 * e.g. "$9.99" for USD, "₹249" for INR.
 */
function formatPrice(amount, currencyCode) {
    try {
        return new Intl.NumberFormat(undefined, {
            style: "currency",
            currency: currencyCode,
        }).format(Number(amount));
    }
    catch {
        // Fall back if Intl is unavailable or the currency code is unrecognised.
        return `${currencyCode} ${amount}`;
    }
}
/**
 * Format a price using the merchant's Shopify money_format template string.
 *
 * The template uses Shopify's standard placeholders:
 *   {{amount}}                              — decimal, 2 places, thousands comma (e.g. 8,475.00)
 *   {{amount_no_decimals}}                  — integer, thousands comma (e.g. 8,475)
 *   {{amount_with_comma_separator}}         — decimal, 2 places, European (e.g. 8.475,00)
 *   {{amount_no_decimals_with_comma_separator}} — integer, European (e.g. 8.475)
 *   {{amount_with_apostrophe_separator}}    — decimal, 2 places, Swiss (e.g. 8'475.00)
 *
 * IMPORTANT: amountMajorUnits is in major currency units (e.g. "8475.00"),
 * NOT in cents. Shopify's own JS divides by 100, but our Storefront MoneyV2
 * amount is already in major units — do NOT divide.
 */
export function formatMoneyTemplate(amountMajorUnits, moneyFormat) {
    const num = Number(amountMajorUnits);
    if (!isFinite(num))
        return amountMajorUnits;
    const fmt = (precision, thousands = ",", decimal = ".") => {
        const n = num.toFixed(precision);
        const parts = n.split(".");
        const dollars = parts[0].replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + thousands);
        const cents = parts[1] ? decimal + parts[1] : "";
        return dollars + cents;
    };
    return moneyFormat.replace(/\{\{\s*(\w+)\s*\}\}/g, (_m, key) => {
        switch (key) {
            case "amount":
                return fmt(2);
            case "amount_no_decimals":
                return fmt(0);
            case "amount_with_comma_separator":
                return fmt(2, ".", ",");
            case "amount_no_decimals_with_comma_separator":
                return fmt(0, ".");
            case "amount_with_apostrophe_separator":
                return fmt(2, "'");
            default:
                return "";
        }
    });
}
/**
 * Extract the numeric product id from a full GID
 * (e.g. "gid://shopify/Product/12345" → "12345").
 * Returns null when the id is not a GID or has no numeric suffix.
 */
function numericProductId(gid) {
    const m = String(gid).match(/(\d+)\s*$/);
    return m ? m[1] : null;
}
/**
 * Clone `cardTemplateHtml`, swap product data into the theme's own slots per
 * `swapMap`, and return the swapped card's `outerHTML`.
 *
 * When `moneyFormat` is a non-empty string the price node is formatted via
 * `formatMoneyTemplate` using the merchant's Shopify money_format template
 * (e.g. "₹{{amount}}", "${{amount_no_decimals}}"). When absent or empty the
 * existing Intl.NumberFormat path is used as a fallback.
 *
 * Throws when a required selector is missing so the caller can fall back to
 * the generic Seekora card for that item.
 */
export function swapCardHtml(cardTemplateHtml, swapMap, product, moneyFormat) {
    const doc = new DOMParser().parseFromString(cardTemplateHtml, "text/html");
    const root = doc.body.firstElementChild;
    if (!root) {
        throw new Error("swapCardHtml: cardTemplateHtml has no root element");
    }
    // --- image ---
    const imgEl = root.querySelector(swapMap.image.selector);
    if (!imgEl) {
        throw new Error(`swapCardHtml: image selector "${swapMap.image.selector}" not found`);
    }
    const imageUrl = product.featuredImage?.url ?? "";
    const altText = product.title;
    // Set src + alt; remove srcset so the browser does not keep the template's
    // CDN-sized variants (they point at the template product).
    imgEl.src = imageUrl;
    imgEl.setAttribute("alt", altText);
    imgEl.removeAttribute("srcset");
    // Clear any additional CDN-hint attributes the theme may use (e.g. data-src).
    if (swapMap.image.attrs) {
        for (const attr of swapMap.image.attrs) {
            if (attr !== "src" && attr !== "alt" && attr !== "srcset") {
                imgEl.setAttribute(attr, imageUrl);
            }
        }
    }
    // Optional alternate image selector (e.g. a second slide / hover image).
    if (swapMap.image.altSelector) {
        const altImg = root.querySelector(swapMap.image.altSelector);
        if (altImg) {
            altImg.src = imageUrl;
            altImg.setAttribute("alt", altText);
            altImg.removeAttribute("srcset");
        }
    }
    // --- title (all matching nodes) ---
    // A theme card can carry the title in several nodes — e.g. a visible heading
    // plus a visually-hidden a11y label inside the card link. Set them all, or the
    // visible one keeps the harvested template's title.
    const titleEls = root.querySelectorAll(swapMap.title.selector);
    if (titleEls.length === 0) {
        throw new Error(`swapCardHtml: title selector "${swapMap.title.selector}" matched no elements`);
    }
    titleEls.forEach((el) => {
        el.textContent = product.title;
    });
    // --- links (all matching nodes) ---
    const linkEls = root.querySelectorAll(swapMap.link.selector);
    if (linkEls.length === 0) {
        throw new Error(`swapCardHtml: link selector "${swapMap.link.selector}" matched no elements`);
    }
    const productPath = `/products/${product.handle}`;
    linkEls.forEach((el) => {
        el.href = productPath;
    });
    // --- price ---
    const priceEl = root.querySelector(swapMap.price.selector);
    if (!priceEl) {
        throw new Error(`swapCardHtml: price selector "${swapMap.price.selector}" not found`);
    }
    const { amount, currencyCode } = product.priceRange.minVariantPrice;
    priceEl.textContent =
        moneyFormat && moneyFormat.length > 0
            ? formatMoneyTemplate(amount, moneyFormat)
            : formatPrice(amount, currencyCode);
    // --- form (skipped when swapMap.form is null — link-only surfaces) ---
    if (swapMap.form != null) {
        const formEl = root.querySelector(swapMap.form.selector);
        if (formEl) {
            // Set variant id input ([name=id]) so the form posts the correct variant.
            const variantInputEl = formEl.querySelector(swapMap.form.variantInput);
            if (variantInputEl && product.variants && product.variants.length > 0) {
                const numericVid = parseVariantId(product.variants[0].id);
                if (numericVid !== null) {
                    variantInputEl.value = String(numericVid);
                }
            }
            // Optional product-id node (Horizon: [data-product-id]).
            if (swapMap.form.productId) {
                const pidEl = root.querySelector(swapMap.form.productId);
                if (pidEl) {
                    const numericPid = numericProductId(product.id);
                    if (numericPid) {
                        pidEl.setAttribute("value", numericPid);
                        pidEl.setAttribute("data-product-id", numericPid);
                        pidEl.value = numericPid;
                    }
                }
            }
        }
    }
    return root.outerHTML;
}
/**
 * Clone `chipTemplateHtml`, swap the query term into the text node(s) and
 * rewrite the anchor href(s) to `/search?q=<encoded term>`.
 *
 * Throws when a required selector matches nothing so the caller can fall back
 * to the generic SuggestionList.
 */
export function swapChipHtml(chipTemplateHtml, chipSwap, term) {
    const doc = new DOMParser().parseFromString(chipTemplateHtml, "text/html");
    const root = doc.body.firstElementChild;
    if (!root) {
        throw new Error("swapChipHtml: chipTemplateHtml has no root element");
    }
    const textEls = root.querySelectorAll(chipSwap.text);
    if (textEls.length === 0) {
        throw new Error(`swapChipHtml: text selector "${chipSwap.text}" matched no elements`);
    }
    textEls.forEach((el) => {
        el.textContent = term;
    });
    const linkEls = root.querySelectorAll(chipSwap.link);
    if (linkEls.length === 0) {
        throw new Error(`swapChipHtml: link selector "${chipSwap.link}" matched no elements`);
    }
    linkEls.forEach((el) => {
        el.href = "/search?q=" + encodeURIComponent(term);
    });
    return root.outerHTML;
}
