<?php
declare(strict_types=1);

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

/**
 * Price list element renderer (shared).
 *
 * Phase 1, step 16. Restaurant-menu-style title…dots…price rows.
 *
 * Pre-extraction this was another "frontend-was-the-prototype" case:
 *
 *   - Editor renders 9 separator styles (dashed, solid, fade, wave,
 *     zigzag, dots, pulse, shimmer, none) including animated SVG
 *     backgrounds for wave/zigzag and CSS-keyframe pulse/shimmer.
 *   - Frontend only honoured 4 (dots, line, dashes, default empty)
 *     and used a totally different rendering technique (border-bottom
 *     on a flex spacer instead of background-image separators).
 *
 *   - Item field-name drift (PRODUCTION BUG): editor reads
 *     `$item['desc']` while frontend reads `$item['description']`.
 *     Whichever name the panel happened to save would only render in
 *     ONE renderer. Both keys are now accepted; preference is `desc`
 *     (editor canonical).
 *
 * Frontend now gains the editor's richer separators + animated
 * variants. CSS vars on container styles preserved for editor live-
 * preview; frontend uses literals (same per-context split as the
 * other extractions).
 *
 * @sideeffect Emits `@keyframes impulsePlWaveBg`, `impulsePlPulse`,
 *             `impulsePlShimmer` once per page (singleton flag) when
 *             those separator types are in use.
 */
/**
 * Per-page singleton keyframes for animated separators. Shared by the
 * price_list renderer and the Products block's "menu" display mode so both
 * emit the @keyframes exactly once. No-op unless $sep is an animated style.
 */
function impulse_pl_sep_keyframes(string $sep): void {
    if (function_exists('wp_add_inline_style')
        && in_array($sep, ['wave', 'zigzag', 'pulse', 'shimmer'], true)
        && empty($GLOBALS['impulse_pl_keyframes_emitted'])) {
        $GLOBALS['impulse_pl_keyframes_emitted'] = true;
        wp_add_inline_style('instinctor-page-inline',
            // wave/zigzag scroll their MASK (the shape is a mask now, the colour is a
            // plain background-color so the palette can reach it — see below).
              '@keyframes impulsePlWaveBg{0%{-webkit-mask-position:0 50%;mask-position:0 50%}'
            . '100%{-webkit-mask-position:200px 50%;mask-position:200px 50%}}'
            . '@keyframes impulsePlPulse{0%,100%{opacity:0.35}50%{opacity:1}}'
            . '@keyframes impulsePlShimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}'
        );
    }
}

/**
 * Connector colour: the PRICE colour at 50% opacity.
 *
 * The connector used to carry its own `pl_sep_color`, which drifted into an
 * orphan hex belonging to no palette slot — the demo's colour chips recoloured
 * the prices and left the leaders a stale tan (Sergei 2026-07-25). Deriving it
 * means the connector can never clash and always follows the swatch: the chips
 * remap rgba() as readily as hex, alpha preserved.
 *
 * Returns an rgba() string; unparseable input falls back to neutral grey.
 */
function impulse_pl_sep_color(string $priceColor): string {
    $c = trim($priceColor);
    if (preg_match('/^#([0-9a-f]{3})$/i', $c, $m)) {
        $c = '#' . $m[1][0] . $m[1][0] . $m[1][1] . $m[1][1] . $m[1][2] . $m[1][2];
    }
    if (preg_match('/^#([0-9a-f]{6})$/i', $c, $m)) {
        [$r, $g, $b] = sscanf($m[0], '#%02x%02x%02x');
        return 'rgba(' . $r . ', ' . $g . ', ' . $b . ', 0.5)';
    }
    if (preg_match('/^rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})/i', $c, $m)) {
        return 'rgba(' . (int) $m[1] . ', ' . (int) $m[2] . ', ' . (int) $m[3] . ', 0.5)';
    }
    return 'rgba(128, 128, 128, 0.5)';
}

/**
 * Restaurant-menu leader separator (title…………price). The 9 styles
 * (dashed/solid/fade/wave/zigzag/dots/pulse/shimmer/none) extracted from the
 * price_list renderer so the Products block's "menu" mode reuses them
 * verbatim. $sepColorAttr must already be esc_attr()'d; animation phase
 * delays depend on the row $idx. Colour is wrapped in the same CSS var the
 * editor injects into so live colour preview keeps working.
 */
