/* ==========================================================================
   Niwa Chatbot -- In-chat Product Experience
   --------------------------------------------------------------------------
   Responsive 1/2/4/6 product grid and an expandable WooCommerce product view.
   Product meaning comes from structured product/attribute data, never labels.
   ========================================================================== */

window.Niwa = window.Niwa || {};

window.Niwa.ProductExperience = (function () {
  'use strict';

  var WISHLIST_KEY = 'niwa_ai_product_wishlist_v1';
  var states = [];
  var activeState = null;
  var lifecycleBound = false;
  var detailsCache = {};
  var detailsRequests = {};
  var prefetchTimer = null;

  function _utils() {
    return window.Niwa && window.Niwa.Utils;
  }

  function _renderer() {
    return window.Niwa && window.Niwa.MessageRenderer;
  }

  function _labels() {
    return (window.NiwaAI && window.NiwaAI.labels) || {};
  }

  function _label(key, fallback) {
    var labels = _labels();
    return labels[key] || fallback;
  }

  function _body() {
    return document.querySelector('.niwa-morph__body')
      || document.querySelector('.niwa-center__body');
  }

  function _shell() {
    return document.querySelector('.niwa-morph')
      || document.querySelector('.niwa-center');
  }

  function _clearTimer(state, key) {
    if (!state || state[key] == null) return;
    clearTimeout(state[key]);
    state[key] = null;
  }

  function _clearFrame(state) {
    if (!state || state.focusFrame == null) return;
    window.cancelAnimationFrame(state.focusFrame);
    state.focusFrame = null;
  }

  function _transitionTime(element) {
    if (!element || !window.getComputedStyle) return 0;
    var style = window.getComputedStyle(element);
    var durations = String(style.transitionDuration || '0s').split(',').map(function (value) {
      return parseFloat(value) * (String(value).indexOf('ms') !== -1 ? 1 : 1000);
    });
    var delays = String(style.transitionDelay || '0s').split(',').map(function (value) {
      return parseFloat(value) * (String(value).indexOf('ms') !== -1 ? 1 : 1000);
    });
    var count = Math.max(durations.length, delays.length);
    var longest = 0;
    for (var index = 0; index < count; index++) {
      longest = Math.max(longest, (durations[index % durations.length] || 0) + (delays[index % delays.length] || 0));
    }
    return longest;
  }

  function _scrollChatToBottom() {
    var body = _body();
    if (!body) return;
    var pin = function () { body.scrollTop = body.scrollHeight; };
    pin();
    requestAnimationFrame(function () {
      pin();
      requestAnimationFrame(pin);
    });
  }

  function _create(tag, className, text) {
    var el = document.createElement(tag);
    if (className) el.className = className;
    if (text !== undefined && text !== null) el.textContent = String(text);
    return el;
  }

  function _button(className, label, action) {
    var button = _create('button', className, label);
    button.type = 'button';
    if (action) button.setAttribute('data-product-action', action);
    return button;
  }

  function _heartSvg() {
    var holder = document.createElement('span');
    holder.className = 'niwa-product-heart__icon';
    holder.setAttribute('aria-hidden', 'true');
    holder.innerHTML = '<svg viewBox="0 0 24 24"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78L12 21.23l8.84-8.84a5.5 5.5 0 0 0 0-7.78Z"></path></svg>';
    return holder;
  }

  function _wishlistIds() {
    try {
      var value = JSON.parse(localStorage.getItem(WISHLIST_KEY) || '[]');
      return Array.isArray(value) ? value.map(String) : [];
    } catch (e) {
      return [];
    }
  }

  function _isWishlisted(productId) {
    return _wishlistIds().indexOf(String(productId || '')) !== -1;
  }

  function _setWishlist(productId, selected) {
    var id = String(productId || '');
    if (!id) return;
    var ids = _wishlistIds().filter(function (item) { return item !== id; });
    if (selected) ids.push(id);
    try { localStorage.setItem(WISHLIST_KEY, JSON.stringify(ids)); } catch (e) { /* ignore */ }
    document.querySelectorAll('.niwa-product-heart[data-niwa-wishlist-id="' + id + '"]').forEach(function (button) {
      button.setAttribute('aria-pressed', selected ? 'true' : 'false');
    });
  }

  function _wishlistButton(product, className) {
    var selected = _isWishlisted(product.id);
    var button = _button('niwa-product-heart ' + (className || ''), '', 'wishlist');
    button.setAttribute('data-niwa-wishlist-id', String(product.id || ''));
    button.setAttribute('aria-label', _label('wishlist_product', 'Save product'));
    button.setAttribute('aria-pressed', selected ? 'true' : 'false');
    button.appendChild(_heartSvg());
    return button;
  }

  function _track(type, product, meta) {
    var utils = _utils();
    if (!utils || typeof utils.trackFrontendEvent !== 'function') return;
    utils.trackFrontendEvent(type, {
      source: 'niwa_product_experience',
      product_id: product && product.id ? product.id : 0,
      product_name: product && product.name ? product.name : '',
      meta: meta || {}
    });
  }

  function _normalizeProducts(products) {
    var utils = _utils();
    var normalized = (Array.isArray(products) ? products : []).map(function (product) {
      return utils && typeof utils.normalizeProduct === 'function'
        ? utils.normalizeProduct(product)
        : product;
    }).filter(Boolean);

    if (normalized.length >= 6) return normalized.slice(0, 6);
    if (normalized.length >= 4) return normalized.slice(0, 4);
    if (normalized.length >= 2) return normalized.slice(0, 2);
    return normalized.slice(0, 1);
  }

  function _saleLabel(product) {
    if (!product) return '';
    if (product.sale_percent_label) return String(product.sale_percent_label);
    if (product.badge) return String(product.badge);
    return product.discount_percent > 0 ? '-' + String(product.discount_percent) + '%' : '';
  }

  function _saleBadge(product) {
    var label = _saleLabel(product);
    return label ? _create('span', 'niwa-product-sale-badge', label) : null;
  }

  function _largestImageMetrics(products) {
    return (products || []).reduce(function (largest, product) {
      var width = parseInt(product && product.image_width, 10) || 0;
      var height = parseInt(product && product.image_height, 10) || 0;
      return width > 0 && height > 0 && (width * height) > largest.area
        ? { width: width, height: height, area: width * height }
        : largest;
    }, { width: 0, height: 0, area: 0 });
  }

  function _applyImageRatio(container, products) {
    if (!container) return;
    var largest = _largestImageMetrics(products);

    function apply(width, height) {
      var area = width * height;
      if (!width || !height || area < largest.area) return;
      largest = { width: width, height: height, area: area };
      container.style.setProperty('--niwa-card-ratio', width + ' / ' + height);
    }

    apply(largest.width, largest.height);
    container.querySelectorAll('img').forEach(function (image) {
      function measure() { apply(image.naturalWidth || 0, image.naturalHeight || 0); }
      if (image.complete) measure();
      else image.addEventListener('load', measure, { once: true });
    });
  }

  function _insertBeforeResponse(body, wrap, anchor) {
    var response = anchor && anchor.parentNode === body ? anchor : null;
    if (!response && body.lastElementChild && body.lastElementChild.classList.contains('niwa-message--bot')) {
      response = body.lastElementChild;
    }
    if (response) body.insertBefore(wrap, response);
    else body.appendChild(wrap);
    return response;
  }

  function _makeTile(state, product, position) {
    var tile = _create('article', 'niwa-product-tile');
    tile.setAttribute('data-niwa-product-id', String(product.id || ''));
    if (product.match_class) tile.setAttribute('data-niwa-match-class', String(product.match_class));

    var open = _button('niwa-product-tile__open', product.name || _label('view_details', 'View details'), 'open');
    open.setAttribute('aria-label', (product.name || _label('view_details', 'View details')));
    open.setAttribute('data-position', String(position));
    if (product.image) {
      var image = _create('img', 'niwa-product-tile__image');
      image.src = product.image;
      image.alt = product.name || '';
      image.loading = 'lazy';
      open.appendChild(image);
    }

    tile.appendChild(open);
    var saleBadge = _saleBadge(product);
    if (saleBadge) tile.appendChild(saleBadge);
    tile.appendChild(_wishlistButton(product, 'niwa-product-tile__heart'));
    tile.addEventListener('click', function (event) {
      var action = event.target.closest('[data-product-action]');
      if (!action) return;
      if (action.getAttribute('data-product-action') === 'wishlist') {
        event.preventDefault();
        event.stopPropagation();
        var selected = action.getAttribute('aria-pressed') !== 'true';
        _setWishlist(product.id, selected);
        _track('product_wishlist_toggle', product, { selected: selected, position: position });
        return;
      }
      if (action.getAttribute('data-product-action') === 'open') {
        _openProduct(state, product, false);
        _track('product_card_click', product, { action: 'open_in_chat', position: position });
      }
    });

    return tile;
  }

  function _detailsRequest(product) {
    var ajaxUrl = window.NiwaAI && window.NiwaAI.ajax;
    var nonce = window.NiwaAI && window.NiwaAI.nonce;
    if (!ajaxUrl || !nonce || !product || !product.id) return Promise.resolve(product || {});

    var productId = String(product.id);
    if (detailsCache[productId]) return Promise.resolve(detailsCache[productId]);
    if (detailsRequests[productId]) return detailsRequests[productId];

    var fd = new FormData();
    fd.append('action', 'niwa_ai_get_product_details');
    fd.append('nonce', nonce);
    fd.append('product_id', String(product.id));

    var request = fetch(ajaxUrl, { method: 'POST', body: fd })
      .then(function (response) { return response.json(); })
      .then(function (payload) {
        if (!payload || !payload.success || !payload.product) throw new Error('product_details_unavailable');
        var utils = _utils();
        var normalized = utils && typeof utils.normalizeProduct === 'function'
          ? utils.normalizeProduct(payload.product)
          : payload.product;
        normalized.short_desc = payload.product.short_desc || '';
        normalized.description = normalized.short_desc;
        normalized.attributes = Array.isArray(payload.product.attributes) ? payload.product.attributes : [];
        normalized.variations = Array.isArray(payload.product.variations) ? payload.product.variations : [];
        normalized.related_products = _normalizeProducts(payload.product.related_products || []);
        return normalized;
      });

    detailsRequests[productId] = request.then(function (details) {
      detailsCache[productId] = details;
      delete detailsRequests[productId];
      return details;
    }, function (error) {
      delete detailsRequests[productId];
      throw error;
    });
    return detailsRequests[productId];
  }

  function _scheduleDetailsPrefetch(state, products) {
    if (prefetchTimer != null) clearTimeout(prefetchTimer);
    prefetchTimer = setTimeout(function () {
      prefetchTimer = null;
      (products || []).forEach(function (product) {
        _detailsRequest(product).then(function (details) {
          state.cache[String(product.id || '')] = details;
        }).catch(function () { /* The normal click path can retry. */ });
      });
    }, 0);
  }

  function _focusElement(state) {
    if (state.focus) return state.focus;
    var focus = _create('section', 'niwa-product-focus');
    focus.hidden = true;
    focus.setAttribute('aria-live', 'polite');
    focus.addEventListener('click', function (event) { _handleFocusAction(state, event); });
    state.focus = focus;
    return focus;
  }

  function _placeFocus(state) {
    var shell = _shell();
    if (!shell) return;
    var focus = _focusElement(state);
    var composer = shell.querySelector('.niwa-morph__pill, .niwa-center__pill');
    if (composer) shell.insertBefore(focus, composer);
    else shell.appendChild(focus);
  }

  function _surfaceImage(product) {
    var image = _create('img', 'niwa-product-focus__thumb');
    image.src = product.image || '';
    image.alt = product.name || '';
    _applyFocusImageRatio(image, product);
    if (!product.url) return image;

    var link = _create('a', 'niwa-product-focus__thumb-link');
    link.href = product.url;
    link.setAttribute('aria-label', _label('view_product', 'View product') + ': ' + (product.name || ''));
    _applyFocusImageRatio(link, product);
    link.appendChild(image);
    return link;
  }

  function _applyFocusImageRatio(element, product) {
    var width = parseInt(product && product.image_width, 10) || 0;
    var height = parseInt(product && product.image_height, 10) || 0;
    if (element && width > 0 && height > 0) {
      element.style.setProperty('--niwa-focus-image-ratio', width + ' / ' + height);
    }
  }

  function _priceElement(product, className) {
    var price = _create('div', className || 'niwa-product-focus__price');
    var utils = _utils();
    var sanitize = utils && typeof utils.sanitizePriceHtml === 'function'
      ? utils.sanitizePriceHtml
      : function () { return ''; };
    price.innerHTML = sanitize(product.price || '');
    if (product.oldPrice) {
      var oldPrice = _create('del', 'niwa-product-focus__old-price');
      oldPrice.innerHTML = sanitize(product.oldPrice);
      price.appendChild(oldPrice);
    }
    var badge = _saleBadge(product);
    if (badge) {
      badge.classList.add('niwa-product-focus__price-badge');
      price.appendChild(badge);
    }
    return price;
  }

  function _hasVisualVariations(product) {
    return _visualVariations(product).length > 1;
  }

  function _visualVariations(product) {
    var seen = {};
    return (product.variations || []).filter(function (variation) {
      if (!variation.image || seen[variation.image]) return false;
      seen[variation.image] = true;
      return true;
    });
  }

  function _visualAttributeNames(product) {
    var byImage = {};
    (product.variations || []).forEach(function (variation) {
      if (!variation.image) return;
      if (!byImage[variation.image]) byImage[variation.image] = [];
      byImage[variation.image].push(variation);
    });

    var images = Object.keys(byImage);
    if (images.length < 2) return [];

    var attributes = {};
    (product.variations || []).forEach(function (variation) {
      Object.keys(variation.attributes || {}).forEach(function (name) { attributes[name] = true; });
    });

    return Object.keys(attributes).filter(function (name) {
      var values = {};
      var consistent = images.every(function (image) {
        var imageValues = {};
        byImage[image].forEach(function (variation) {
          var value = String((variation.attributes || {})[name] || '');
          if (value) imageValues[value] = true;
        });
        var keys = Object.keys(imageValues);
        if (keys.length !== 1) return false;
        values[keys[0]] = true;
        return true;
      });
      return consistent && Object.keys(values).length > 1;
    });
  }

  function _optionAttributes(product) {
    var visual = {};
    _visualAttributeNames(product).forEach(function (name) { visual[name] = true; });
    return (product.attributes || product.variation_options || []).filter(function (attribute) {
      return !visual[attribute.attribute];
    });
  }

  function _optionPanelLabel(state, product) {
    var attributes = _optionAttributes(product);
    if (attributes.length === 1 && attributes[0].label) {
      var attribute = attributes[0];
      var selected = String(state.selection[attribute.attribute] || '');
      var choice = (attribute.choices || []).find(function (item) {
        return String(item.value) === selected;
      });
      return attribute.label + ':' + (selected ? ' ' + (choice ? choice.label : selected) : '');
    }
    return _label('select_options', 'Select options');
  }

  function _attributeChoiceLabel(product, attributeName, value) {
    var attribute = (product.attributes || product.variation_options || []).find(function (item) {
      return item.attribute === attributeName;
    });
    var choice = attribute && _choiceData(attribute).find(function (item) {
      return String(item.value) === String(value || '');
    });
    return choice ? choice.label : String(value || '');
  }

  function _visualSelectionLabel(state, product) {
    var values = _visualAttributeNames(product).map(function (attributeName) {
      var value = state.selection[attributeName];
      return value ? _attributeChoiceLabel(product, attributeName, value) : '';
    }).filter(Boolean);
    return values.length ? values.join(' / ') : _label('see_all_colors', 'See all colors');
  }

  function _renderChips(state, focus, product) {
    var description = product.short_desc || '';
    var related = product.related_products || [];
    if (!description && !related.length) return;

    var chips = _create('div', 'niwa-product-focus__chips');
    if (description) {
      var descriptionButton = _button('niwa-product-focus__chip', _label('product_description', 'Description'), 'description');
      descriptionButton.setAttribute('aria-pressed', state.panel === 'description' ? 'true' : 'false');
      chips.appendChild(descriptionButton);
    }
    if (related.length) {
      var relatedButton = _button('niwa-product-focus__chip', _label('goes_well_with', 'Goes well with'), 'related');
      relatedButton.setAttribute('aria-pressed', state.panel === 'related' ? 'true' : 'false');
      chips.appendChild(relatedButton);
    }
    focus.appendChild(chips);
  }

  function _panelHeader(title, showCollapse) {
    var header = _create('div', 'niwa-product-panel__header');
    header.appendChild(_create('strong', 'niwa-product-panel__title', title));
    if (showCollapse !== false) {
      var collapse = _button('niwa-product-panel__collapse', '−', 'panel-close');
      collapse.setAttribute('aria-label', _label('close_panel', 'Close panel'));
      header.appendChild(collapse);
    }
    return header;
  }

  function _renderDescription(product) {
    var panel = _create('section', 'niwa-product-panel niwa-product-panel--description');
    panel.appendChild(_panelHeader(_label('product_description', 'Description')));
    panel.appendChild(_create('p', 'niwa-product-panel__text', product.short_desc || ''));
    return panel;
  }

  function _renderRelated(state, product) {
    var panel = _create('section', 'niwa-product-panel niwa-product-panel--related');
    panel.appendChild(_panelHeader(_label('goes_well_with', 'Goes well with')));
    var grid = _create('div', 'niwa-product-related-grid');
    var relatedProducts = (product.related_products || []).slice(0, 8);
    relatedProducts.forEach(function (related) {
      var tile = _create('article', 'niwa-product-related');
      tile.setAttribute('data-niwa-product-id', String(related.id || ''));
      var media = _create('div', 'niwa-product-related__media');
      var open = _button('niwa-product-related__open', related.name || _label('view_details', 'View details'), 'open-related');
      open._niwaProduct = related;
      if (related.image) {
        var image = _create('img', 'niwa-product-related__image');
        image.src = related.image;
        image.alt = related.name || '';
        image.loading = 'lazy';
        open.appendChild(image);
      }
      media.appendChild(open);
      var saleBadge = _saleBadge(related);
      if (saleBadge) media.appendChild(saleBadge);
      var relatedHeart = _wishlistButton(related, 'niwa-product-related__heart');
      relatedHeart._niwaProduct = related;
      media.appendChild(relatedHeart);
      if (!related.hasOptions) {
        var add = _button('niwa-product-related__add', '+', 'related-cart');
        add._niwaProduct = related;
        add.setAttribute('aria-label', _label('add_to_cart', 'Add to cart') + ': ' + (related.name || ''));
        if (related.in_stock === false || related.purchasable === false) {
          add.disabled = true;
          add.setAttribute('aria-disabled', 'true');
        }
        media.appendChild(add);
      }
      tile.appendChild(media);
      tile.appendChild(_create('p', 'niwa-product-related__name', related.name || ''));
      grid.appendChild(tile);
    });
    _applyImageRatio(grid, relatedProducts);
    panel.appendChild(grid);
    window.requestAnimationFrame(function () {
      var first = grid.querySelector('.niwa-product-related');
      if (first && relatedProducts.length > 4) {
        grid.style.maxBlockSize = ((first.getBoundingClientRect().height * 2) + 7) + 'px';
      }
    });
    return panel;
  }

  function _renderVariations(state, product) {
    var panel = _create('section', 'niwa-product-panel niwa-product-panel--variations');
    var browser = _create('div', 'niwa-product-variation-browser');
    var rail = _create('div', 'niwa-product-variation-rail');
    var variations = _visualVariations(product);
    variations.forEach(function (variation) {
      var option = _button('niwa-product-variation', _label('select_variation', 'Select variation'), 'variation');
      option._niwaVariation = variation;
      option.setAttribute('aria-pressed', state.variation && Number(state.variation.id) === Number(variation.id) ? 'true' : 'false');
      var visualLabel = _visualAttributeNames(product).map(function (attributeName) {
        return _attributeChoiceLabel(product, attributeName, (variation.attributes || {})[attributeName]);
      }).filter(Boolean).join(' / ');
      if (visualLabel) option.setAttribute('aria-label', visualLabel);
      var image = _create('img', 'niwa-product-variation__image');
      image.src = variation.image;
      image.alt = product.name || '';
      option.appendChild(image);
      rail.appendChild(option);
    });
    _applyImageRatio(rail, variations);
    browser.appendChild(rail);
    if (variations.length > 3) {
      var navigation = _create('div', 'niwa-product-variation-navigation');
      var previous = _button('niwa-product-variation-navigation__button', '‹', 'variation-previous');
      previous.setAttribute('aria-label', _label('previous_variation', 'Previous variation'));
      var pagination = _create('div', 'niwa-product-variation-pagination');
      var pageCount = Math.ceil(variations.length / 3);
      for (var page = 0; page < pageCount; page++) {
        var dot = _button('niwa-product-variation-pagination__dot', '', 'variation-page');
        dot._niwaPage = page;
        dot.setAttribute('aria-label', _label('product_page', 'Product page') + ' ' + (page + 1));
        dot.setAttribute('aria-current', page === 0 ? 'true' : 'false');
        pagination.appendChild(dot);
      }
      var next = _button('niwa-product-variation-navigation__button', '›', 'variation-next');
      next.setAttribute('aria-label', _label('next_variation', 'Next variation'));
      navigation.appendChild(previous);
      navigation.appendChild(pagination);
      navigation.appendChild(next);
      browser.appendChild(navigation);
      rail.addEventListener('scroll', function () {
        var currentPage = Math.round(rail.scrollLeft / Math.max(1, rail.clientWidth));
        pagination.querySelectorAll('.niwa-product-variation-pagination__dot').forEach(function (pageDot, index) {
          pageDot.setAttribute('aria-current', index === currentPage ? 'true' : 'false');
        });
      }, { passive: true });
    }
    panel.appendChild(browser);
    return panel;
  }

  function _choiceData(attribute) {
    if (Array.isArray(attribute.choices) && attribute.choices.length) return attribute.choices;
    return (attribute.options || []).map(function (option) { return { value: option, label: option }; });
  }

  function _renderOptions(state, product) {
    var panel = _create('section', 'niwa-product-panel niwa-product-panel--options');
    panel.appendChild(_panelHeader(_optionPanelLabel(state, product), false));
    var groups = _create('div', 'niwa-product-option-groups');
    var attributes = _optionAttributes(product);
    if (attributes.length === 1) panel.classList.add('is-single-attribute');

    attributes.forEach(function (attribute) {
      var group = _create('fieldset', 'niwa-product-option-group');
      group.appendChild(_create('legend', 'niwa-product-option-group__label', attribute.label || _label('option', 'Option')));
      var choices = _create('div', 'niwa-product-option-group__choices');
      _choiceData(attribute).forEach(function (choice) {
        var button = _button('niwa-product-option', choice.label || choice.value, 'attribute');
        button._niwaAttribute = attribute.attribute;
        button._niwaValue = choice.value;
        button.setAttribute('aria-pressed', state.selection[attribute.attribute] === choice.value ? 'true' : 'false');
        choices.appendChild(button);
      });
      group.appendChild(choices);
      groups.appendChild(group);
    });

    panel.appendChild(groups);
    var back = _button('niwa-product-panel__back', '‹', 'panel-close');
    back.setAttribute('aria-label', _label('back_to_product', 'Back to product'));
    panel.appendChild(back);
    return panel;
  }

  function _buildPanel(state, product, panel) {
    if (panel === 'description') return _renderDescription(product);
    if (panel === 'related') return _renderRelated(state, product);
    if (panel === 'variations') return _renderVariations(state, product);
    if (panel === 'options') return _renderOptions(state, product);
    return null;
  }

  function _renderPanels(state, focus, product) {
    var available = {
      description: !!(product.short_desc || ''),
      related: !!(product.related_products || []).length,
      variations: _hasVisualVariations(product),
      options: !!_optionAttributes(product).length
    };
    Object.keys(available).forEach(function (panelName) {
      if (!available[panelName]) return;
      var panel = _buildPanel(state, product, panelName);
      panel.setAttribute('data-niwa-product-panel', panelName);
      _setPanelVisibility(panel, state.panel === panelName);
      focus.appendChild(panel);
    });
  }

  function _setPanelVisibility(panel, visible) {
    panel.hidden = !visible;
    panel.inert = !visible;
    panel.setAttribute('aria-hidden', visible ? 'false' : 'true');
  }

  function _syncPanelVisibility(state, focus) {
    focus.querySelectorAll('[data-niwa-product-panel]').forEach(function (panel) {
      _setPanelVisibility(panel, panel.getAttribute('data-niwa-product-panel') === state.panel);
    });
  }

  function _applyPanelState(state, focus) {
    var panel = state.panel || '';
    focus.classList.remove('is-info-open', 'is-description-mode', 'is-related-mode', 'is-variations-mode', 'is-options-mode');
    if (panel) focus.classList.add('is-info-open', 'is-' + panel + '-mode');
    focus.querySelectorAll('.niwa-product-focus__chip').forEach(function (chip) {
      chip.setAttribute('aria-pressed', chip.getAttribute('data-product-action') === panel ? 'true' : 'false');
    });
    var close = focus.querySelector('.niwa-product-focus__close');
    if (close) {
      close.setAttribute('data-product-action', panel ? 'panel-close' : 'close');
      close.setAttribute('aria-label', panel ? _label('back_to_product', 'Back to product') : _label('close_product', 'Close product'));
      close.textContent = '×';
    }
  }

  function _actionMode() {
    var cfg = window.NiwaAI || {};
    var nested = cfg.product_card || {};
    return String(nested.action_mode || cfg.product_card_action_mode || 'store').toLowerCase();
  }

  function _isCatalogProduct(product) {
    return _actionMode() === 'catalog' || (product && product.purchasable === false);
  }

  function _primaryActionLabel(state, product) {
    if (_isCatalogProduct(product)) return _label('catalog_cta', 'View more');
    var attributes = product.attributes || product.variation_options || [];
    return (product.hasOptions || attributes.length) && !_matchingVariation(product, state.selection)
      ? _label('select_options', 'Select options')
      : _label('add_to_cart', 'Add to cart');
  }

  function _renderSurface(state, focus, product) {
    var surface = _create('div', 'niwa-product-focus__surface');
    var summary = _create('div', 'niwa-product-focus__summary');
    summary.appendChild(_surfaceImage(product));

    var copy = _create('div', 'niwa-product-focus__copy');
    copy.appendChild(_create('h3', 'niwa-product-focus__title', product.name || ''));
    copy.appendChild(_priceElement(product));
    if (product.meta) copy.appendChild(_create('p', 'niwa-product-focus__meta', product.meta));
    summary.appendChild(copy);
    summary.appendChild(_wishlistButton(product, 'niwa-product-focus__favorite'));
    surface.appendChild(summary);

    var actions = _create('div', 'niwa-product-focus__actions');
    var catalogProduct = _isCatalogProduct(product);
    var primaryLabel = _primaryActionLabel(state, product);
    var primary = _button('niwa-product-focus__action niwa-product-focus__action--primary', primaryLabel, 'cart');
    if (state.loading && product.hasOptions) {
      primary.disabled = true;
      primary.setAttribute('aria-busy', 'true');
    }
    if (!catalogProduct && (product.in_stock === false || product.purchasable === false)) {
      primary.disabled = true;
      primary.setAttribute('aria-disabled', 'true');
    }
    actions.appendChild(primary);
    if (_hasVisualVariations(product)) {
      actions.appendChild(_button('niwa-product-focus__action', _visualSelectionLabel(state, product), 'variations'));
    }
    if (_optionAttributes(product).length) {
      actions.appendChild(_button('niwa-product-focus__action', _optionPanelLabel(state, product), 'options'));
    }
    var close = _button('niwa-product-focus__close', '×', 'close');
    close.setAttribute('aria-label', _label('close_product', 'Close product'));
    actions.appendChild(close);
    surface.appendChild(actions);
    focus.appendChild(surface);
  }

  function _renderFocus(state) {
    var focus = _focusElement(state);
    var product = state.details || state.product;
    var wasVisible = focus.classList.contains('is-visible');
    var wasClosing = focus.classList.contains('is-closing');
    focus.innerHTML = '';
    focus.className = 'niwa-product-focus';
    if (wasVisible) focus.classList.add('is-visible');
    if (wasClosing) focus.classList.add('is-closing');
    _renderChips(state, focus, product);
    _renderPanels(state, focus, product);
    _renderSurface(state, focus, product);
    _applyPanelState(state, focus);
  }

  function _renderFocusWithHeightTransition(state) {
    var focus = _focusElement(state);
    var canAnimate = !focus.hidden
      && focus.classList.contains('is-visible')
      && !focus.classList.contains('is-closing');
    if (!canAnimate) {
      _renderFocus(state);
      return;
    }

    _clearTimer(state, 'heightTimer');
    _clearFrame(state);
    var startSize = focus.getBoundingClientRect().height;
    focus.style.blockSize = startSize + 'px';
    focus.offsetHeight;
    _renderFocus(state);
    var targetSize = focus.scrollHeight;
    state.focusFrame = requestAnimationFrame(function () {
      state.focusFrame = null;
      if (!focus.hidden && focus.classList.contains('is-visible') && !focus.classList.contains('is-closing')) {
        focus.style.blockSize = targetSize + 'px';
        _scrollChatToBottom();
      }
    });
    state.heightTimer = setTimeout(function () {
      state.heightTimer = null;
      if (!focus.classList.contains('is-closing')) {
        focus.style.removeProperty('block-size');
        _scrollChatToBottom();
      }
    }, 520);
  }

  function _syncSelectionUi(state) {
    var focus = _focusElement(state);
    var product = state.details || state.product;
    var price = focus.querySelector('.niwa-product-focus__price');
    var image = focus.querySelector('.niwa-product-focus__thumb');
    var utils = _utils();
    if (price) {
      price.replaceWith(_priceElement(product));
    }
    if (image && product.image) image.src = product.image;
    if (image) {
      _applyFocusImageRatio(image, product);
      _applyFocusImageRatio(image.closest('.niwa-product-focus__thumb-link'), product);
    }
    focus.querySelectorAll('.niwa-product-variation').forEach(function (option) {
      var selected = state.variation && option._niwaVariation
        && Number(option._niwaVariation.id) === Number(state.variation.id);
      option.setAttribute('aria-pressed', selected ? 'true' : 'false');
    });
    focus.querySelectorAll('.niwa-product-option').forEach(function (option) {
      var selected = state.selection[option._niwaAttribute] === option._niwaValue;
      option.setAttribute('aria-pressed', selected ? 'true' : 'false');
    });
    var optionsButton = focus.querySelector('[data-product-action="options"]');
    if (optionsButton) optionsButton.textContent = _optionPanelLabel(state, product);
    var optionsTitle = focus.querySelector('.niwa-product-panel--options .niwa-product-panel__title');
    if (optionsTitle) optionsTitle.textContent = _optionPanelLabel(state, product);
    var variationsButton = focus.querySelector('[data-product-action="variations"]');
    if (variationsButton) variationsButton.textContent = _visualSelectionLabel(state, product);
    var primary = focus.querySelector('[data-product-action="cart"]');
    if (primary) primary.textContent = _primaryActionLabel(state, product);
  }

  function _selectTile(state, productId) {
    state.wrap.querySelectorAll('.niwa-product-tile').forEach(function (tile) {
      tile.setAttribute('aria-current', tile.getAttribute('data-niwa-product-id') === String(productId) ? 'true' : 'false');
    });
  }

  function _showFocus(state) {
    var focus = _focusElement(state);
    var shell = _shell();
    _clearTimer(state, 'hideTimer');
    _clearTimer(state, 'heightTimer');
    _clearFrame(state);
    if (shell) shell.classList.add('is-product-open');
    focus.hidden = false;
    focus.classList.remove('is-closing');
    focus.style.removeProperty('block-size');
    focus.getBoundingClientRect();
    focus.classList.add('is-visible');
  }

  function _withCardPreview(details, card) {
    if (!details || !card || !card.preview_image) return details;
    var previewed = Object.assign({}, details);
    previewed.image = card.preview_image;
    previewed.image_width = card.preview_image_width || card.image_width || details.image_width || 0;
    previewed.image_height = card.preview_image_height || card.image_height || details.image_height || 0;
    return previewed;
  }

  function _seedCardPreviewSelection(state, product, card) {
    if (!state || !product || !card) return;
    var previewAttributes = card.preview_attributes && typeof card.preview_attributes === 'object'
      ? card.preview_attributes
      : {};
    Object.keys(previewAttributes).forEach(function (name) {
      var value = String(previewAttributes[name] || '');
      if (value) state.selection[name] = value;
    });

    var previewId = parseInt(card.preview_variation_id || 0, 10) || 0;
    if (!previewId) return;
    var variation = (product.variations || []).find(function (candidate) {
      return Number(candidate.id) === previewId;
    });
    if (variation) state.variation = variation;
  }

  function _hideFocus(state, remember, immediate) {
    if (!state || !state.focus) return;
    var shell = _shell();
    var relatedWasOpen = state.focus.classList.contains('is-related-mode');
    _clearTimer(state, 'hideTimer');
    _clearTimer(state, 'heightTimer');
    _clearTimer(state, 'panelTimer');
    _clearTimer(state, 'restoreTimer');
    _clearFrame(state);
    if (shell) shell.classList.remove('is-product-open');
    if (!remember) {
      state.remembered = null;
      state.restoreOnOpen = false;
      _selectTile(state, 0);
    }
    state.panel = '';
    if (state.focus.hidden) return;
    if (immediate) {
      state.focus.hidden = true;
      state.focus.className = 'niwa-product-focus';
      state.focus.style.removeProperty('block-size');
      return;
    }
    var currentSize = state.focus.getBoundingClientRect().height;
    state.focus.style.blockSize = currentSize + 'px';
    state.focus.offsetHeight;
    state.focus.classList.add('is-closing');
    state.focus.classList.remove('is-visible');
    state.focusFrame = requestAnimationFrame(function () {
      state.focusFrame = null;
      state.focus.style.blockSize = '0px';
    });
    var closeDelay = relatedWasOpen ? 620 : 500;
    state.hideTimer = setTimeout(function () {
      state.hideTimer = null;
      if (!state.focus.classList.contains('is-visible')) {
        state.focus.hidden = true;
        state.focus.classList.remove('is-closing');
        state.focus.style.removeProperty('block-size');
      }
    }, closeDelay);
  }

  function _closeOtherState(nextState) {
    if (activeState && activeState !== nextState) _hideFocus(activeState, false);
    activeState = nextState;
  }

  function _openProduct(state, product, restoring) {
    if (!state || !product) return;
    var currentId = state.product ? String(state.product.id || '') : '';
    var nextId = String(product.id || '');
    var sameProduct = activeState === state
      && state.product
      && ((currentId && currentId === nextId) || state.product === product)
      && state.focus
      && !state.focus.hidden
      && state.focus.classList.contains('is-visible')
      && !state.focus.classList.contains('is-closing');
    if (sameProduct) {
      state.product = product;
      state.remembered = product;
      state.panel = '';
      state.selection = {};
      state.variation = null;
      var sameDetails = state.cache[String(product.id || '')] || detailsCache[String(product.id || '')] || state.details || product;
      state.details = _withCardPreview(sameDetails, product);
      _seedCardPreviewSelection(state, state.details, product);
      _renderFocus(state);
      _selectTile(state, product.id);
      return;
    }

    _clearTimer(state, 'hideTimer');
    _clearTimer(state, 'heightTimer');
    _clearTimer(state, 'panelTimer');
    _clearTimer(state, 'restoreTimer');
    _clearFrame(state);
    _closeOtherState(state);
    state.product = product;
    state.remembered = product;
    state.restoreOnOpen = true;
    state.panel = '';
    state.selection = {};
    state.variation = null;
    _selectTile(state, product.id);
    _placeFocus(state);

    var cached = state.cache[String(product.id || '')] || detailsCache[String(product.id || '')];
    if (cached) state.cache[String(product.id || '')] = cached;
    state.loading = !cached && !!product.hasOptions;
    state.details = cached ? _withCardPreview(cached, product) : product;
    _seedCardPreviewSelection(state, state.details, product);
    _renderFocus(state);
    _showFocus(state);
    _scrollChatToBottom();
    if (cached) return;

    state.loading = true;
    _detailsRequest(product).then(function (details) {
      state.loading = false;
      if (!state.product || String(state.product.id) !== String(product.id)) return;
      state.cache[String(product.id || '')] = details;
      state.details = _withCardPreview(details, product);
      _seedCardPreviewSelection(state, state.details, product);
      _renderFocusWithHeightTransition(state);
    }).catch(function () {
      state.loading = false;
      if (!state.product || String(state.product.id) !== String(product.id)) return;
      state.details = product;
      _renderFocusWithHeightTransition(state);
    });
  }

  function _matchingVariation(product, selection) {
    var attributes = product.attributes || product.variation_options || [];
    if (!attributes.length) return null;
    var complete = attributes.every(function (attribute) { return !!selection[attribute.attribute]; });
    if (!complete) return null;

    return (product.variations || []).find(function (variation) {
      if (variation.in_stock === false || variation.purchasable === false) return false;
      return attributes.every(function (attribute) {
        var expected = String(variation.attributes && variation.attributes[attribute.attribute] || '');
        return !expected || expected === String(selection[attribute.attribute] || '');
      });
    }) || null;
  }

  function _selectVariation(state, variation) {
    var product = state.details || state.product;
    var visualAttributes = _visualAttributeNames(product);
    state.variation = variation;
    if (visualAttributes.length) {
      visualAttributes.forEach(function (name) {
        var value = String((variation.attributes || {})[name] || '');
        if (value) state.selection[name] = value;
      });
    } else {
      state.selection = Object.assign({}, state.selection, variation.attributes || {});
    }
    if (variation.image) state.details.image = variation.image;
    if (variation.image_width) state.details.image_width = variation.image_width;
    if (variation.image_height) state.details.image_height = variation.image_height;
    if (variation.price) state.details.price = variation.price;
    _syncSelectionUi(state);
  }

  function _openPanel(state, panel) {
    _transitionPanel(state, panel);
  }

  function _releasePanelFocus(focus, nextPanel) {
    var active = document.activeElement;
    if (!active || !focus.contains(active) || typeof active.closest !== 'function') return false;
    var currentPanel = active.closest('[data-niwa-product-panel]');
    if (!currentPanel || currentPanel.getAttribute('data-niwa-product-panel') === nextPanel) return false;
    if (typeof active.blur === 'function') active.blur();
    return true;
  }

  function _transitionPanel(state, panel) {
    if (!state) return;
    var nextPanel = panel || '';
    if (state.panel === nextPanel) return;
    var focus = _focusElement(state);
    _clearTimer(state, 'panelTimer');
    _clearTimer(state, 'heightTimer');
    _clearFrame(state);
    focus.style.removeProperty('block-size');
    var releasedFocus = _releasePanelFocus(focus, nextPanel);
    state.panel = nextPanel;
    _applyPanelState(state, focus);
    _syncPanelVisibility(state, focus);
    if (releasedFocus && !nextPanel) {
      var primary = focus.querySelector('.niwa-product-focus__action--primary');
      if (primary && typeof primary.focus === 'function') primary.focus({ preventScroll: true });
    }
  }

  function _addSimpleProductToCart(product, button) {
    if (!product || !product.id || product.hasOptions || !window.Niwa.ProductManager) return;
    button.disabled = true;
    button.textContent = _label('adding_to_cart', 'Adding...');
    window.Niwa.ProductManager.addToCartSmart(product.id, 1, button, function (ok) {
      button.disabled = false;
      button.textContent = ok ? '✓' : '+';
      if (!ok) return;
      _track('product_add_to_cart', product, { related: true });
      if (window.Niwa.MorphController && window.Niwa.MorphController.isOpen()) {
        window.Niwa.MorphController.close();
      }
    }, {
      recommended: true,
      source: 'in_chat_related_product',
      context: 'product_recommendation'
    });
  }

  function _addToCart(state, button) {
    var product = state.details || state.product;
    if (_isCatalogProduct(product)) {
      _track('product_card_click', product, { action: 'view_details', destination: product.url || '' });
      window.location.href = product.url || '#';
      return;
    }

    var attributes = product.attributes || product.variation_options || [];
    if (product.hasOptions && state.loading) return;
    if (product.hasOptions && !attributes.length) {
      var renderer = _renderer();
      var labels = _labels();
      if (renderer) renderer.addMessage(labels.cart_error || 'Could not add to cart. Please try again.', 'bot');
      return;
    }
    var variation = attributes.length ? _matchingVariation(product, state.selection) : null;
    if (attributes.length && !variation) {
      var visualAttributes = _visualAttributeNames(product);
      var missingVisual = visualAttributes.some(function (name) { return !state.selection[name]; });
      var optionAttributes = _optionAttributes(product);
      _openPanel(state, (missingVisual || !optionAttributes.length) && _hasVisualVariations(product) ? 'variations' : 'options');
      return;
    }

    if (!window.Niwa.ProductManager || typeof window.Niwa.ProductManager.addToCartSmart !== 'function') return;
    button.disabled = true;
    button.textContent = _label('adding_to_cart', 'Adding...');
    window.Niwa.ProductManager.addToCartSmart(product.id, 1, button, function (ok) {
      button.disabled = false;
      button.textContent = ok ? _label('added_to_cart', 'Added') : _label('add_to_cart', 'Add to cart');
      if (ok) {
        button.classList.add('is-added');
        _track('product_add_to_cart', product, { variation_id: variation ? variation.id : 0 });
        if (window.Niwa.MorphController && window.Niwa.MorphController.isOpen()) {
          window.Niwa.MorphController.close();
        }
      }
    }, {
      recommended: true,
      source: 'in_chat_product',
      context: 'product_recommendation',
      variationId: variation ? variation.id : 0,
      variationAttributes: variation ? state.selection : {}
    });
  }

  function _handleFocusAction(state, event) {
    var control = event.target.closest('[data-product-action]');
    if (!control) return;
    var action = control.getAttribute('data-product-action');
    var product = state.details || state.product;

    if (action === 'wishlist') {
      var wishlistProduct = control._niwaProduct || product;
      var selected = control.getAttribute('aria-pressed') !== 'true';
      _setWishlist(wishlistProduct.id, selected);
      _track('product_wishlist_toggle', wishlistProduct, { selected: selected });
    } else if (action === 'close') {
      _hideFocus(state, false);
    } else if (action === 'panel-close') {
      _transitionPanel(state, '');
    } else if (action === 'variation-previous' || action === 'variation-next') {
      var rail = control.closest('.niwa-product-panel').querySelector('.niwa-product-variation-rail');
      if (rail) {
        var step = Math.max(1, rail.clientWidth);
        rail.scrollBy({ left: (action === 'variation-next' ? 1 : -1) * step, behavior: 'smooth' });
      }
    } else if (action === 'variation-page') {
      var pageRail = control.closest('.niwa-product-panel').querySelector('.niwa-product-variation-rail');
      if (pageRail) pageRail.scrollTo({ left: control._niwaPage * pageRail.clientWidth, behavior: 'smooth' });
    } else if (action === 'description' || action === 'related' || action === 'variations' || action === 'options') {
      _openPanel(state, action);
    } else if (action === 'variation' && control._niwaVariation) {
      _selectVariation(state, control._niwaVariation);
    } else if (action === 'attribute') {
      state.selection[control._niwaAttribute] = control._niwaValue;
      state.variation = _matchingVariation(product, state.selection);
      if (state.variation && state.variation.image) product.image = state.variation.image;
      if (state.variation && state.variation.price) product.price = state.variation.price;
      _syncSelectionUi(state);
    } else if (action === 'open-related' && control._niwaProduct) {
      _openProduct(state, control._niwaProduct, false);
    } else if (action === 'related-cart' && control._niwaProduct) {
      _addSimpleProductToCart(control._niwaProduct, control);
    } else if (action === 'cart') {
      _addToCart(state, control);
    }
  }

  function _bindLifecycle() {
    if (lifecycleBound) return;
    lifecycleBound = true;
    window.addEventListener('niwa:morph:close', function () {
      if (!activeState || !activeState.remembered) return;
      activeState.restoreOnOpen = true;
      _hideFocus(activeState, true, true);
    });
    window.addEventListener('niwa:morph:open', function () {
      var state = activeState;
      if (!state || !state.restoreOnOpen || !state.remembered) return;
      _clearTimer(state, 'restoreTimer');
      state.restoreTimer = setTimeout(function () {
        state.restoreTimer = null;
        if (state.restoreOnOpen && state.remembered) _openProduct(state, state.remembered, true);
      }, _transitionTime(_shell()) + 120);
    });
  }

  function render(products, skipSave, opts) {
    var body = _body();
    var normalized = _normalizeProducts(products);
    if (!body || !normalized.length) return null;

    var wrap = _create('div', 'niwa-message-products niwa-message-products--grid');
    var grid = _create('div', 'niwa-product-grid niwa-product-grid--' + normalized.length);
    var state = {
      wrap: wrap,
      grid: grid,
      anchor: null,
      focus: null,
      product: null,
      details: null,
      remembered: null,
      restoreOnOpen: false,
      panel: '',
      selection: {},
      variation: null,
      cache: {},
      loading: false,
      hideTimer: null,
      heightTimer: null,
      panelTimer: null,
      restoreTimer: null,
      focusFrame: null
    };

    normalized.forEach(function (product, index) {
      grid.appendChild(_makeTile(state, product, index + 1));
      if (!skipSave) _track('recommendation_impression', product, { position: index + 1 });
    });
    _applyImageRatio(grid, normalized);
    wrap.appendChild(grid);
    state.anchor = _insertBeforeResponse(body, wrap, opts && opts.anchorEl);
    states.push(state);
    _bindLifecycle();
    _scheduleDetailsPrefetch(state, normalized);

    if (!skipSave) {
      var renderer = _renderer();
      if (renderer && typeof renderer.addHistoryEntry === 'function') {
        renderer.addHistoryEntry({ type: 'products', data: normalized, timestamp: Date.now() }, true);
      } else if (renderer && typeof renderer.saveHistory === 'function') {
        renderer.saveHistory();
      }
    }

    if (state.anchor && _renderer() && typeof _renderer().scrollNodeToTop === 'function') {
      _renderer().scrollNodeToTop(wrap);
    }
    return wrap;
  }

  return { render: render };
})();
