I am developing the online shop, and making the popup modal for user to select the spec and amount of product.
The following html code is the modal for that:
 <!-- Product Spec + Quantity Modal -->
    <div id="product-spec-modal" class="modal-overlay" style="display:none;">
        <div class="modal-window" role="dialog" aria-modal="true" aria-labelledby="modalTitle" tabindex="-1">
            <header class="modal-header">
            <h2 id="modalTitle">選擇產品規格和數量</h2>
            <button type="button" aria-label="Close modal" class="modal-close">&times;</button>
            </header>
            <main class="modal-content">
            <form id="product-spec-form">
                <div id="variants-container">
                <!-- Variant options injected dynamically here -->
                </div>
                <label for="modal-qty" class="mt-16">數量:</label>
                <div class="quantity-input">
                <button type="button" class="qty-btn qty-minus" aria-label="減少數量">−</button>
                <input type="number" id="modal-qty" name="qty" value="1" min="1" step="1" />
                <button type="button" class="qty-btn qty-plus" aria-label="增加數量">+</button>
                </div>
                <div class="modal-error" aria-live="polite" style="color:red; margin-top: 8px;"></div>
                <button type="submit" class="btn btn-main mt-24" style="width:100%">確認加入購物車</button>
            </form>
            </main>
        </div>
    </div>

The following is the script for perform the action and logic of add to cart function:
$(document).ready(function () {
  const modal = $('#product-spec-modal');
  const variantsContainer = modal.find('#variants-container');
  const qtyInput = modal.find('#modal-qty');
  const qtyMinusBtn = modal.find('.qty-minus');
  const qtyPlusBtn = modal.find('.qty-plus');
  const modalCloseBtn = modal.find('.modal-close');
  const modalForm = modal.find('#product-spec-form');
  const modalError = modal.find('.modal-error');

  let currentProductData = null;
  let activeAddToCartBtn = null; // store clicked button to restore UI

  function buildVariantOptions(variants) {
    if (!Array.isArray(variants) || variants.length === 0) {
      variantsContainer.html('');
      return;
    }
    const attrNames = variants[0].attr_name ? variants[0].attr_name.split(',').map(s => s.trim()) : [];
    if (attrNames.length === 0) {
      variantsContainer.html('');
      return;
    }
    let attrValuesByName = {};
    attrNames.forEach(attr => attrValuesByName[attr] = new Set());
    variants.forEach(variant => {
      if (!variant.attr_value) return;
      const vals = variant.attr_value.split(',').map(s => s.trim());
      vals.forEach((val, i) => {
        if (attrNames[i]) attrValuesByName[attrNames[i]].add(val);
      });
    });
    let html = '';
    attrNames.forEach((attrName, groupIndex) => {
      html += `<div class="variant-group" data-variant-attribute="${attrName}">
        <label class="variant-label">${attrName}:</label>
        <div class="variant-options" role="radiogroup" aria-label="${attrName} options">`;
      Array.from(attrValuesByName[attrName]).forEach((val, optionIndex) => {
        const optionId = `variant-${attrName}-${optionIndex}`;
        html += `
          <label class="variant-option" for="${optionId}" tabindex="0" role="radio" aria-checked="false" data-variant-attr="${attrName}" data-variant-val="${val}">
            <input type="radio" name="variant-${attrName}" id="${optionId}" value="${val}">
            ${val}
          </label>`;
      });
      html += '</div></div>';
    });
    variantsContainer.html(html);

    function updateSelectedStyles() {
      variantsContainer.find('.variant-option').each(function () {
        var label = $(this);
        var input = label.find('input[type="radio"]');
        if (input.prop('checked')) {
          label.addClass('selected').attr('aria-checked', 'true');
        } else {
          label.removeClass('selected').attr('aria-checked', 'false');
        }
      });
    }
    updateSelectedStyles();

    variantsContainer.find('.variant-option').on('click', function () {
      var input = $(this).find('input[type="radio"]');
      input.prop('checked', true).trigger('change');
      updateSelectedStyles();
    }).on('keydown', function (e) {
      if (e.key === ' ' || e.key === 'Enter') {
        e.preventDefault();
        $(this).trigger('click');
      }
    });

    variantsContainer.find('input[type="radio"]').on('change', updateSelectedStyles);
  }

  function openModal(productData, clickedBtn) {
    currentProductData = productData;
    activeAddToCartBtn = clickedBtn;

    // If product has multiple variants, build variant selectors
    if (productData.hasMultipleVariants) {
        buildVariantOptions(productData.variants);
        variantsContainer.show();
    } else {
        // Single variant: no variant selection UI needed
        variantsContainer.hide().html('');
    }

    // Set quantity input attributes
    const min = Number(productData.min) || 1;
    const step = Number(productData.step) || 1;
    const max = Number(productData.max) || null;

    qtyInput.attr('min', min);
    qtyInput.attr('step', step);
    qtyInput.val(min);
    if (max) {
        qtyInput.attr('max', max);
    } else {
        qtyInput.removeAttr('max');
    }

    modalError.text('');
    modal.fadeIn(200);
    $('body').css('overflow', 'hidden');
    //qtyInput.focus();
    }

  function closeModal() {
    modal.fadeOut(200);
    $('body').css('overflow', '');
    currentProductData = null;
    activeAddToCartBtn = null;
    variantsContainer.html('');
    modalError.text('');
  }

  qtyMinusBtn.on('click', function () {
    let val = parseInt(qtyInput.val()) || 1;
    const min = parseInt(qtyInput.attr('min')) || 1;
    const step = parseInt(qtyInput.attr('step')) || 1;
    val = Math.max(min, val - step);
    qtyInput.val(val);
  });

  qtyPlusBtn.on('click', function () {
    let val = parseInt(qtyInput.val()) || 1;
    const step = parseInt(qtyInput.attr('step')) || 1;
    const max = parseInt(qtyInput.attr('max'));
    val = val + step;
    if (!isNaN(max)) val = Math.min(max, val);
    qtyInput.val(val);
  });

  qtyInput.on('input', function () {
    let val = +qtyInput.val();
    const min = +qtyInput.attr('min');
    const max = +qtyInput.attr('max');
    const step = +qtyInput.attr('step');
    if (isNaN(val) || val < min) val = min;
    if (!isNaN(max) && val > max) val = max;
    if (step > 1) val = Math.round(val / step) * step;
    qtyInput.val(val);
  });

  modalCloseBtn.on('click', closeModal);
  modal.on('click', function (e) {
    if (e.target === this) closeModal();
  });

  modalForm.on('submit', function (e) {
    e.preventDefault();
    modalError.text('');

    if (!currentProductData) {
      modalError.text('產品資料缺失');
      return;
    }

    // Check all variant groups have selection
    let selectedAttributes = {};
    let valid = true;
    variantsContainer.find('.variant-group').each(function () {
      const attrName = $(this).data('variant-attribute');
      const checkedInput = $(this).find('input[type=radio]:checked');
      if (checkedInput.length === 0) {
        modalError.text(`請選擇${attrName}`);
        valid = false;
        return false; // break each loop
      }
      selectedAttributes[attrName] = checkedInput.val();
    });
    if (!valid) return;

    // Validate qty
    let qty = parseInt(qtyInput.val());
    const minQty = Number(currentProductData.min) || 1;
    const maxQty = Number(currentProductData.max);
    if (isNaN(qty) || qty < minQty) {
      modalError.text(`數量不能小於 ${minQty}`);
      return;
    }
    if (!isNaN(maxQty) && maxQty > 0 && qty > maxQty) {
      modalError.text(`數量不能超過最大限制 ${maxQty}`);
      return;
    }

    // Find matching variant
    let matchedVariant = null;
    if (currentProductData.hasMultipleVariants) {
        matchedVariant = currentProductData.variants.find(variant => {
        if (!variant.attr_name || !variant.attr_value) return false;
        const attrNames = variant.attr_name.split(',').map(s => s.trim());
        const attrValues = variant.attr_value.split(',').map(s => s.trim());
        for (let i = 0; i < attrNames.length; i++) {
          if (selectedAttributes[attrNames[i]] !== attrValues[i]) return false;
        }
        return true;
      });
    } else {
        // Single variant — just use the first variant
        matchedVariant = currentProductData.variants && currentProductData.variants.length > 0
                     ? currentProductData.variants[0]
                     : null;
    }
   
    if (!matchedVariant) {
      modalError.text('請選擇有效的產品規格');
      return;
    }

    closeModal();

    // Do AJAX add to cart following your original logic:
    if (!matchedVariant || !matchedVariant.id) {
      Swal.fire({
        icon: 'error',
        title: '錯誤',
        text: '無法找到匹配的產品規格，請重試',
      });
      return;
    }

    $.ajax({
      type: 'POST',
      url: base_url + 'cart/manage',
      data: {
        product_variant_id: matchedVariant.id,
        qty: qty,
        is_saved_for_later: false,
        [csrfName]: csrfHash,
      },
      dataType: 'json',
      success: function (result) {
        csrfName = result.csrfName;
        csrfHash = result.csrfHash;
        if (!result.error) {
          Toast.fire({
            icon: 'success',
            title: result.message
          });
          $('.cart_count').each(function () {
            $(this).text(result.data.cart_count);
          });

          display_cart(result.data.items);
        } else {
          if (is_loggedin == 0) {
            Toast.fire({
              icon: 'success',
              title: "已加入至購物車"
            });
            var cart_item = {
              product_variant_id: matchedVariant.id,
              name: currentProductData.title || '',
              short_description: currentProductData.description || '',
              qty: qty,
              image: currentProductData.image || '',
              price: currentProductData.price
            };
            var cart = localStorage.getItem("cart");
            cart = cart ? JSON.parse(cart) : [];
            cart.push(cart_item);
            localStorage.setItem("cart", JSON.stringify(cart));
            display_cart(cart);
          } else {
            Toast.fire({
              icon: 'error',
              title: result.message
            });
          }
        }
      },
      error: function () {
        Toast.fire({
          icon: 'error',
          title: '伺服器錯誤，請稍後重試'
        });
      }
    });

  });

  // Bind popup open on all add_to_cart buttons, pass variants JSON as data attribute if possible
  $(document).on('click', '.add_to_cart', function (e) {
    e.preventDefault();
    const btn = $(this);

    // Fetch variants JSON from data-variants attribute (set server side as JSON string)
    let variantsData = [];
    try {
      variantsData = btn.attr('data-variants') ? JSON.parse(btn.attr('data-variants')) : [];
    } catch {
      variantsData = [];
    }

    const productData = {
        id: btn.data('product-id'),
        title: btn.data('product-title'),
        name: btn.data('product-title'),
        price: btn.data('product-price'),
        image: btn.data('product-image'),
        min: Number(btn.data('min')) || 1,
        step: Number(btn.data('step')) || 1,
        max: Number(btn.data('max')) || null,
        variants: variantsData,
        description: btn.data('product-description') || '',
        hasMultipleVariants: btn.data('has-multiple-variants') === 1 || btn.data('has-multiple-variants') === '1',
        };

    openModal(productData, btn);
  });
});