function impulse_pl_separator_html(string $sep, int $idx, string $sepColorAttr): void {
    // Echo-internal + full-style esc_attr (NOT wp_kses): the separator styles use
    // gradient/data-URI/animation which safecss_filter_attr would strip. esc_attr on
    // the whole style value preserves all CSS (only escapes &<>"', absent here), so
    // output is byte-identical while satisfying EscapeOutput at the echo. v2.1.2032.
    $sepBase = 'flex:1;min-width:20px;margin:0 8px;position:relative;top:-4px';
    $sc      = $sepColorAttr;
    $scVar   = 'var(--impulse-pl-sep-color,' . $sc . ')';
    if ($sep === 'dashed' || $sep === 'solid') {
        echo '<span class="impulse-pl-sep" style="' . esc_attr($sepBase . ';border-bottom:1px ' . $sep . ' ' . $scVar) . '"></span>';
        return;
    }
    if ($sep === 'fade') {
        echo '<span class="impulse-pl-sep" style="' . esc_attr($sepBase . ';height:1px;background:linear-gradient(90deg, transparent, ' . $scVar . ' 30%, ' . $scVar . ' 70%, transparent)') . '"></span>';
        return;
    }
    // wave / zigzag paint the SHAPE as a mask and the COLOUR as a plain
    // background-color. Baking the colour into the data URI (the old form) put
    // it behind %23-encoding, where neither the demo's palette remap nor any
    // stylesheet could ever see it — the leaders kept a stale hue while the
    // rest of the row recoloured. A mask keeps the artwork and frees the colour.
    if ($sep === 'wave' || $sep === 'zigzag') {
        $uri = $sep === 'wave'
            ? 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22200%22%20height=%226%22%20viewBox=%220%200%20200%206%22%3E%3Cpath%20d=%22M0%2C3%20C8%2C0%2012%2C0%2020%2C3%20C28%2C6%2032%2C6%2040%2C3%20C48%2C0%2052%2C0%2060%2C3%20C68%2C6%2072%2C6%2080%2C3%20C88%2C0%2092%2C0%20100%2C3%20C108%2C6%20112%2C6%20120%2C3%20C128%2C0%20132%2C0%20140%2C3%20C148%2C6%20152%2C6%20160%2C3%20C168%2C0%20172%2C0%20180%2C3%20C188%2C6%20192%2C6%20200%2C3%22%20fill=%22none%22%20stroke=%22%23fff%22%20stroke-width=%221%22%20vector-effect=%22non-scaling-stroke%22%2F%3E%3C%2Fsvg%3E'
            : 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20width=%22200%22%20height=%226%22%20viewBox=%220%200%20200%206%22%3E%3Cpath%20d=%22M0%2C5%20L5%2C1%20L10%2C5%20L15%2C1%20L20%2C5%20L25%2C1%20L30%2C5%20L35%2C1%20L40%2C5%20L45%2C1%20L50%2C5%20L55%2C1%20L60%2C5%20L65%2C1%20L70%2C5%20L75%2C1%20L80%2C5%20L85%2C1%20L90%2C5%20L95%2C1%20L100%2C5%20L105%2C1%20L110%2C5%20L115%2C1%20L120%2C5%20L125%2C1%20L130%2C5%20L135%2C1%20L140%2C5%20L145%2C1%20L150%2C5%20L155%2C1%20L160%2C5%20L165%2C1%20L170%2C5%20L175%2C1%20L180%2C5%20L185%2C1%20L190%2C5%20L195%2C1%20L200%2C5%22%20fill=%22none%22%20stroke=%22%23fff%22%20stroke-width=%221%22%20vector-effect=%22non-scaling-stroke%22%2F%3E%3C%2Fsvg%3E';
        $dur = $sep === 'wave' ? '6s' : '16s';
        echo '<span class="impulse-pl-sep" style="' . esc_attr($sepBase . ';height:6px;background-color:' . $scVar
            . ';-webkit-mask-image:url(' . $uri . ');mask-image:url(' . $uri . ')'
            . ';-webkit-mask-repeat:repeat-x;mask-repeat:repeat-x'
            . ';-webkit-mask-size:200px 100%;mask-size:200px 100%'
            . ';animation:impulsePlWaveBg ' . $dur . ' linear infinite') . '"></span>';
        return;
    }
    if ($sep === 'dots') {
        echo '<span class="impulse-pl-sep" style="' . esc_attr($sepBase . ';border-bottom:2px dotted ' . $scVar) . '"></span>';
        return;
    }
    if ($sep === 'pulse') {
        $delay = $idx * 0.5;
        echo '<span class="impulse-pl-sep" style="' . esc_attr($sepBase . ';height:1px;background:' . $scVar . ';animation:impulsePlPulse 2.5s ease-in-out ' . $delay . 's infinite') . '"></span>';
        return;
    }
    if ($sep === 'shimmer') {
        $delay = $idx * 0.4;
        echo '<span class="impulse-pl-sep" style="' . esc_attr($sepBase . ';height:1px;background:linear-gradient(90deg, transparent 0%, ' . $scVar . ' 50%, transparent 100%);background-size:200% 100%;animation:impulsePlShimmer 3.5s linear ' . $delay . 's infinite') . '"></span>';
        return;
    }
    echo '<span class="impulse-pl-sep" style="flex:1;min-width:20px"></span>';
}

