<?php
declare(strict_types=1);

if (!defined('ABSPATH')) { exit; }

/**
 * Shared product card renderer.
 *
 * Phase 1, step 29. Before this file existed, `impulse_render_product_card`
 * was defined inline in BOTH `templates/impulse-page.php` (4-arg modern
 * signature taking `$product`) and `admin/views/impulse-preview.php` (20-arg
 * positional signature taking pre-extracted name/thumb/price/etc.). They
 * never collided because only one of the two parent files loads per request,
 * but the divergence blocked extracting products + product_carousel into
 * shared renderers — neither extracted renderer could pick a single helper
 * to call.
 *
 * The chosen canonical is the frontend's modern signature:
 *
 *   impulse_render_product_card($product, $design, $flipCards=false, $flipEffect='flip-h')
 *
 * with one feature merged in from the editor's positional version: the
 * `featured` badge is now actually emitted when `$product->is_featured` is
 * truthy (frontend previously had the design-keys for it but wasn't drawing
 * the badge). The featured badge sits at top-left and pushes the sale badge
 * down to top-40px when both are present.
 *
 * The editor's effect-mode-specific layout (`$imgHeightPx > 0` switching to
 * absolute-positioned slides) was tied to product_carousel's fade/card_flip
 * effects in the editor preview only — those effects don't ship on the live
 * frontend at all, so this drops on extraction. Editor preview previously
 * showed a slightly different mini-card in those modes; it'll now match the
 * frontend's grid card.
 *
 * The function `echo`s the card directly. Earlier versions had a mix of
 * `echo` and `return` (editor returned a string; frontend echoed). For
 * flip-cards the flow has always been to ob_start + ob_get_clean to build
 * the front face into a string, then echo the wrapped flip-card; that path
 * stays.
 *
 * Loaded by `_loader.php` before the per-element renderer files so the
 * function exists when products / product_carousel renderers (and any
 * straggling inline definitions in the two parent files) try to declare or
 * call it. The inline definitions are guarded by `function_exists` and
 * skip themselves when the shared helper already exists.
 */