And the data will fetch from server by following function:
function fetch_product($user_id = NULL, $filter = NULL, $id = NULL, $category_id = NULL, $limit = NULL, $offset = NULL, $sort = NULL, $order = NULL, $return_count = NULL, $is_deliverable = NULL, $seller_id = NULL)
{

    $settings = get_settings('system_settings', true);
    $low_stock_limit = isset($settings['low_stock_limit']) ? $settings['low_stock_limit'] : 5;
    $t = &get_instance();

    if ($sort == 'pv.price' && !empty($sort) && $sort != NULL) {

        // $t->db->order_by("IF( pv.special_price > 0 , pv.special_price , pv.price )" . $order, False);
        $t->db->order_by("IF(pv.special_price > 0,
             IF(p.is_prices_inclusive_tax = 1,
                  pv.special_price,
                  pv.special_price + ((pv.special_price * p.tax )/100)
             ),
            IF(p.is_prices_inclusive_tax = 1,
                 pv.price,
                 pv.price  + ((pv.price * p.tax )/100)
            )
               ) " . $order, false);
    }


    if (isset($filter['show_only_active_products']) && $filter['show_only_active_products'] == 0) {
        $where = [];
    } else {
        $where = ['p.status' => '1', 'pv.status' => 1, 'sd.status' => 1];
    }

    $discount_filter_data = (isset($filter['discount']) && !empty($filter['discount'])) ? ' pv.*,( if(pv.special_price > 0,( (pv.price-pv.special_price)/pv.price)*100,0)) as cal_discount_percentage, ' : '';

    $t->db->select($discount_filter_data . ' (select count(id)  from products where products.category_id=c.id ) as total,count(p.id) as sales, p.stock_type ,
     p.is_prices_inclusive_tax, p.type ,GROUP_CONCAT(DISTINCT(pa.attribute_value_ids)) as attr_value_ids,sd.rating as seller_rating,sd.slug as seller_slug,sd.no_of_ratings as seller_no_of_ratings,sd.logo as seller_profile, sd.store_name as store_name,sd.store_description, p.seller_id, u.username as seller_name,
     p.id,p.stock,p.name,p.category_id,p.short_description,p.slug,p.description,p.extra_description,p.total_allowed_quantity,p.status,p.deliverable_type,p.is_attachment_required,p.deliverable_zipcodes,p.minimum_order_quantity,p.sku,
     p.quantity_step_size,p.cod_allowed,p.row_order,p.rating,p.no_of_ratings,p.image,p.is_returnable,p.is_cancelable,p.cancelable_till,p.indicator,p.other_images, 
     p.video_type, p.video, p.tags, p.warranty_period, p.guarantee_period, p.made_in,p.hsn_code,p.download_allowed,p.download_type,p.download_link,p.pickup_location,p.brand,p.availability, p.slug as product_slug, b.slug as brand_slug,c.name as category_name,tax.percentage as tax_percentage ,tax.id as tax_id ')
        ->join(" categories c", "p.category_id=c.id ", 'LEFT')
        ->join(" brands b", "p.brand=b.name", 'LEFT')
        ->join(" seller_data sd", "p.seller_id=sd.user_id ", 'LEFT')
        ->join(" users u", "p.seller_id=u.id", 'LEFT')
        ->join('`product_variants` pv', 'p.id = pv.product_id', 'LEFT')
        ->join('`taxes` tax', 'tax.id = p.tax', 'LEFT')
        ->join('`product_attributes` pa', ' pa.product_id = p.id ', 'LEFT');


    if (isset($filter['show_only_stock_product']) && $filter['show_only_stock_product'] == 1) {
        $t->db->where('(p.stock != "" or pv.stock != "")');
    }

    if (isset($filter) && !empty($filter['product_type']) && strtolower($filter['product_type']) == 'most_selling_products') {
        $t->db->join('`order_items` oi', 'oi.product_variant_id = pv.id', 'LEFT');
        $sort = 'count(p.id)';
        $order = 'DESC';
    }

    if (isset($filter) && !empty($filter['search'])) {
        $tags = explode(" ", $filter['search']);
        $t->db->group_Start();
        foreach ($tags as $i => $tag) {
            if ($i == 0) {
                $t->db->like('p.tags', trim($tag));
            } else {
                $t->db->or_like('p.tags', trim($tag));
            }
        }
        $t->db->or_like('p.name', trim($filter['search']));
        $t->db->group_end();
    }
    if (isset($filter) && !empty($filter['flag']) && $filter['flag'] != "null" && $filter['flag'] != "") {
        $flag = $filter['flag'];
        if ($flag == 'low') {
            $t->db->group_Start();
            $where1 = "p.stock_type is  NOT NULL";
            $t->db->where($where1);
            $t->db->where('p.stock <=', $low_stock_limit);
            $t->db->where('p.availability =', '1');
            $t->db->or_where('pv.stock <=', $low_stock_limit);
            $t->db->where('pv.availability =', '1');
            $t->db->group_End();
        } else {
            $t->db->group_Start();
            $t->db->or_where('p.availability ', '0');
            $t->db->or_where('pv.availability ', '0');
            $t->db->where('p.stock ', '0');
            $t->db->or_where('pv.stock ', '0');
            $t->db->group_End();
        }
    }
    if (isset($filter['min_price']) && $filter['min_price'] > 0) {
        $min_price = $filter['min_price'];
        $where_min = "if( pv.special_price > 0 , pv.special_price , pv.price ) >=$min_price";
        $t->db->group_Start();
        $t->db->where($where_min);
        $t->db->group_End();
    }
    if (isset($filter['max_price']) && $filter['max_price'] > 0 && isset($filter['min_price']) && $filter['min_price'] > 0) {
        $max_price = $filter['max_price'];
        $where_max = "if( pv.special_price > 0 , pv.special_price , pv.price ) <=$max_price";
        $t->db->group_Start();
        $t->db->where($where_max);
        $t->db->group_End();
    }

    if (isset($filter) && !empty($filter['tags'])) {
        $tags = explode(",", $filter['tags']);
        $t->db->group_Start();
        foreach ($tags as $i => $tag) {
            if ($i == 0) {
                $t->db->like('p.tags', trim($tag));
            } else {
                $t->db->or_like('p.tags', trim($tag));
            }
        }
        $t->db->group_end();
    }

    if (isset($filter) && !empty($filter['brand'])) {
        $t->db->where('p.brand', trim($filter['brand']));
    }

    if (isset($filter) && !empty($filter['slug'])) {
        $where['p.slug'] = $filter['slug'];
    }
    if (isset($seller_id) && !empty($seller_id) && $seller_id != "") {
        $where['p.seller_id'] = $seller_id;
    }


    if (isset($filter) && !empty($filter['attribute_value_ids'])) {
        /* https://stackoverflow.com/questions/5015403/mysql-find-in-set-with-multiple-search-string */
        $str = str_replace(',', '|', $filter['attribute_value_ids']); //str_replace(find,replace,string,count)
        //$str = $filter['attribute_value_ids'];
        $t->db->where('CONCAT(",", pa.attribute_value_ids , ",") REGEXP ",(' . $str . ')," !=', 0, false);
    }
    //print_r($filter);
    //print_r($category_id);
    
    if(isset($filter) && !empty($filter['categories']) && empty($category_id)){
        $str = str_replace(',', '|', $filter['categories']);
        $t->db->where('CONCAT(",", p.category_id , ",") REGEXP ",(' . $str . ')," !=', 0, false);
    }

    if (isset($category_id) && !empty($category_id) && empty($filter['categories'])) {
        if (is_array($category_id) && !empty($category_id)) {
            $t->db->group_Start();
            $t->db->where_in('p.category_id', $category_id);
            //$t->db->like('p.category_id', $category_id);
            $t->db->or_where_in('c.parent_id', $category_id);
            $t->db->group_End();
            $t->db->where($where);
        } else {
            $where['p.category_id'] = $category_id;
        }
    }

    if (isset($filter) && !empty($filter['product_type']) && strtolower($filter['product_type']) == 'products_on_sale') {
        $t->db->where('pv.special_price >', '0');
    }

    if (isset($filter) && !empty($filter['product_type']) && strtolower($filter['product_type']) == 'top_rated_products') {
        $sort = null;
        $order = null;
        $t->db->order_by("p.rating", "desc");
        $t->db->order_by("p.no_of_ratings", "desc");
        $where = ['p.no_of_ratings > ' => 0];
    }



    if (isset($filter) && !empty($filter['product_type']) && strtolower($filter['product_type']) == 'top_rated_product_including_all_products') {
        $sort = null;
        $order = null;
        $t->db->order_by("p.rating", "desc");
        $t->db->order_by("p.no_of_ratings", "desc");
    }

    if (isset($filter) && !empty($filter['product_type']) && $filter['product_type'] == 'new_added_products') {
        $sort = 'p.id';
        $order = 'desc';
    }

    if (isset($filter) && !empty($filter['product_variant_ids'])) {
        if (is_array($filter['product_variant_ids'])) {
            $t->db->where_in('pv.id', $filter['product_variant_ids']);
        }
    }

    if (isset($id) && !empty($id) && $id != null) {
        if (is_array($id) && !empty($id)) {
            $t->db->where_in('p.id', $id);
            $t->db->where($where);
        } else {
            if (isset($filter) && !empty($filter['is_similar_products']) && $filter['is_similar_products'] == '1') {
                $where[' p.id != '] = $id;
            } else {
                $where['p.id'] = $id;
            }
            $t->db->where($where);
        }
    } else {
        $t->db->where($where);
    }
    if (!isset($filter['flag']) && empty($filter['flag'])) {
        $t->db->group_Start();
        $t->db->or_where('c.status', '1');
        $t->db->or_where('c.status', '0');
        $t->db->group_End();
    }
    if (isset($filter['discount']) && !empty($filter['discount']) && $filter['discount'] != "") {
        $discount_pr = $filter['discount'];
        $t->db->group_by('p.id')->having("cal_discount_percentage  <= " . $discount_pr, null, false)->having("cal_discount_percentage  > 0 ", null, false);
    } else {
        $t->db->group_by('p.id');
    }


    if ($limit != null || $offset != null) {
        $t->db->limit($limit, $offset);
    }
    if (isset($filter['discount']) && !empty($filter['discount']) && $filter['discount'] != "") {
        $t->db->order_by('cal_discount_percentage', 'DESC');
    } else {
        if ($sort != null || $order != null && $sort != 'pv.price') {
            $t->db->order_by($sort, $order);
        }
        $t->db->order_by('p.row_order', 'ASC');
    }


    if (!empty($return_count)) {
        return $t->db->count_all_results('products p');
    } else {
        $product = $t->db->get('products p')->result_array();
    }

    $count = isset($filter) && !empty($filter['flag']) ? 'count(DISTINCT(p.id))' : 'count(DISTINCT(p.id))';
    $discount_filter = (isset($filter['discount']) && !empty($filter['discount'])) ? ' , GROUP_CONCAT( IF( ( IF( pv.special_price > 0, ((pv.price - pv.special_price) / pv.price) * 100, 0 ) ) > ' . $filter['discount'] . ', ( IF( pv.special_price > 0, ((pv.price - pv.special_price) / pv.price) * 100, 0 ) ), 0 ) ) AS cal_discount_percentage ' : '';
    $product_count = $t->db->select('count(DISTINCT(p.id)) as total , GROUP_CONCAT(pa.attribute_value_ids) as attr_value_ids' . $discount_filter)
        ->join(" categories c", "p.category_id=c.id ", 'LEFT')
        ->join(" seller_data sd", "p.seller_id=sd.user_id ")
        ->join('`product_variants` pv', 'p.id = pv.product_id', 'LEFT')
        ->join('`product_attributes` pa', ' pa.product_id = p.id ', 'LEFT');

    if (isset($filter) && !empty($filter['search'])) {
        $tags = explode(" ", $filter['search']);
        $t->db->group_Start();
        foreach ($tags as $i => $tag) {
            if ($i == 0) {
                $t->db->like('p.tags', trim($tag));
            } else {
                $t->db->or_like('p.tags', trim($tag));
            }
        }
        $product_count->or_like('p.name', $filter['search']);
        $t->db->group_End();
    }
    if (isset($filter) && !empty($filter['flag'])) {
        $flag = $filter['flag'];
        if ($flag == 'low') {
            $t->db->group_Start();
            $where1 = "p.stock_type is  NOT NULL";
            $t->db->where($where1);
            $t->db->where('p.stock <=', $low_stock_limit);
            $t->db->where('p.availability =', '1');
            $t->db->or_where('pv.stock <=', $low_stock_limit);
            $t->db->where('pv.availability =', '1');
            $t->db->group_End();
        } else {
            $t->db->group_Start();
            $t->db->or_where('p.availability ', '0');
            $t->db->or_where('pv.availability ', '0');
            $t->db->where('p.stock ', '0');
            $t->db->or_where('pv.stock ', '0');
            $t->db->group_End();
        }
    }

    if (isset($filter) && !empty($filter['tags'])) {
        $tags = explode(",", $filter['tags']);
        $t->db->group_Start();
        foreach ($tags as $i => $tag) {
            if ($i == 0) {
                $t->db->like('p.tags', trim($tag));
            } else {
                $t->db->or_like('p.tags', trim($tag));
            }
        }
        $t->db->group_End();
    }

    if (isset($filter) && !empty($filter['brand'])) {
        $t->db->where('p.brand', trim($filter['brand']));
    }

    if (isset($filter) && !empty($filter['attribute_value_ids'])) {
        $str = str_replace(',', '|', $filter['attribute_value_ids']); // Ids should be in string and comma separated 
        $product_count->where('CONCAT(",", pa.attribute_value_ids, ",") REGEXP ",(' . $str . ')," !=', 0, false);
    }
    if (isset($filter) && !empty($filter['product_type']) && strtolower($filter['product_type']) == 'most_selling_products') {
        $product_count->join('`order_items` oi', 'oi.product_variant_id = pv.id', 'LEFT');
    }
    if (isset($category_id) && !empty($category_id)) {
        if (is_array($category_id) && !empty($category_id)) {
            $product_count->where_in('p.category_id', $category_id);
            $product_count->or_where_in('c.parent_id', $category_id);
            $product_count->where($where);
        }
    }

    if (isset($filter) && !empty($filter['product_type']) && strtolower($filter['product_type']) == 'products_on_sale') {
        $product_count->where('pv.special_price >=', '0');
    }
    if (isset($id) && !empty($id) && $id != null) {
        if (is_array($id) && !empty($id)) {
            $product_count->where_in('p.id', $id);
        }
    }
    if (isset($seller_id) && !empty($seller_id) && $seller_id != "") {
        $where['p.seller_id'] = $seller_id;
    }
    if (isset($seller_id) && !empty($seller_id) && $seller_id != "") {
        if (isset($filter['show_only_stock_product']) && $filter['show_only_stock_product'] == 1) {
            $t->db->where('(p.stock != "" or pv.stock != "")');
        }
    }
    if (isset($filter['show_only_stock_product']) && $filter['show_only_stock_product'] == 1) {
        $t->db->where('(p.stock != "" or pv.stock != "")');
    }
    $product_count->where($where);
    if (!isset($filter['flag']) && empty($filter['flag'])) {
        $product_count->group_Start();
        $product_count->or_where('c.status', '1');
        $product_count->or_where('c.status', '0');
        $product_count->group_End();
    }

    $count_res = $product_count->get('products p')->result_array();

    // return;
    $attribute_values_ids = array();
    $temp = [];
    $min_price = get_price('min');
    $max_price = get_price('max');



    if (!empty($product)) {
        $t->load->model('rating_model');
        for ($i = 0; $i < count($product); $i++) {

            $rating = $t->rating_model->fetch_rating($product[$i]['id'], '', 8, 0, 'pr.id', 'desc', '', 1);
            $product[$i]['review_images'] = (!empty($rating)) ? [$rating] : array();

            $product[$i]['tax_percentage'] = (isset($product[$i]['tax_percentage']) && intval($product[$i]['tax_percentage']) > 0) ? $product[$i]['tax_percentage'] : '0';
            $product[$i]['tax_id'] = ((isset($product[$i]['tax_id']) && intval($product[$i]['tax_id']) > 0) && $product[$i]['tax_id'] != "") ? $product[$i]['tax_id'] : '0';
            $product[$i]['attributes'] = get_attribute_values_by_pid($product[$i]['id']);
            // print_r($product[$i]['attributes']);
            // die;
            $product[$i]['variants'] = get_variants_values_by_pid($product[$i]['id']);
            $variants =   get_variants_values_by_pid($product[$i]['id']);
            $total_stock = 0;
            foreach ($variants as $variant) {
                $stock = (isset($variant['stock']) && !empty($variant['stock'])) ? $variant['stock'] : 0;
                $total_stock  += $stock;
                $product[$i]['total_stock'] = isset($total_stock) && !empty($total_stock) ? $total_stock : '';
            }
            $product[$i]['min_max_price'] = get_min_max_price_of_product($product[$i]['id']);
            $product[$i]['stock_type'] = isset($product[$i]['stock_type']) && ($product[$i]['stock_type'] != '') ? $product[$i]['stock_type'] : '';
            $product[$i]['stock'] = isset($product[$i]['stock']) && !empty($product[$i]['stock']) ? $product[$i]['stock'] : '';
            $product[$i]['relative_path'] = isset($product[$i]['image']) && !empty($product[$i]['image']) ? $product[$i]['image'] : '';
            $product[$i]['other_images_relative_path'] = isset($product[$i]['other_images']) && !empty($product[$i]['other_images']) ? json_decode($product[$i]['other_images']) : [];
            $product[$i]['video_relative_path'] = (isset($product[$i]['video']) && (!empty($product[$i]['video']))) ? $product[$i]['video'] : "";
            $product[$i]['video_type'] = isset($product[$i]['video_type']) && !empty($product[$i]['video_type']) ? $product[$i]['video_type'] : '';
            $product[$i]['attr_value_ids'] = isset($product[$i]['attr_value_ids']) && !empty($product[$i]['attr_value_ids']) ? $product[$i]['attr_value_ids'] : '';
            $product[$i]['made_in'] = isset($product[$i]['made_in']) && !empty($product[$i]['made_in']) ? $product[$i]['made_in'] : '';
            $product[$i]['hsn_code'] = isset($product[$i]['hsn_code']) && !empty($product[$i]['hsn_code']) ? $product[$i]['hsn_code'] : '';
            $product[$i]['brand'] = isset($product[$i]['brand']) && !empty($product[$i]['brand']) ? $product[$i]['brand'] : '';


            // echo "<pre>";
            // print_r($product[$i]);
            // die;

            $product[$i]['warranty_period'] = isset($product[$i]['warranty_period']) && !empty($product[$i]['warranty_period']) ? $product[$i]['warranty_period'] : '';
            $product[$i]['guarantee_period'] = isset($product[$i]['guarantee_period']) && !empty($product[$i]['guarantee_period']) ? $product[$i]['guarantee_period'] : '';
            $product[$i]['total_allowed_quantity'] = isset($product[$i]['total_allowed_quantity']) && !empty($product[$i]['total_allowed_quantity']) ? $product[$i]['total_allowed_quantity'] : '';
            $product[$i]['download_allowed'] = isset($product[$i]['download_allowed']) && !empty($product[$i]['download_allowed']) ? $product[$i]['download_allowed'] : '';
            $product[$i]['download_type'] = isset($product[$i]['download_type']) && !empty($product[$i]['download_type']) ? $product[$i]['download_type'] : '';
            $product[$i]['download_link'] = isset($product[$i]['download_link']) && !empty($product[$i]['download_link']) ? $product[$i]['download_link'] : '';
            $product[$i]['status'] = isset($product[$i]['status']) && !empty($product[$i]['status']) ? $product[$i]['status'] : '';
            $total_product = $t->db->query("select count(id) as total  from products where products.seller_id=" . $product[$i]['seller_id'] . " AND products.status='1'")->result_array();

            /* outputing escaped data */
            $product[$i]['name'] = output_escaping($product[$i]['name']);
            $product[$i]['total_product'] = ($total_product[0]['total']);
            $product[$i]['store_name'] = output_escaping($product[$i]['store_name']);
            $product[$i]['seller_rating'] = (isset($product[$i]['seller_rating']) && !empty($product[$i]['seller_rating'])) ? output_escaping(number_format($product[$i]['seller_rating'], 1)) : 0;
            $product[$i]['store_description'] = (isset($product[$i]['store_description']) && !empty($product[$i]['store_description'])) ? output_escaping($product[$i]['store_description']) : "";
            $product[$i]['seller_profile'] = output_escaping(base_url() . $product[$i]['seller_profile']);
            $product[$i]['seller_name'] = output_escaping($product[$i]['seller_name']);
            $product[$i]['short_description'] = output_escaping($product[$i]['short_description']);
            $product[$i]['description'] = (isset($product[$i]['description']) && !empty($product[$i]['description'])) ? output_escaping($product[$i]['description']) : "";
            $product[$i]['extra_description'] = (isset($product[$i]['extra_description']) && !empty($product[$i]['extra_description']) && $product[$i]['extra_description'] != 'NULL') ? output_escaping($product[$i]['extra_description']) : "";
            $product[$i]['pickup_location'] = (isset($product[$i]['pickup_location']) && !empty($product[$i]['pickup_location']) && $product[$i]['pickup_location']) != 'NULL' ? $product[$i]['pickup_location'] : '';

            $product[$i]['seller_slug'] = isset($product[$i]['seller_slug']) && !empty($product[$i]['seller_slug']) ? output_escaping($product[$i]['seller_slug']) : "";
            $product[$i]['deliverable_type'] = $product[$i]['deliverable_type'];
            $product[$i]['deliverable_zipcodes_ids'] = output_escaping($product[$i]['deliverable_zipcodes']);
            if (isset($filter['discount']) && !empty($filter['discount']) && $filter['discount'] != "") {
                $product[$i]['cal_discount_percentage'] = output_escaping(number_format($product[$i]['cal_discount_percentage'], 2));
            }
            $product[$i]['cancelable_till'] = isset($product[$i]['cancelable_till']) && !empty($product[$i]['cancelable_till']) ? $product[$i]['cancelable_till'] : '';
            $product[$i]['is_attachment_required'] = isset($product[$i]['is_attachment_required']) && !empty($product[$i]['is_attachment_required']) ? $product[$i]['is_attachment_required'] : '0';
            $product[$i]['indicator'] = isset($product[$i]['indicator']) && !empty($product[$i]['indicator']) ? $product[$i]['indicator'] : '0';
            $product[$i]['deliverable_zipcodes_ids'] = isset($product[$i]['deliverable_zipcodes_ids']) && !empty($product[$i]['deliverable_zipcodes_ids']) ? $product[$i]['deliverable_zipcodes_ids'] : '';
            $product[$i]['rating'] = output_escaping(number_format($product[$i]['rating'], 2));
            $product[$i]['availability'] = isset($product[$i]['availability']) && ($product[$i]['availability'] != "") ? $product[$i]['availability'] : '';
            $product[$i]['sku'] = isset($product[$i]['sku']) && ($product[$i]['sku'] != "") ? $product[$i]['sku'] : '';

            /* getting zipcodes from ids */
            if ($product[$i]['deliverable_type'] != NONE && $product[$i]['deliverable_type'] != ALL) {
                $zipcodes = array();
                $zipcode_ids = explode(",", $product[$i]['deliverable_zipcodes_ids']);
                // $t->db->select('zipcode');
                // $t->db->where_in('id', $zipcode_ids);
                // $zipcodes = $t->db->get('zipcodes')->result_array();
                $zipcodes = array_column($zipcodes, "zipcode");
                $product[$i]['deliverable_zipcodes'] = implode(",", $zipcode_ids);
            } else {
                $product[$i]['deliverable_zipcodes'] = '';
            }
            $product[$i]['category_name'] = (isset($product[$i]['category_name']) && !empty($product[$i]['category_name'])) ? output_escaping($product[$i]['category_name']) : '';
            /* check product delivrable or not */
            if ($is_deliverable != NULL) {
                $zipcode = fetch_details('zipcodes', ['zipcode' => $is_deliverable], 'id');
                if (!empty($zipcode)) {
                    $product[$i]['is_deliverable'] = is_product_delivarable($type = 'zipcode', $zipcode[0]['id'], $product[$i]['id']);
                } else {
                    $product[$i]['is_deliverable'] = false;
                }
            } else {
                $product[$i]['is_deliverable'] = false;
            }

            if ($product[$i]['deliverable_type'] == 1) {
                $product[$i]['is_deliverable'] = true;
            }


            $product[$i]['tags'] = (!empty($product[$i]['tags'])) ? explode(",", $product[$i]['tags']) : [];

            $product[$i]['video'] = (isset($product[$i]['video_type']) && (!empty($product[$i]['video_type']) || $product[$i]['video_type'] != NULL)) ? (($product[$i]['video_type'] == 'youtube' || $product[$i]['video_type'] == 'vimeo') ? $product[$i]['video'] : base_url($product[$i]['video'])) : "";
            $product[$i]['minimum_order_quantity'] = isset($product[$i]['minimum_order_quantity']) && (!empty($product[$i]['minimum_order_quantity'])) ? $product[$i]['minimum_order_quantity'] : 1;
            $product[$i]['quantity_step_size'] = isset($product[$i]['quantity_step_size']) && (!empty($product[$i]['quantity_step_size'])) ? $product[$i]['quantity_step_size'] : 1;
            if (!empty($product[$i]['variants'])) {
                $count_stock = array();
                $is_purchased_count = array();
                for ($k = 0; $k < count($product[$i]['variants']); $k++) {

                    $variant_other_images = $variant_other_images_sm = $variant_other_images_md = json_decode((string)$product[$i]['variants'][$k]['images'], 1);

                    if (!empty($variant_other_images[0]) && isset($variant_other_images[0])) {

                        $product[$i]['variants'][$k]['variant_relative_path'] = isset($product[$i]['variants'][$k]['images']) && !empty($product[$i]['variants'][$k]['images']) ? json_decode($product[$i]['variants'][$k]['images']) : [];
                        $counter = 0;
                        foreach ($variant_other_images_md as $row) {
                            $variant_other_images_md[$counter] = get_image_url($variant_other_images_md[$counter], 'thumb', 'md');
                            $counter++;
                        }
                        $product[$i]['variants'][$k]['images_md'] = isset($variant_other_images_md) && !empty($variant_other_images_md) ? $variant_other_images_md : "";

                        $counter = 0;
                        foreach ($variant_other_images_sm as $row) {
                            $variant_other_images_sm[$counter] = get_image_url($variant_other_images_sm[$counter], 'thumb', 'sm');
                            $counter++;
                        }
                        $product[$i]['variants'][$k]['images_sm'] = $variant_other_images_sm;

                        $counter = 0;
                        foreach ($variant_other_images as $row) {
                            $variant_other_images[$counter] = get_image_url($variant_other_images[$counter]);
                            $counter++;
                        }
                        $product[$i]['variants'][$k]['images'] = isset($variant_other_images) && !empty($variant_other_images) ? $variant_other_images : "";
                    } else {
                        $product[$i]['variants'][$k]['images'] = array();
                        $product[$i]['variants'][$k]['images_md'] = array();
                        $product[$i]['variants'][$k]['images_sm'] = array();
                        $product[$i]['variants'][$k]['variant_relative_path'] = array();
                    }
                    $product[$i]['variants'][$k]['swatche_type'] = (!empty($product[$i]['variants'][$k]['swatche_type'])) ? $product[$i]['variants'][$k]['swatche_type'] : "0";
                    $product[$i]['variants'][$k]['swatche_value'] = (!empty($product[$i]['variants'][$k]['swatche_value'])) ? $product[$i]['variants'][$k]['swatche_value'] : "0";
                    if (($product[$i]['stock_type'] == 0  || $product[$i]['stock_type'] == null)) {
                        if ($product[$i]['availability'] != null) {
                            $product[$i]['variants'][$k]['availability'] = $product[$i]['availability'];
                        }
                    } else {
                        $product[$i]['variants'][$k]['availability'] = ($product[$i]['variants'][$k]['availability'] != null) ? $product[$i]['variants'][$k]['availability'] : 1;
                        array_push($count_stock, $product[$i]['variants'][$k]['availability']);
                    }
                    if (($product[$i]['stock_type'] == 0)) {
                        $product[$i]['variants'][$k]['stock'] = isset($product[$i]['variants'][$k]['stock']) && !empty($product[$i]['variants'][$k]['stock']) ? get_stock($product[$i]['id'], 'product') : '';
                    } else {
                        $product[$i]['variants'][$k]['stock'] = isset($product[$i]['variants'][$k]['stock']) && !empty($product[$i]['variants'][$k]['stock']) ? get_stock($product[$i]['variants'][$k]['id'], 'variant') : '';
                    }
                    $percentage = (isset($product[$i]['tax_percentage']) && intval($product[$i]['tax_percentage']) > 0 && $product[$i]['tax_percentage'] != null) ? $product[$i]['tax_percentage'] : '0';
                    if ((isset($product[$i]['is_prices_inclusive_tax']) && $product[$i]['is_prices_inclusive_tax'] == 0) || (!isset($product[$i]['is_prices_inclusive_tax'])) && $percentage > 0) {
                        $price_tax_amount = $product[$i]['variants'][$k]['price'] * ($percentage / 100);
                        $product[$i]['variants'][$k]['price'] =  strval($product[$i]['variants'][$k]['price'] + $price_tax_amount);
                        $special_price_tax_amount = $product[$i]['variants'][$k]['special_price'] * ($percentage / 100);
                        $product[$i]['variants'][$k]['special_price'] =  strval($product[$i]['variants'][$k]['special_price'] + $special_price_tax_amount);
                    } else {
                        $product[$i]['variants'][$k]['price'] =  strval($product[$i]['variants'][$k]['price']);
                        $product[$i]['variants'][$k]['special_price'] =  strval($product[$i]['variants'][$k]['special_price']);
                    }
                    if (isset($user_id) && $user_id != NULL) {
                        $user_cart_data = $t->db->select('qty as cart_count')->where(['product_variant_id' => $product[$i]['variants'][$k]['id'], 'user_id' => $user_id, 'is_saved_for_later' => 0])->get('cart')->result_array();
                        if (!empty($user_cart_data)) {
                            $product[$i]['variants'][$k]['cart_count'] = $user_cart_data[0]['cart_count'];
                        } else {
                            $product[$i]['variants'][$k]['cart_count'] = "0";
                        }
                        $is_purchased = $t->db->where(['oi.product_variant_id' => $product[$i]['variants'][$k]['id'], 'oi.user_id' => $user_id])->order_by('oi.id', 'DESC')->limit(1)->get('order_items oi')->result_array();

                        if (!empty($is_purchased) && strtolower($is_purchased[0]['active_status']) == 'delivered') {
                            array_push($is_purchased_count, 1);
                            $product[$i]['variants'][$k]['is_purchased'] = 1;
                        } else {
                            array_push($is_purchased_count, 0);
                            $product[$i]['variants'][$k]['is_purchased'] = 0;
                        }

                        $user_rating = $t->db->select('rating,comment')->where(['user_id' => $user_id, 'product_id' => $product[$i]['id']])->get('product_rating')->result_array();
                        if (!empty($user_rating)) {
                            $product[$i]['user']['user_rating'] =   (isset($product[$i]['user']['user_rating']) && (!empty($product[$i]['user']['user_rating']))) ? $user_rating[0]['rating'] : '';
                            $product[$i]['user']['user_comment'] =   (isset($product[$i]['user']['user_comment']) && (!empty($product[$i]['user']['user_comment']))) ? $user_rating[0]['user_comment'] : '';
                        }
                    } else {
                        $product[$i]['variants'][$k]['cart_count'] = "0";
                    }
                }
            }

            $is_purchased_count = array_count_values($is_purchased_count);
            $is_purchased_count = array_keys($is_purchased_count);
            $product[$i]['is_purchased'] = (isset($is_purchased) && array_sum($is_purchased_count) == 1) ? true : false;

            if (($product[$i]['stock_type'] != null && !empty($product[$i]['stock_type']))) {


                //Case 2 & 3 : Product level(variable product) ||  Variant level(variable product)
                if ($product[$i]['stock_type'] == 1 || $product[$i]['stock_type'] == 2) {
                    $counts = array_count_values($count_stock);
                    $counts = array_keys($counts);
                    if (isset($counts)) {
                        $product[$i]['availability'] = array_sum($counts);
                    }
                }
            }

            if (isset($user_id) && $user_id != null) {
                $fav = $t->db->where(['product_id' => $product[$i]['id'], 'user_id' => $user_id])->get('favorites')->num_rows();
                $product[$i]['is_favorite'] = $fav;
            } else {
                $product[$i]['is_favorite'] = '0';
            }

            $product[$i]['image_md'] = get_image_url($product[$i]['image'], 'thumb', 'md');
            $product[$i]['image_sm'] = get_image_url($product[$i]['image'], 'thumb', 'sm');
            $product[$i]['image'] = get_image_url($product[$i]['image']);
            $other_images = $other_images_sm =  $other_images_md = json_decode($product[$i]['other_images'], 1);

            if (!empty($other_images)) {

                $k = 0;
                foreach ($other_images_md as $row) {
                    $other_images_md[$k] = get_image_url($row, 'thumb', 'md');
                    $k++;
                }
                $other_images_md = (array) $other_images_md;
                $other_images_md = array_values($other_images_md);
                $product[$i]['other_images_md'] = $other_images_md;

                $k = 0;
                foreach ($other_images_sm as $row) {
                    $other_images_sm[$k] = get_image_url($row, 'thumb', 'sm');
                    $k++;
                }
                $other_images_sm = (array) $other_images_sm;
                $other_images_sm = array_values($other_images_sm);
                $product[$i]['other_images_sm'] = $other_images_sm;

                $k = 0;
                foreach ($other_images as $row) {
                    $other_images[$k] = get_image_url($row);
                    $k++;
                }
                $other_images = (array) $other_images;
                $other_images = array_values($other_images);
                $product[$i]['other_images'] = $other_images;
            } else {
                $product[$i]['other_images'] = array();
                $product[$i]['other_images_sm'] = array();
                $product[$i]['other_images_md'] = array();
            }
            $tags_to_strip = array("table", "<th>", "<td>");
            $replace_with = array("", "h3", "p");
            $n = 0;
            foreach ($tags_to_strip as $tag) {
                // $product[$i]['description'] = output_escaping(str_replace('\r\n', '&#13;&#10;', (string)$product[$i]['description']));
                $product[$i]['description'] = !empty($product[$i]['description']) ? output_escaping(str_replace('\r\n', '&#13;&#10;', (string)$product[$i]['description'])) : "";
                $product[$i]['extra_description'] = !empty($product[$i]['extra_description']) && $product[$i]['extra_description'] != null ? output_escaping(str_replace('\r\n', '&#13;&#10;', (string)$product[$i]['extra_description'])) : "";
                $n++;
            }
            $variant_attributes = [];
            $attributes_array = explode(',', $product[$i]['variants'][0]['attr_name']);

            foreach ($attributes_array as $attribute) {
                $attribute = trim($attribute);
                $key = array_search($attribute, array_column($product[$i]['attributes'], 'name'), false);
                if (($key === 0 || !empty($key)) && isset($product[0]['attributes'][$key])) {
                    $variant_attributes[$key]['ids'] = $product[0]['attributes'][$key]['ids'];
                    $variant_attributes[$key]['values'] = $product[0]['attributes'][$key]['value'];
                    $variant_attributes[$key]['swatche_type'] = $product[0]['attributes'][$key]['swatche_type'];
                    $variant_attributes[$key]['swatche_value'] = $product[0]['attributes'][$key]['swatche_value'];
                    $variant_attributes[$key]['attr_name'] = $attribute;
                }
            }
            $product[$i]['variant_attributes'] = $variant_attributes;
        }

        if (isset($count_res[0]['cal_discount_percentage'])) {
            $dicounted_total = array_values(array_filter(explode(',', $count_res[0]['cal_discount_percentage'])));
        } else {
            $dicounted_total = 0;
        }
        $response['total'] = (isset($filter) && !empty($filter['discount'])) ? count($dicounted_total) : $count_res[0]['total'];

        array_push($attribute_values_ids, $count_res[0]['attr_value_ids']);
        $attribute_values_ids = implode(",", $attribute_values_ids);
        $attr_value_ids = array_filter(array_unique(explode(',', $attribute_values_ids)));
    }
    $response['min_price'] = $min_price;
    $response['max_price'] = $max_price;
    $response['product'] = $product;
    if (isset($filter) && $filter != null) {
        if (!empty($attr_value_ids)) {
            $response['filters'] = get_attribute_values_by_id($attr_value_ids);
        }
    } else {
        $response['filters'] = [];
    }
    // print_r($response['filters']);
    // print_r($response);
    // die;
    return $response;
}

After execute the process, the spec options are not display on modal,
and I found the data structure of the spec array item:
Object { id: "14", product_id: "14", attribute_value_ids: "1", … }
​​
attr_name: " 面額"
​​
attribute_set: ""
​​
attribute_value_ids: "1"
​​
availability: "1"
​​
breadth: "0"
​​
cart_count: "0"
​​
date_added: "2025-06-24 18:16:09"
​​
height: "0"
​​
id: "14"
​​
images: Array []
​​
images_md: Array []
​​
images_sm: Array []
​​
is_purchased: 0
​​
length: "0"
​​
price: "100"
​​
product_id: "14"
​​
sku: "T001"
​​
special_price: "0"
​​
status: "1"
​​
stock: "5"
​​
swatche_type: "0"
​​
swatche_value: "0"
​​
variant_ids: "1"
​​
variant_relative_path: Array []
​​
variant_values: "100"
​​
weight: "0"

Based on the information I provided, please fix the missing option problem