<?php
declare(strict_types=1);

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

/**
 * Marquee block renderer (shared frontend + editor).
 *
 * Continuous horizontal strips of hand-authored, clickable cards
 * (icon / title / subtitle / link). 1-3 rows, each drifting left or
 * right. The drift is a pure CSS keyframe animation: each row is a
 * `.impulse-marquee-track` holding TWO identical card sets, animated
 * translateX(0) to -50%, so the loop runs on the compositor and its wrap
 * is seamless with no main-thread transform jump. (This replaced the
 * shared JS carousel-init engine, whose per-loop transform reset flashed
 * the whole row.) Opposing rows just flip the keyframe direction.
 *
 * Seamless loop: -50% lands the track exactly one set back, which is why
 * the two sets must be identical. Each set repeats the author's cards
 * enough times to clear ~4000px (wider than a 4K viewport) so a set is
 * never narrower than the row. Only the first author pass is exposed to
 * assistive tech / keyboard; every repeat + clone is aria-hidden +
 * non-tabbable. The animation also runs in the editor now.
 *
 * Settings (stored on the element):
 *   speed       1-10, drift pace (default 5)
 *   card_width  120-360, drives padding/icon/type proportionally (default 200)
 *   card_height min-height px (0 = size to content)
 *   card_radius corner rounding px
 *   row_count   1-3 rows (default 2)
 *   dir_0/1/2   'left' | 'right' per row (default alternating)
 *   edge_fade   bool - faded strip edges (default true)
 *   pause_hover bool - pause the row under the pointer (default true)
 *   hover_reveal bool - cross-fade a card front to its 'back' detail on hover
 *   card_bg/text_color/icon_color/border_color  card colours
 *   cards       JSON array: [{icon,title,subtitle,link,new_tab,back,row}, ...]
 *
 * Free for everyone.
 *
 * @param array|object $el       Element node.
 * @param array        $settings Resolved (editor) or raw (frontend) settings.
 * @param string       $context  'frontend' or 'editor'.
 * @return string Inner HTML.
 *
 * @sideeffect Frontend enqueues instinctor-marquee-css (self-contained;
 *             no JS engine).
 */