if (!function_exists('impulse_render_product_card')) {
    function impulse_render_product_card($product, $design, $flipCards = false, $flipEffect = 'flip-h') {
        $elements        = $design['elements'] ?? ['image', 'title', 'price', 'add_to_cart'];
        $cardBg          = $design['card']['background']    ?? '#ffffff';
        $cardRadius      = $design['card']['border_radius'] ?? 0;
        $cardBorderWidth = $design['card']['border_width']  ?? 1;
        $cardBorderColor = $design['card']['border_color']  ?? '#e0e0e0';

        // For flip cards the outer wrapper, inner, and front-face open are
        // emitted here (before the card body) so the body's echoes land
        // directly inside the front face — no output buffering, so every
        // dynamic value is escaped inline at its own echo.
        if ($flipCards) {
            $shadows     = ['none' => 'none', 'small' => '0 1px 3px rgba(0,0,0,0.1)', 'medium' => '0 4px 6px rgba(0,0,0,0.1)', 'large' => '0 10px 25px rgba(0,0,0,0.15)'];
            $outerShadow = $shadows[$design['card']['shadow'] ?? 'small'] ?? 'none';
            $safeEff     = str_replace('_', '-', preg_replace('/[^a-z_-]/', '', $flipEffect ?: 'flip-h'));
            echo '<div class="impulse-pcard impulse-flip-card pfc-' . esc_attr($safeEff) . '" data-flip="true" data-flip-effect="' . esc_attr($safeEff) . '" data-product-id="' . intval($product->id ?? 0) . '" style="cursor:pointer;box-shadow:' . esc_attr($outerShadow) . ';border-radius:' . (int) $cardRadius . 'px">';
            echo '<div class="impulse-flip-inner">';
            echo '<div class="impulse-flip-front">';
        }

        // ── Resolve primary thumbnail ────────────────────────────────────────
        $thumb = '';
        if (!empty($product->images)) {
            $images = json_decode((string) $product->images, true);
            if (is_array($images) && !empty($images[0]['url'])) $thumb = (string) $images[0]['url'];
        }
        // Fallback: featured_image is an attachment id under WordPress.
        if (!$thumb && !empty($product->featured_image) && function_exists('wp_get_attachment_image_url')) {
            $url = wp_get_attachment_image_url(intval($product->featured_image), 'large');
            if ($url) $thumb = (string) $url;
        }
        // Standalone fallback: the wp_get_attachment_image_url shim returns
        // false. Resolve directly from inst_instinctor_images.rel_path —
        // matches how AdminController formats product listings.
        if (!$thumb && !empty($product->featured_image)
            && class_exists('\\Instinctor\\Core\\DB')
            && class_exists('\\Instinctor\\Core\\Storage')) {
            $imgTable = \Instinctor\Core\DB::prefix() . 'instinctor_images';
            $imgRow   = \Instinctor\Core\DB::get_row(\Instinctor\Core\DB::prepare(
                "SELECT rel_path FROM {$imgTable} WHERE id = %d AND parent_id IS NULL LIMIT 1",
                intval($product->featured_image)
            ));
            if ($imgRow && !empty($imgRow->rel_path)) {
                $thumb = \Instinctor\Core\Storage::baseurl() . '/' . ltrim((string) $imgRow->rel_path, '/');
            }
        }

        // ── Sale flag + product link ─────────────────────────────────────────
        $regPrice  = floatval($product->price ?? 0);
        $salePrice = floatval($product->sale_price ?? 0);
        $hasSale   = $salePrice > 0 && $regPrice > 0 && $salePrice < $regPrice;
        $slug      = (string) ($product->slug ?? '');
        $link      = $slug !== '' && function_exists('home_url')
            ? home_url('/product/' . $slug)
            : '/product/' . $slug;
        $isFeatured = !empty($product->is_featured);

        // ── Card shell ────────────────────────────────────────────────────────
        // Shadow on inner pcard for non-flip cards; flip cards put the shadow on
        // the outer wrapper so the 3D rotation doesn't glitch the shadow.
        $cardStyle = 'background:' . esc_attr($cardBg) . ';';
        $cardStyle .= 'border-radius:' . intval($cardRadius) . 'px;';
        if (intval($cardBorderWidth) > 0) {
            $cardStyle .= 'border:' . intval($cardBorderWidth) . 'px solid ' . esc_attr($cardBorderColor) . ';';
        } else {
            $cardStyle .= 'border:none;';
        }
        $cardShadowVal = $design['card']['shadow'] ?? '';
        $cardShadowMap = [
            'small'  => '0 1px 4px rgba(0,0,0,0.10)',
            'medium' => '0 4px 12px rgba(0,0,0,0.14)',
            'large'  => '0 10px 28px rgba(0,0,0,0.18)',
        ];
        if (!$flipCards && $cardShadowVal && isset($cardShadowMap[$cardShadowVal])) {
            $cardStyle .= 'box-shadow:' . $cardShadowMap[$cardShadowVal] . ';';
        }
        $cardStyle .= 'overflow:hidden;position:relative;transition:all 0.3s ease-in-out;';

        echo '<div class="instinctor-card-designed impulse-pcard" data-product-id="' . intval($product->id ?? 0) . '" style="' . esc_attr($cardStyle) . '">';

        // ── Badge design resolution (handles legacy saved data) ──────────────
        $saleBadgeDef = [
            'enabled'           => true,
            'bg_color'          => '#1d2327',
            'text_color'        => '#ffffff',
            'text'              => 'Sale',
            'featured_bg_color' => '#1d2327',
            'featured_text'     => 'Featured',
        ];
        $saleBadge    = array_merge($saleBadgeDef, is_array($design['sale_badge'] ?? null) ? $design['sale_badge'] : []);
        $badgeEnabled = !isset($design['sale_badge']['enabled']) || !empty($design['sale_badge']['enabled']);

        // ── Image ────────────────────────────────────────────────────────────
        if (in_array('image', $elements, true)) {
            $aspectRatio = $design['image']['aspect_ratio'] ?? 'square';
            $aspectMap   = ['square' => '100%', 'portrait' => '133%', 'landscape' => '75%', 'wide' => '56.25%'];
            $paddingBottom = $aspectMap[$aspectRatio] ?? '100%';

            // Image fit + inset. The box is a fixed ratio (the padding-bottom
            // trick), so `cover` scales the photo until it fills BOTH axes and
            // slices whatever overflows. On a tightly cropped packshot that eats
            // the product itself: a 906x867 pizza in a square box loses ~2% off
            // each side, flattening the crust. `contain` fits the whole product
            // and letterboxes the remainder instead.
            //   Cover stays the default so no existing store's grid moves.
            // Padding insets the image inside the box so a packshot can breathe
            // rather than run edge to edge. Both were previously unreachable:
            // card_design.image existed in the panel defaults but emitted no
            // controls and was never collected. Sergei 2026-07-16.
            $imgFit = (($design['image']['fit'] ?? 'cover') === 'contain') ? 'contain' : 'cover';
            $imgPad = max(0, min(40, intval($design['image']['padding'] ?? 0)));
            $imgBox = $imgPad > 0
                ? 'top:' . $imgPad . 'px;left:' . $imgPad . 'px;width:calc(100% - ' . ($imgPad * 2) . 'px);height:calc(100% - ' . ($imgPad * 2) . 'px)'
                : 'top:0;left:0;width:100%;height:100%';

            echo '<div class="instinctor-product-image" style="position:relative">';
            if ($thumb) {
                echo '<a href="' . esc_url($link) . '" style="display:block;padding-bottom:' . esc_attr($paddingBottom) . ';position:relative;background:#f8f8f8">';
                echo '<img src="' . esc_url($thumb) . '" alt="' . esc_attr((string) ($product->name ?? '')) . '" style="position:absolute;' . esc_attr($imgBox) . ';object-fit:' . esc_attr($imgFit) . '">';
                echo '</a>';
            } else {
                // No featured image: fall back to the platform's canonical
                // placeholder logo (same asset used by image / carousel /
                // admin previews). Keeps every empty card on-brand instead
                // of the generic "image icon" tiny grey SVG.
                $_phLogo = (defined('INSTINCTOR_PLUGIN_URL') ? INSTINCTOR_PLUGIN_URL : '/instinctor-plugin/') . 'assets/images/placeholder-logo.svg';
                echo '<a href="' . esc_url($link) . '" style="display:block;padding-bottom:' . esc_attr($paddingBottom) . ';position:relative;background:#f0f0f0">';
                echo '<div style="position:absolute;top:0;left:0;width:100%;height:100%;display:flex;align-items:center;justify-content:center">';
                echo '<img src="' . esc_url($_phLogo) . '" alt="" style="width:48%;max-width:160px;height:auto;opacity:0.55;pointer-events:none">';
                echo '</div>';
                echo '</a>';
            }

            // Featured badge — top-left. Renders when product is_featured,
            // regardless of whether 'sale_badge' is in $elements (featured
            // is a separate concept from sale, only sharing the design row).
            if ($isFeatured) {
                echo '<span class="impulse-pcard-badge impulse-pcard-badge-featured" style="position:absolute;top:8px;left:8px;background:' . esc_attr((string) $saleBadge['featured_bg_color']) . ';color:' . esc_attr((string) $saleBadge['text_color']) . ';padding:4px 10px;border-radius:4px;font-size:12px;font-weight:600;pointer-events:none">' . esc_html((string) $saleBadge['featured_text']) . '</span>';
            }
            // Sale badge — shifts down to top-40px when featured badge is
            // also showing, so they stack instead of overlapping.
            if ($hasSale && $badgeEnabled) {
                $saleTop = $isFeatured ? '40px' : '8px';
                echo '<span class="impulse-pcard-badge" style="position:absolute;top:' . esc_attr($saleTop) . ';left:8px;background:' . esc_attr((string) $saleBadge['bg_color']) . ';color:' . esc_attr((string) $saleBadge['text_color']) . ';padding:4px 10px;border-radius:4px;font-size:12px;font-weight:600;pointer-events:none">' . esc_html((string) $saleBadge['text']) . '</span>';
            }
            echo '</div>';
        }

        // ── Content padding wrapper ──────────────────────────────────────────
        $contentPadding = $design['card']['padding'] ?? 12;
        echo '<div style="padding:' . intval($contentPadding) . 'px">';

        // ── Title ────────────────────────────────────────────────────────────
        if (in_array('title', $elements, true)) {
            $titleSize   = $design['title']['font_size']    ?? 16;
            $titleWeight = $design['title']['font_weight']  ?? '600';
            $titleColor  = $design['title']['color']        ?? '#1d2327';
            echo '<div style="overflow:hidden;flex-shrink:0;">';
            echo '<h3 class="instinctor-product-title" style="margin:0 0 8px;font-size:' . intval($titleSize) . 'px;font-weight:' . esc_attr($titleWeight) . ';color:' . esc_attr($titleColor) . ';line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical">';
            echo '<a href="' . esc_url($link) . '" class="imp-link-reset">' . esc_html((string) ($product->name ?? '')) . '</a>';
            echo '</h3>';
            echo '</div>';
        }

        // ── Description ──────────────────────────────────────────────────────
        // Renders short_description (or description as fallback) when
        // 'description' is in the elements array. Admin has had design
        // settings (font_size, color, lines) for description forever, but
        // the renderer never read them — only the flip-card BACK face
        // showed description. This branch surfaces it on the front face
        // so cards in Grid / Carousel / Single layouts can carry a
        // tagline beneath the title without flipping. Honours the
        // -webkit-line-clamp `lines` setting just like the title does.
        if (in_array('description', $elements, true)) {
            $descText = (string) ($product->short_description ?? $product->description ?? '');
            if (function_exists('wp_strip_all_tags')) {
                $descText = wp_strip_all_tags($descText);
            } else {
                $descText = wp_strip_all_tags($descText);
            }
            if ($descText !== '') {
                $descSize  = $design['description']['font_size'] ?? 13;
                $descColor = $design['description']['color']     ?? '#666666';
                $descLines = max(1, intval($design['description']['lines'] ?? 2));
                echo '<div class="instinctor-product-desc" style="margin:0 0 8px;font-size:' . intval($descSize) . 'px;color:' . esc_attr($descColor) . ';line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:' . (int) $descLines . ';-webkit-box-orient:vertical">'
                   . esc_html($descText)
                   . '</div>';
            }
        }

        // ── Rating ────────────────────────────────────────────────────────────
        if (in_array('rating', $elements, true)) {
            $starColor    = $design['rating']['star_color'] ?? '#fbbf24';
            $ratingMargin = $design['rating']['margin_top'] ?? 8;
            echo '<div class="instinctor-product-rating" style="margin-top:' . intval($ratingMargin) . 'px;color:' . esc_attr($starColor) . ';font-size:14px">★★★★☆</div>';
        }

        // ── Price ────────────────────────────────────────────────────────────
        if (in_array('price', $elements, true)) {
            $priceSize   = $design['price']['font_size']    ?? 18;
            $priceWeight = $design['price']['font_weight']  ?? '700';
            $priceColor  = $design['price']['color']        ?? '#1d2327';
            $saleColor   = $design['price']['sale_color']   ?? '#dc2626';
            $priceMargin = $design['price']['margin_top']   ?? 8;

            // Subscription interval label (only for product_type='subscription')
            $intervalLabel = '';
            $meta = null;
            if (($product->product_type ?? '') === 'subscription') {
                $meta     = json_decode((string) ($product->meta ?? '{}'), true);
                $interval = is_array($meta) ? ($meta['subscription_interval'] ?? 'monthly') : 'monthly';
                $count    = is_array($meta) ? (int) ($meta['subscription_interval_count'] ?? 1) : 1;
                $singularMap = ['weekly' => 'week', 'monthly' => 'month', 'yearly' => 'year'];
                $pluralMap   = ['weekly' => 'weeks', 'monthly' => 'months', 'yearly' => 'years'];
                if ($count > 1) {
                    $unit = $pluralMap[$interval] ?? $interval;
                    $intervalLabel = ' / ' . $count . ' ' . $unit;
                } else {
                    $unit = $singularMap[$interval] ?? $interval;
                    $intervalLabel = ' / ' . $unit;
                }
            }

            echo '<div class="instinctor-product-price" style="margin-top:' . intval($priceMargin) . 'px;padding-bottom:4px;font-size:' . intval($priceSize) . 'px">';
            if ($hasSale) {
                echo '<span style="color:#999;text-decoration:line-through;margin-right:8px;font-size:' . (int) ($priceSize - 2) . 'px">$' . number_format($regPrice, 2) . '</span>';
                echo '<span style="font-weight:' . esc_attr($priceWeight) . ';color:' . esc_attr($saleColor) . '">$' . number_format($salePrice, 2) . esc_html($intervalLabel) . '</span>';
            } else {
                $effective = $regPrice > 0 ? $regPrice : $salePrice;
                echo '<span style="font-weight:' . esc_attr($priceWeight) . ';color:' . esc_attr($priceColor) . '">$' . number_format($effective, 2) . esc_html($intervalLabel) . '</span>';
            }
            // Trial badge — subscription product with trial_days set.
            if (($product->product_type ?? '') === 'subscription') {
                if ($meta === null) $meta = json_decode((string) ($product->meta ?? '{}'), true);
                $trialDays = is_array($meta) ? (int) ($meta['trial_days'] ?? 0) : 0;
                if ($trialDays > 0) {
                    echo '<div style="font-size:11px;color:#888;margin-top:2px">' . intval($trialDays) . '-day free trial</div>';
                }
            }
            echo '</div>';
        }

        // ── Add to Cart ──────────────────────────────────────────────────────
        // For flip cards, the front face holds a spacer that matches the
        // back-face button height so the price sits at the same vertical
        // position on both faces. The actual ATC button lives on the back.
        if ($flipCards) {
            echo '<div style="height:26px"></div>';
        }
        if (in_array('add_to_cart', $elements, true) && !$flipCards) {
            $btnBg      = $design['add_to_cart']['bg_color']       ?? '#1d2327';
            $btnColor   = $design['add_to_cart']['text_color']     ?? '#ffffff';
            $btnRadius  = $design['add_to_cart']['border_radius']  ?? 0;
            $btnText    = $design['add_to_cart']['button_text']    ?? 'Add to Cart';
            $btnFull    = $design['add_to_cart']['full_width']     ?? true;
            $btnMargin  = $design['add_to_cart']['margin_top']     ?? 12;
            $btnWidth   = $btnFull ? 'width:100%;' : '';

            // External-URL override. When a product's meta contains
            // `external_url`, the cart button becomes a direct link to that
            // URL instead of an add-to-cart action. Useful for products
            // sold elsewhere — license keys issued by a different service,
            // SaaS subscriptions handled on a partner domain, etc.
            // Regular products without this meta key keep the normal cart
            // flow, so users' own stores are unaffected.
            if ($meta === null) $meta = json_decode((string) ($product->meta ?? '{}'), true);
            $externalUrl = is_array($meta) ? trim((string) ($meta['external_url'] ?? '')) : '';

            $btnStyle = $btnWidth . 'margin-top:' . intval($btnMargin) . 'px;padding:10px 16px;background:' . esc_attr($btnBg) . ';color:' . esc_attr($btnColor) . ';border:none;border-radius:' . intval($btnRadius) . 'px;cursor:pointer;font-weight:600;font-size:14px';

            if ($externalUrl !== '' && filter_var($externalUrl, FILTER_VALIDATE_URL)) {
                $btnStyle .= ';display:inline-flex;align-items:center;justify-content:center;text-decoration:none;box-sizing:border-box';
                echo '<a class="instinctor-external-buy-btn" href="' . esc_url($externalUrl) . '" data-product-id="' . intval($product->id ?? 0) . '" style="' . esc_attr($btnStyle) . '">' . esc_html((string) $btnText) . '</a>';
            } else {
                echo '<button class="instinctor-add-to-cart-btn" data-product-id="' . intval($product->id ?? 0) . '" style="' . esc_attr($btnStyle) . '">' . esc_html((string) $btnText) . '</button>';
            }
        }

        echo '</div>'; // content padding
        echo '</div>'; // card

        // ── Flip card wrap ───────────────────────────────────────────────────
        if ($flipCards) {
            // Close the front face opened before the card body above.
            echo '</div>';

            $btn = $design['add_to_cart'] ?? [];
            $prc = $design['price']       ?? [];
            $tColor  = esc_attr($design['title']['color'] ?? '#1d2327');
            $btnBgF  = esc_attr($btn['bg_color']    ?? '#1d2327');
            $btnTxtF = esc_attr($btn['text_color']  ?? '#ffffff');
            $btnRadF = intval($btn['border_radius'] ?? 6);
            $btnLbl  = esc_html((string) ($btn['button_text'] ?? 'Add to Cart'));
            $cardBgF = esc_attr($cardBg);
            $cardRadF = intval($cardRadius);
            $cardBWF  = intval($cardBorderWidth);
            $cardBCF  = esc_attr($cardBorderColor);

            $descText = function_exists('wp_strip_all_tags')
                ? wp_strip_all_tags((string) ($product->short_description ?? $product->description ?? ''))
                : wp_strip_all_tags((string) ($product->short_description ?? $product->description ?? ''));
            // Skip the "No description available" fallback for blank
            // placeholder cards (product_id=0 AND no name) — those are
            // empty-state stand-ins; the fallback string only looks
            // right next to real products.
            $_isBlankPlaceholder = (intval($product->id ?? 0) === 0 && trim((string) ($product->name ?? '')) === '');
            if ($descText === '' && !$_isBlankPlaceholder) $descText = 'No description available.';
            $skuText  = trim((string) ($product->sku ?? ''));

            $backStyle = 'background:' . $cardBgF . ';border-radius:' . $cardRadF . 'px;border:' . $cardBWF . 'px solid ' . $cardBCF . ';display:flex;flex-direction:column;';
            echo '<div class="impulse-flip-back" style="' . esc_attr($backStyle) . '">';
            echo '<div style="padding:16px 16px 6px;flex-shrink:0">';
            echo '<h3 style="margin:0 0 4px;font-size:14px;font-weight:600;color:' . esc_attr($tColor) . ';line-height:1.4">' . esc_html((string) ($product->name ?? '')) . '</h3>';
            if ($skuText !== '') echo '<div style="font-size:11px;color:#999">' . esc_html($skuText) . '</div>';
            echo '</div>';
            echo '<div style="flex:1;padding:0 16px 12px;font-size:12px;color:#666;line-height:1.6;overflow:auto">' . esc_html($descText) . '</div>';
            echo '<div style="padding:0 16px 16px;flex-shrink:0">';
            // External-URL override on flip-card back face: link instead of button.
            $_backMeta    = is_array($meta) ? $meta : json_decode((string) ($product->meta ?? '{}'), true);
            $_backExtUrl  = is_array($_backMeta) ? trim((string) ($_backMeta['external_url'] ?? '')) : '';
            $_backBtnBase = 'width:100%;padding:10px 16px;background:' . $btnBgF . ';color:' . $btnTxtF . ';border:none;border-radius:' . $btnRadF . 'px;cursor:pointer;font-weight:600;font-size:14px';
            if ($_backExtUrl !== '' && filter_var($_backExtUrl, FILTER_VALIDATE_URL)) {
                echo '<a class="instinctor-external-buy-btn impulse-pcard-btn" href="' . esc_url($_backExtUrl) . '" data-product-id="' . intval($product->id ?? 0) . '" style="' . esc_attr($_backBtnBase) . ';display:inline-flex;align-items:center;justify-content:center;text-decoration:none;box-sizing:border-box">' . esc_html($btnLbl) . '</a>';
            } else {
                echo '<button class="instinctor-add-to-cart-btn impulse-pcard-btn" data-product-id="' . intval($product->id ?? 0) . '" style="' . esc_attr($_backBtnBase) . '">' . esc_html($btnLbl) . '</button>';
            }
            echo '</div>';
            echo '</div>'; // impulse-flip-back

            echo '</div></div>'; // impulse-flip-inner, impulse-flip-card
        }
    }
}