function impulse_element_render_price_list($el, array $settings, string $context): string {
    if (is_object($el)) $el = (array) $el;
    $elId     = preg_replace('/[^A-Za-z0-9_-]/', '', (string) ($el['id'] ?? '0'));
    $isEditor = ($context === 'editor');
    $device   = $isEditor ? ($GLOBALS['impulse_preview_device'] ?? 'desktop') : 'desktop';

    $read = function (string $key, $default) use ($settings, $isEditor) {
        if ($isEditor && function_exists('impulse_device_val')) {
            return impulse_device_val($settings, $key, $device = $GLOBALS['impulse_preview_device'] ?? 'desktop', $default);
        }
        return $settings[$key] ?? $default;
    };

    $plItems = $settings['pl_items'] ?? [];
    if (is_string($plItems)) $plItems = json_decode($plItems, true) ?: [];

    // Editor: default to a 3-item demo so blank blocks look intentional.
    if (empty($plItems)) {
        if ($isEditor) {
            $plItems = [
                ['title'=>'Consultation','desc'=>'','price'=>'$50','icon'=>'circle'],
                ['title'=>'Basic Package','desc'=>'Includes setup and training','price'=>'$199','icon'=>'circle'],
                ['title'=>'Premium Package','desc'=>'Full service with priority support','price'=>'$499','icon'=>'circle'],
            ];
        } else {
            return ''; // Frontend: render nothing if the block has no items.
        }
    }

    $plSep         = (string) ($settings['pl_separator'] ?? 'dashed');
    if ($plSep === 'dotted') $plSep = 'dashed'; // backward compat
    $plTitleSize   = intval($read('pl_title_size',  16));
    $plPriceSize   = intval($read('pl_price_size',  16));
    $plDescSize    = intval($read('pl_desc_size',   13));
    $plSpacing     = intval($read('pl_spacing',     16));
    $plIconSize    = intval($read('pl_icon_size',   18));
    $plTitleColor  = (string) ($settings['pl_title_color']  ?? '#1d2327');
    $plPriceColor  = (string) ($settings['pl_price_color']  ?? '#1d2327');
    $plDescColor   = (string) ($settings['pl_desc_color']   ?? '#888888');
    $plBorderColor = (string) ($settings['pl_border_color'] ?? '#e0e0e0');
    $plIconColor   = (string) ($settings['pl_icon_color']   ?? $plTitleColor);
    $plMaxWidth    = intval($settings['section_max_width']  ?? 800);
    $plShowDivider = !isset($settings['pl_show_divider']) || !empty($settings['pl_show_divider']);

    // Connector = the price colour at 50%. Derived, never stored: see
    // impulse_pl_sep_color() for why a standalone setting could not hold.
    $sc = esc_attr(impulse_pl_sep_color($plPriceColor));

    // Per-page singleton keyframes for animated separators (shared helper).
    impulse_pl_sep_keyframes($plSep);

    // ── Compose HTML ─────────────────────────────────────────────
    // v2.1.1367: explicit `width: 100%` because `margin: 0 auto` on a flex
    // child cancels `align-self: stretch` (CSS Flexbox spec: "If a flex item
    // has margin: auto on the cross axis, then align-self: stretch has no
    // effect"). Without the explicit width the wrapper shrinks to its content's
    // intrinsic max-content width and the rows bunch into a narrow column at
    // the centre of any compact section that's wider than the rows themselves.
    // max-width still caps it on wider parents; auto margins then centre.
    $plWrapStyle = $isEditor
        ? 'width:100%;max-width:var(--impulse-section-max-width,' . $plMaxWidth . 'px);margin:0 auto'
        : 'width:100%;max-width:' . $plMaxWidth . 'px;margin:0 auto';

    ob_start();
    echo '<div id="pl-' . esc_attr($elId) . '" class="impulse-price-list" style="' . esc_attr($plWrapStyle) . '">';

    $idx = 0;
    foreach ($plItems as $item) {
        $title = (string) ($item['title'] ?? '');
        $price = (string) ($item['price'] ?? '');
        // Field-name drift fix: pre-extraction frontend read 'description',
        // editor wrote 'desc' — items saved via the panel never showed
        // their description on the frontend. Accept both, prefer 'desc'.
        $desc  = (string) ($item['desc'] ?? ($item['description'] ?? ''));
        $icon  = (string) ($item['icon'] ?? 'circle');
        $hasIcon = $icon !== '' && $icon !== 'none';

        $dividerStyle = $plShowDivider
            ? ($isEditor
                ? 'border-bottom:1px solid var(--impulse-pl-border-color,' . esc_attr($plBorderColor) . ')'
                : 'border-bottom:1px solid ' . esc_attr($plBorderColor))
            : 'border-bottom:none';
        $spacingClause = $isEditor
            ? 'padding:var(--impulse-pl-spacing,' . $plSpacing . 'px) 0;'
            : 'padding:' . $plSpacing . 'px 0;';

        echo '<div class="impulse-price-item" style="' . esc_attr($spacingClause . $dividerStyle) . '">';
        echo '<div style="display:flex;align-items:baseline;gap:8px">';

        if ($hasIcon && function_exists('impulse_lucide_svg')) {
            if ($isEditor) {
                $iconCssStyle = 'flex-shrink:0;position:relative;top:1px;width:var(--impulse-pl-icon-size,' . $plIconSize . 'px);height:var(--impulse-pl-icon-size,' . $plIconSize . 'px);color:var(--impulse-pl-icon-color,' . esc_attr($plIconColor) . ')';
                echo wp_kses(impulse_lucide_svg($icon, $plIconSize, 'currentColor', ['style' => $iconCssStyle]), impulse_svg_allowed());
            } else {
                echo '<span style="color:' . esc_attr($plIconColor) . ';flex-shrink:0;display:flex;align-items:center">' . wp_kses(impulse_lucide_svg($icon, $plIconSize, 'currentColor'), impulse_svg_allowed()) . '</span>';
            }
        }

        $titleStyleE = 'font-weight:600;color:var(--impulse-pl-title-color,' . esc_attr($plTitleColor) . ');font-size:var(--impulse-pl-title-size,' . $plTitleSize . 'px);flex-shrink:0';
        $titleStyleF = 'font-size:' . $plTitleSize . 'px;font-weight:600;color:' . esc_attr($plTitleColor);
        echo '<span class="impulse-pl-title" style="' . esc_attr($isEditor ? $titleStyleE : $titleStyleF) . '">' . esc_html($title) . '</span>';

        impulse_pl_separator_html($plSep, $idx, $sc);

        $priceStyleE = 'font-weight:700;color:var(--impulse-pl-price-color,' . esc_attr($plPriceColor) . ');font-size:var(--impulse-pl-price-size,' . $plPriceSize . 'px);white-space:nowrap;flex-shrink:0';
        $priceStyleF = 'font-size:' . $plPriceSize . 'px;font-weight:600;color:' . esc_attr($plPriceColor) . ';white-space:nowrap';
        echo '<span class="impulse-pl-price" style="' . esc_attr($isEditor ? $priceStyleE : $priceStyleF) . '">' . esc_html($price) . '</span>';

        echo '</div>';

        if ($desc !== '') {
            $descPad = $hasIcon ? ($plIconSize + 8) : 0;
            $descStyleE = 'font-size:var(--impulse-pl-desc-size,' . $plDescSize . 'px);color:var(--impulse-pl-desc-color,' . esc_attr($plDescColor) . ');margin-top:2px;padding-left:' . $descPad . 'px';
            $descStyleF = 'font-size:' . $plDescSize . 'px;color:' . esc_attr($plDescColor) . ';margin-top:4px;line-height:1.4;padding-left:' . $descPad . 'px';
            echo '<div class="impulse-pl-desc" style="' . esc_attr($isEditor ? $descStyleE : $descStyleF) . '">' . esc_html($desc) . '</div>';
        }

        echo '</div>';
        $idx++;
    }

    echo '</div>';

    // Mobile-first: the per-row vertical padding (pl_spacing) that gives a menu
    // generous breathing room on desktop stacks into huge gaps on a phone (2x the
    // value between every row). Scale it down for <=767px — honour an explicit
    // pl_spacing_mobile if set, else ~60% of desktop (min 8px).
    if (!$isEditor && function_exists('wp_add_inline_style')) {
        $plSpacingMobile = (isset($settings['pl_spacing_mobile']) && $settings['pl_spacing_mobile'] !== '')
            ? max(0, intval($settings['pl_spacing_mobile']))
            : max(8, (int) round($plSpacing * 0.6));
        if ($plSpacingMobile < $plSpacing) {
            wp_add_inline_style('instinctor-page-inline',
                '@media(max-width:767px){#pl-' . $elId . ' .impulse-price-item{padding-top:' . $plSpacingMobile . 'px!important;padding-bottom:' . $plSpacingMobile . 'px!important}}');
        }
    }

    return ob_get_clean();
}