function impulse_element_render_marquee($el, array $settings, string $context): string {
    if (is_object($el)) $el = (array) $el;
    $isEditor = ($context === 'editor');

    // Card size drives the card width; padding, icon, and type scale off it so
    // cards stay proportioned from Compact to Showcase on one slider. Replaces
    // the old Calm/Standard/Brisk/Showcase presets (they mostly varied speed,
    // which now has its own slider, plus size, which this is).
    $cardW = (int) ($settings['card_width'] ?? 200);
    if ($cardW < 120) $cardW = 120;
    if ($cardW > 360) $cardW = 360;
    $icoSize   = (int) round($cardW * 0.10);
    $titleSize = (int) round(12 + $cardW * 0.018);
    $subSize   = max(10, $titleSize - 3);
    $padV      = (int) round($cardW * 0.07);
    $padH      = (int) round($cardW * 0.08);
    $gap       = (int) round($cardW * 0.07);
    $rowGap    = (int) round(10 + $cardW * 0.02);
    $fade      = 12;

    $speed = (float) ($settings['speed'] ?? 5);
    if ($speed < 1)  $speed = 1;
    if ($speed > 10) $speed = 10;
    // Speed -> px/sec is applied per row (duration = one set width / px-per-sec).

    $pauseHover = ($settings['pause_hover'] ?? true) !== false;
    $edgeFade   = ($settings['edge_fade']   ?? true) !== false;
    $rowCount   = max(1, min(3, intval($settings['row_count'] ?? 2)));

    // Card mode: icon (default) | image (logo/photo strip) | testimonial.
    $mode = (string) ($settings['mode'] ?? 'icon');
    if (!in_array($mode, ['icon', 'image', 'testimonial'], true)) $mode = 'icon';
    $imageStyle = ((string) ($settings['image_style'] ?? 'photo') === 'logo') ? 'logo' : 'photo';
    $captionPos = (string) ($settings['caption_pos'] ?? 'below');
    if (!in_array($captionPos, ['below', 'overlay', 'hover', 'none'], true)) $captionPos = 'below';

    $cards = $settings['cards'] ?? null;
    if (is_string($cards)) $cards = json_decode($cards, true);
    if (empty($cards) || !is_array($cards)) $cards = impulse_marquee_default_cards();

    // Bucket cards into their rows (out-of-range folds into the last row).
    $rows = array_fill(0, $rowCount, []);
    foreach ($cards as $c) {
        if (!is_array($c)) continue;
        $r = intval($c['row'] ?? 0);
        if ($r < 0) $r = 0;
        if ($r >= $rowCount) $r = $rowCount - 1;
        $rows[$r][] = $c;
    }

    $hasContent = false;
    foreach ($rows as $rc) { if (!empty($rc)) { $hasContent = true; break; } }
    if (!$hasContent && !$isEditor) return '';

    if (!$isEditor && class_exists('\\Instinctor\\Core\\Assets')) {
        \Instinctor\Core\Assets::enqueue('instinctor-marquee-css');
        // Motion is a pure CSS keyframe (see impulse-marquee.css) — no JS engine.
    }

    $advance = $cardW + $gap; // per-card horizontal advance

    // Optional card-style overrides. Blank colours fall back to the neutral
    // CSS defaults that read on both light and dark pages; colours accept any
    // CSS colour string. Height 0 = auto (fit content). Radius default 12.
    $cardBg      = trim((string) ($settings['card_bg']      ?? ''));
    $textColor   = trim((string) ($settings['text_color']   ?? ''));
    $iconColor   = trim((string) ($settings['icon_color']   ?? ''));
    $borderColor = trim((string) ($settings['border_color'] ?? ''));
    $cardHeight  = max(0, intval($settings['card_height'] ?? 0));
    $cardRadius  = isset($settings['card_radius']) ? max(0, intval($settings['card_radius'])) : 12;

    $wrapStyle = '--mq-card-w:' . $cardW . 'px'
        . ';--mq-gap:' . $gap . 'px'
        . ';--mq-pad-v:' . $padV . 'px'
        . ';--mq-pad-h:' . $padH . 'px'
        . ';--mq-title-size:' . $titleSize . 'px'
        . ';--mq-sub-size:' . $subSize . 'px'
        . ';--mq-fade:' . $fade . '%'
        . ';--mq-radius:' . $cardRadius . 'px'
        . ';--mq-row-gap:' . $rowGap . 'px';
    if ($cardBg      !== '') $wrapStyle .= ';--mq-card-bg:' . $cardBg;
    if ($textColor   !== '') $wrapStyle .= ';--mq-text:' . $textColor;
    if ($iconColor   !== '') $wrapStyle .= ';--mq-icon-color:' . $iconColor;
    if ($borderColor !== '') $wrapStyle .= ';--mq-border:' . $borderColor;
    if ($cardHeight  > 0)    $wrapStyle .= ';--mq-card-h:' . $cardHeight . 'px';

    ob_start();

    if ($isEditor && function_exists('impulse_visibility_badge')) {
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- impulse_visibility_badge() composes its markup from esc_attr()'d values internally.
        echo wp_kses(impulse_visibility_badge($settings), impulse_svg_allowed());
    }

    $hoverReveal = !empty($settings['hover_reveal']);
    $frosted     = !empty($settings['frosted_glass']) && $settings['frosted_glass'] !== '0';

    $modeClass = ' impulse-marquee--' . $mode . ($mode === 'image' ? ' mq-img-' . $imageStyle . ' mq-cap-' . $captionPos : '') . ($frosted ? ' mq-frosted' : '');
    echo '<div class="impulse-marquee' . esc_attr($modeClass) . '"'
       . ' data-pause="' . ($pauseHover ? '1' : '0') . '"'
       . ($hoverReveal ? ' data-reveal="1"' : '')
       . ' style="' . esc_attr($wrapStyle) . '">';

    $rendered = 0;
    foreach ($rows as $i => $rowCards) {
        if (empty($rowCards)) continue;
        $rendered++;

        $d = strtolower((string) ($settings['dir_' . $i] ?? ''));
        if ($d !== 'left' && $d !== 'right') $d = ($i % 2 === 0) ? 'left' : 'right';

        $nUser = count($rowCards);
        // Repeat the author's cards until ONE "set" is wider than the widest
        // likely viewport (~4000px covers up to 4K). translateX(-50%) reveals up
        // to one set past the start, so a set narrower than the viewport would
        // expose empty track near the loop point; a wider set keeps it full.
        $reps  = max(1, (int) ceil(4000 / max(1, $nUser * $advance)));
        $perSet = $nUser * $reps; // cards in one set

        $keyframe = ($d === 'right') ? 'impulse-mq-right' : 'impulse-mq-left';
        // CSS keyframe drift: translateX(-50%) equals exactly one set (the track
        // holds two identical sets), so the loop point is seamless AND runs on
        // the compositor with no main-thread transform jump. Duration = time to
        // cross one set at the chosen speed.
        $pxPerSec = 20 + (int) round($speed) * 12;   // speed 1..10 -> 32..140 px/s
        $duration = max(4, (int) round(($perSet * $advance) / max(1, $pxPerSec)));

        echo '<div class="impulse-marquee-row' . ($edgeFade ? ' mq-fade' : '') . '">';
        echo '<div class="impulse-marquee-track" style="animation:'
           . esc_attr($keyframe . ' ' . $duration . 's linear infinite') . '">';

        // Two identical sets of [author cards x reps] -> translateX(-50%) is one
        // set. The first author pass is exposed; the rest are hidden clones so
        // each card is announced / tabbed exactly once.
        $slideIdx = 0;
        for ($set = 0; $set < 2; $set++) {
            for ($rep = 0; $rep < $reps; $rep++) {
                foreach ($rowCards as $c) {
                    $clone = ($slideIdx >= $nUser);
                    impulse_marquee_card($c, $icoSize, $isEditor, $clone, $hoverReveal, $mode);
                    $slideIdx++;
                }
            }
        }

        echo '</div></div>';
    }

    if ($rendered === 0 && $isEditor) {
        echo '<div style="padding:24px;text-align:center;font-size:13px;color:#999;border:1px dashed #d0d0d0;border-radius:8px">Add cards to the marquee.</div>';
    }

    echo '</div>';

    return (string) ob_get_clean();
}

/**
 * Echo one marquee card, dispatching on the block's card mode. Clones (loop
 * repeats) are hidden from assistive tech + keyboard so each real card is
 * announced and tabbed exactly once. Modes: 'icon' (icon + title + subtitle,
 * optional hover-reveal detail), 'image' (logo/photo strip), 'testimonial'
 * (quote + stars + avatar/name/role). The card frame + link are shared; only
 * the inner body differs by mode.
 */
function impulse_marquee_card(array $c, int $icoSize, bool $isEditor, bool $clone, bool $reveal = false, string $mode = 'icon'): void {
    $link   = (string) ($c['link'] ?? '');
    $newTab = !empty($c['new_tab']);
    $isLink = ($link !== '' && !$isEditor);

    echo $isLink
        ? '<a class="impulse-marquee-card" href="' . esc_url($link) . '"'
            . ($newTab ? ' target="_blank" rel="noopener"' : '')
            . ($clone ? ' aria-hidden="true" tabindex="-1"' : '') . '>'
        : '<div class="impulse-marquee-card"' . ($clone ? ' aria-hidden="true"' : '') . '>';

    if ($mode === 'image') {
        impulse_marquee_card_image($c);
    } elseif ($mode === 'testimonial') {
        impulse_marquee_card_testimonial($c);
    } else {
        impulse_marquee_card_icon($c, $icoSize, $reveal);
    }

    echo $isLink ? '</a>' : '</div>';
}

/** Icon-card body: icon + title + subtitle, optional hover-reveal 'back' detail. */
function impulse_marquee_card_icon(array $c, int $icoSize, bool $reveal): void {
    $icon  = (string) ($c['icon']     ?? '');
    $title = (string) ($c['title']    ?? '');
    $sub   = (string) ($c['subtitle'] ?? '');
    $back  = (string) ($c['back']     ?? '');
    $svg   = ($icon !== '' && function_exists('impulse_lucide_svg')) ? impulse_lucide_svg($icon, $icoSize) : '';
    echo '<span class="impulse-marquee-front">';
    if ($svg !== '') echo '<span class="impulse-marquee-ico">' . wp_kses($svg, impulse_svg_allowed()) . '</span>';
    echo '<span class="impulse-marquee-tt">';
    if ($title !== '') echo '<span class="impulse-marquee-title">' . esc_html($title) . '</span>';
    if ($sub   !== '') echo '<span class="impulse-marquee-sub">' . esc_html($sub) . '</span>';
    echo '</span></span>';
    if ($reveal && $back !== '') echo '<span class="impulse-marquee-back">' . esc_html($back) . '</span>';
}

/** Image-card body: a logo or photo (logo/photo style is a block-level class), optional caption. */
function impulse_marquee_card_image(array $c): void {
    $url = impulse_marquee_img_url($c, 'image');
    $cap = (string) ($c['caption'] ?? '');
    if ($url !== '') {
        echo '<img class="impulse-marquee-img" src="' . esc_url($url) . '" alt="' . esc_attr($cap) . '" loading="lazy">';
    } else {
        echo '<span class="impulse-marquee-img impulse-marquee-img-ph" aria-hidden="true"></span>';
    }
    if ($cap !== '') echo '<span class="impulse-marquee-cap">' . esc_html($cap) . '</span>';
}

/** Testimonial-card body: star rating + quote + avatar/name/role. */
function impulse_marquee_card_testimonial(array $c): void {
    $avatar = impulse_marquee_img_url($c, 'avatar');
    $name   = (string) ($c['name']  ?? '');
    $role   = (string) ($c['role']  ?? '');
    $quote  = (string) ($c['quote'] ?? '');
    $rating = (int) round((float) ($c['rating'] ?? 5));
    if ($rating < 0) $rating = 0;
    if ($rating > 5) $rating = 5;
    if ($rating > 0) impulse_marquee_stars($rating);
    if ($quote !== '') echo '<span class="impulse-marquee-quote">' . esc_html($quote) . '</span>';
    echo '<span class="impulse-marquee-person">';
    if ($avatar !== '') echo '<img class="impulse-marquee-avatar" src="' . esc_url($avatar) . '" alt="' . esc_attr($name) . '" loading="lazy">';
    echo '<span class="impulse-marquee-person-tt">';
    if ($name !== '') echo '<span class="impulse-marquee-name">' . esc_html($name) . '</span>';
    if ($role !== '') echo '<span class="impulse-marquee-role">' . esc_html($role) . '</span>';
    echo '</span></span>';
}

/** Resolve a card image/avatar URL: prefer a fresh resolve from the stored id
 *  (handles the standalone library + WP attachment fallback), else the saved URL. */
function impulse_marquee_img_url(array $c, string $key): string {
    $id = intval($c[$key . '_id'] ?? 0);
    if ($id > 0 && function_exists('impulse_resolve_image_url')) {
        $u = impulse_resolve_image_url($id, 'large');
        if ($u !== '') return $u;
    }
    return (string) ($c[$key] ?? '');
}

/** Echo a 5-star row, gold-filled to $rating (star colour is CSS-driven). */
function impulse_marquee_stars(int $rating): void {
    $star = wp_kses(
        '<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14l-5-4.87 6.91-1.01L12 2z"/></svg>',
        impulse_svg_allowed()
    );
    echo '<span class="impulse-marquee-stars" role="img" aria-label="' . esc_attr($rating . ' out of 5 stars') . '">';
    for ($i = 1; $i <= 5; $i++) {
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- $star is a static wp_kses'd literal SVG.
        echo '<span class="impulse-marquee-star' . ($i <= $rating ? ' on' : '') . '">' . $star . '</span>';
    }
    echo '</span>';
}

/** Default demo cards for a freshly-inserted block — two opposing rows. */
function impulse_marquee_default_cards(): array {
    return [
        ['icon' => 'search',         'title' => 'Found on Google', 'subtitle' => 'Built-in SEO',       'link' => '', 'new_tab' => false, 'row' => 0],
        ['icon' => 'shopping-bag',   'title' => 'Storefront',      'subtitle' => 'Sell online',        'link' => '', 'new_tab' => false, 'row' => 0],
        ['icon' => 'clipboard-list', 'title' => 'Live menus',      'subtitle' => 'Update everywhere',  'link' => '', 'new_tab' => false, 'row' => 0],
        ['icon' => 'credit-card',    'title' => 'Payments',        'subtitle' => 'Cards and wallets',  'link' => '', 'new_tab' => false, 'row' => 0],
        ['icon' => 'calendar',       'title' => 'Bookings',        'subtitle' => 'Schedule and pay',   'link' => '', 'new_tab' => false, 'row' => 1],
        ['icon' => 'message-circle', 'title' => 'Smart chat',      'subtitle' => 'SMS and WhatsApp',   'link' => '', 'new_tab' => false, 'row' => 1],
        ['icon' => 'star',           'title' => 'Reviews',         'subtitle' => 'Social proof',       'link' => '', 'new_tab' => false, 'row' => 1],
        ['icon' => 'database',       'title' => 'Your data',       'subtitle' => 'All in one place',   'link' => '', 'new_tab' => false, 'row' => 1],
    ];
}
