<?php
declare(strict_types=1);

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

/**
 * Slider element renderer (shared).
 *
 * Phase 1, step 20.
 *
 * Slider hosts an ordered list of slides; children carry a `slide_index`
 * setting that pins them to a specific slide. The same renderer drives
 * three families of effect:
 *
 *   - **Plain slide** (default) — flex track translates left/right (or
 *     up/down). Single visible slide at a time (or N visible when
 *     items_to_show > 1 in editor; frontend always shows 1 currently).
 *
 *   - **Scroll** (marquee) — flex track flows continuously via rAF.
 *     The slide set is duplicated 3+ times so the loop never reveals
 *     empty track.
 *
 *   - **Grid modes** (coverflow / cube / conveyor / portal) — slides
 *     absolutely-positioned and animated in 3D by
 *     impulse-image-carousel.js. The renderer just emits the markup;
 *     all motion is JS-driven.
 *
 *   - **Stacked effects** (depth_rush / card_flip etc.) — absolute-
 *     positioned, opacity-cycled by the new-slider init.
 *
 * Pre-extraction differences:
 *
 *   - Editor adds `current_slide` data attr (for the editor's slide
 *     dot indicator), `items_to_show` + `gap` (multi-slide-visible
 *     mode), an empty-slide dropzone with `+` indicator, and an
 *     `impulse-carousel-slide-empty` class.
 *
 *   - Frontend has `tilt_sens` data attr for the orbit/table/cube
 *     modes (mouse-sensitivity for 3D rotation), `height_mode` (auto
 *     vs fixed), and a real `mobile_stack` data attr (editor only
 *     emits it when device tab = mobile).
 *
 *   - Empty-slide handling: frontend skips truly-empty slides
 *     entirely; editor renders them with a dropzone + plus icon so
 *     the user can populate them.
 *
 *   - Responsive height @media (height_tablet / height_mobile) is
 *     frontend-only; editor resolves per-device server-side via
 *     impulse_get_responsive on the base `height` key.
 *
 * @sideeffect Primes $GLOBALS['impulse_needs_carousel'] +
 *             impulse_has_carousel; sets impulse_inside_slider
 *             during child rendering so child blocks can detect
 *             they're inside a slider (e.g. products block caps
 *             count to 4).
 */
function impulse_element_render_slider($el, array $settings, array $children, string $context, callable $renderChild): 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_get_responsive')) {
            return impulse_get_responsive($settings, $key, $default);
        }
        return $settings[$key] ?? $default;
    };

    $GLOBALS['impulse_needs_carousel'] = true;
    $GLOBALS['impulse_has_carousel']   = true;

    // ── Settings ──────────────────────────────────────────────────
    $height          = intval($read('height', 500));
    $heightMode      = (string) ($settings['height_mode'] ?? 'fixed');
    $autoplay        = $isEditor
        ? (($settings['autoplay'] ?? true) !== false)
        : !empty($settings['autoplay']);
    $autoplaySpeed   = intval($read('autoplay_speed', 5));
    if ($autoplaySpeed <= 100) $autoplaySpeed *= 1000;
    $direction       = (string) ($settings['direction'] ?? 'right');
    $transitionSpeed = intval($read('transition_speed', 400));
    $currentSlide    = intval($settings['current_slide'] ?? 0);
    $itemsToShow     = max(1, intval($read('items_to_show', 1)));
    $gap             = intval($read('gap', 0));
    $sliderWidth     = intval($read('slider_width', 0));
    $sliderEffect    = (string) ($settings['slider_effect'] ?? 'slide');
    $mobileStack     = !empty($settings['mobile_stack']);

    // ── Slides array ──────────────────────────────────────────────
    $slides = $settings['slides'] ?? [];
    if (is_string($slides)) $slides = json_decode($slides, true) ?: [];
    if (empty($slides)) {
        $slides = $isEditor
            ? [
                ['id' => 'slide-1', 'background' => '', 'valign' => 'center'],
                ['id' => 'slide-2', 'background' => '', 'valign' => 'center'],
            ]
            : [['id' => 'slide-1', 'background' => '#f5f5f5', 'valign' => 'center']];
    }
    // Frontend honours slide_count cap (1..20); editor doesn't (allows arbitrary count).
    if (!$isEditor) {
        $savedCount = isset($settings['slide_count']) ? intval($settings['slide_count']) : 0;
        if ($savedCount >= 1 && $savedCount <= 20) $slides = array_slice($slides, 0, $savedCount);
    }
    // Editor: auto-pad slides up to itemsToShow so the multi-visible mode always shows placeholders.
    if ($isEditor && $itemsToShow > 1) {
        while (count($slides) < $itemsToShow) {
            $n = count($slides) + 1;
            $slides[] = ['id' => 'slide-' . $n, 'background' => '', 'valign' => 'center'];
        }
    }
    $slideCount = count($slides);

    $isVertical    = ($direction === 'up' || $direction === 'down');
    // depth_rush + card_flip added 2026-07-20: the image-carousel engine
    // implements both, but they were missing from this list, so content
    // sliders using them fell to the generic track engine which just
    // translated the whole strip off-screen ("card flies off to the left").
    $sliderGridModes = ['coverflow', 'cube', 'conveyor', 'portal', 'depth_rush', 'card_flip'];
    $isGridMode    = in_array($sliderEffect, $sliderGridModes, true);
    $isScrollMode  = ($sliderEffect === 'scroll');
    $isImgCarouselMode = $isGridMode || $isScrollMode;
    $isStackedEffect   = !in_array($sliderEffect, ['slide', 'scroll'], true);

    // Slides container style.
    $slidesHgt = $heightMode === 'auto' ? 'min-height:100%' : 'height:100%';
    $flexDir   = $isVertical ? 'flex-direction:column;' : '';
    $gapStyle  = ($isEditor && $gap > 0 && !$isStackedEffect) ? 'gap:' . $gap . 'px;' : '';
    $slidesStyle = $isStackedEffect
        ? 'position:relative;width:100%;' . $slidesHgt
        : 'display:flex;' . $flexDir . $gapStyle . 'transition:transform ' . $transitionSpeed . 'ms ease-in-out;' . $slidesHgt;

    // Slide flex style — multi-visible (editor) or single (frontend).
    if ($isEditor && $itemsToShow > 1 && !$isStackedEffect && !$isVertical) {
        $slideFlexBasis = 'calc(' . (100 / $itemsToShow) . '% - ' . round($gap * ($itemsToShow - 1) / $itemsToShow, 2) . 'px)';
        $slideFlexStyle = 'flex:0 0 ' . $slideFlexBasis . ';min-width:0;';
    } elseif ($isVertical) {
        $slideFlexStyle = 'flex:0 0 100%;min-height:100%;';
    } else {
        $slideFlexStyle = 'flex:0 0 100%;min-width:100%;';
    }

    $wrapOverflow = $isImgCarouselMode ? 'visible' : 'hidden';
    $sliderWrapStyle = 'position:relative;overflow:' . $wrapOverflow . ';';
    if ($sliderWidth > 0) $sliderWrapStyle .= 'max-width:' . $sliderWidth . 'px;margin-left:auto;margin-right:auto;';

    // Editor only emits when on mobile device tab; frontend always emits.
    $emitMobileStack = $mobileStack && (!$isEditor || $device === 'mobile');

    $wrapperClasses = $isImgCarouselMode
        ? 'impulse-carousel impulse-new-slider impulse-image-carousel'
        : 'impulse-carousel impulse-new-slider';

    $scrollSpd = ($isImgCarouselMode && $isScrollMode)
        ? max(3, round(60 / max(1, intval($settings['scroll_speed'] ?? 3))))
        : 0;
    // Tilt sensitivity for 3D rotational effects (frontend only — editor doesn't need it).
    $tiltSens = ($isImgCarouselMode && !$isEditor && in_array($sliderEffect, ['orbit', 'table', 'cube'], true))
        ? max(0, min(10, isset($settings['tilt_sens']) ? intval($settings['tilt_sens']) : 5))
        : 0;

    $carouselId = 'new-slider-' . ($elId ?: (function_exists('wp_rand') ? wp_rand(1000, 9999) : random_int(1000, 9999)));

    // ── Compose HTML ──────────────────────────────────────────────
    ob_start();

    // Wrapper data attrs differ slightly between contexts.
    echo '<div id="' . esc_attr($carouselId) . '" class="' . esc_attr($wrapperClasses) . '"';
    echo ' data-autoplay="' . esc_attr($autoplay ? 'true' : 'false') . '"';
    // Emit on the frontend too (not just editor) for stacked/content sliders, so the
    // autoplay-speed setting is honoured live instead of defaulting to 5s. Image-carousel
    // modes emit their own data-speed/data-interval in the block below, so skip there.
    if ($isEditor || !$isImgCarouselMode) echo ' data-speed="' . esc_attr((string) $autoplaySpeed) . '"';
    echo ' data-direction="' . esc_attr($direction) . '"';
    echo ' data-transition="' . esc_attr((string) $transitionSpeed) . '"';
    if ($isEditor) echo ' data-current-slide="' . esc_attr((string) $currentSlide) . '"';
    echo ' data-effect="' . esc_attr($sliderEffect) . '"';
    if ($isEditor) echo ' data-items="' . esc_attr((string) $itemsToShow) . '" data-gap="' . esc_attr((string) $gap) . '"';
    if (!$isEditor) echo ' data-height-mode="' . esc_attr($heightMode) . '"';
    if ($emitMobileStack) echo ' data-mobile-stack="1"';
    if ($isImgCarouselMode) {
        echo ' data-mode="' . esc_attr($sliderEffect) . '" data-pause="true" data-items="1" data-gap="0" data-slide-count="' . esc_attr((string) $slideCount) . '"';
        if ($isScrollMode) {
            echo ' data-speed="' . esc_attr((string) $scrollSpd) . '"';
        } else {
            echo ' data-interval="' . esc_attr((string) $autoplaySpeed) . '"';
        }
        if (!$isEditor && in_array($sliderEffect, ['orbit', 'table', 'cube'], true)) {
            echo ' data-tilt-sens="' . esc_attr((string) $tiltSens) . '"';
        }
    }
    echo ' style="' . esc_attr($sliderWrapStyle) . '">';
    if (function_exists('impulse_render_bg_shapes')) {
        impulse_render_bg_shapes($settings, $device, $isEditor);
    }

    $trackHgt      = ($isEditor || $heightMode !== 'auto') ? 'min-height:' . $height . 'px' : 'min-height:' . $height . 'px';
    $trackHgtRule  = $heightMode === 'auto' ? 'min-height:' . $height . 'px' : 'height:' . $height . 'px';
    $trackOverflow = $isImgCarouselMode ? 'visible' : 'hidden';
    $trackStyle    = $isEditor
        ? 'overflow:' . $trackOverflow . ';min-height:' . $height . 'px'
        : 'overflow:' . $trackOverflow . ';' . $trackHgtRule;
    echo '<div class="impulse-carousel-track" style="' . esc_attr($trackStyle) . '">';
    echo '<div class="impulse-carousel-slides" style="' . esc_attr($slidesStyle) . '">';

    // Scroll mode emits multiple copies so marquee never reveals empty track.
    $scrollSets = $isScrollMode && $slideCount > 0 ? max(3, ceil(6 / $slideCount)) : 1;
    for ($set = 0; $set < $scrollSets; $set++) {
        foreach ($slides as $idx => $slide) {
            $bgColor    = (string) ($slide['background'] ?? '');
            $slideId    = (string) ($slide['id'] ?? ('slide-' . $idx));
            $slideValign = (string) ($slide['valign'] ?? 'center');

            // Children for this slide.
            $slideChildren = [];
            foreach ($children as $child) {
                $cs = is_object($child) ? ((array) ($child->settings ?? new stdClass())) : ($child['settings'] ?? []);
                if (is_object($cs)) $cs = (array) $cs;
                if (isset($cs['slide_index']) && intval($cs['slide_index']) === $idx) {
                    $slideChildren[] = $child;
                }
            }
            $slideIsEmpty = empty($slideChildren);
            $realBg       = ($bgColor !== '' && $bgColor !== '#f5f5f5') ? $bgColor : '';
            $slideBgStyle = $realBg !== '' ? 'background:' . $realBg . ';' : '';

            // Frontend skip-empty: live pages don't render slides with nothing in them.
            if (!$isEditor && $slideIsEmpty && $realBg === '') continue;

            $justifyMap = [
                'top'     => 'flex-start', 'center'  => 'center', 'bottom'  => 'flex-end',
                'between' => 'space-between', 'around' => 'space-around', 'evenly' => 'space-evenly',
            ];
            $justifyContent = $justifyMap[$slideValign] ?? 'center';

            $slideHgtRule = ($isEditor || $heightMode === 'auto') ? 'min-height:' . $height . 'px' : 'height:100%';
            $slideSize = $isStackedEffect
                ? 'position:absolute;inset:0;' . ($idx === 0 ? 'opacity:1;z-index:2' : 'opacity:0;z-index:0')
                : ($isEditor ? $slideFlexStyle . $slideHgtRule . ';position:relative'
                             : ($isVertical ? 'flex:0 0 100%;min-height:100%' : 'flex:0 0 100%;min-width:100%') . ';' . $slideHgtRule . ';position:relative');

            // Grid-mode JS rewrites slide cssText, so bg/valign must live on the inner wrapper.
            $outerBgStyle = $isGridMode ? '' : $slideBgStyle;
            $innerBgStyle = $isGridMode ? $slideBgStyle : '';

            $slideClass = 'impulse-carousel-slide' . (($isEditor && $slideIsEmpty) ? ' impulse-carousel-slide-empty' : '');

            echo '<div class="' . esc_attr($slideClass) . '" data-index="' . esc_attr((string) $idx) . '"';
            if ($isEditor) echo ' data-slide-id="' . esc_attr($slideId) . '"';
            echo ' style="' . esc_attr($slideSize . ';' . $outerBgStyle) . '">';

            if ($isEditor) {
                // Editor: dropzone wrapper so empty slides accept drops.
                echo '<div class="impulse-dropzone impulse-new-slider-dropzone" data-parent="' . esc_attr((string) $elId) . '" data-slide="' . esc_attr((string) $idx) . '" style="' . esc_attr('position:relative;width:100%;min-height:' . $height . 'px;display:flex;flex-direction:column;align-items:stretch;justify-content:' . $justifyContent . ';box-sizing:border-box;z-index:2;' . $innerBgStyle) . '">';
                if ($slideIsEmpty) {
                    echo '<span class="impulse-cell-empty-indicator" style="position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:20px;color:rgba(128,128,128,0.45);pointer-events:none;z-index:3;user-select:none">+</span>';
                }
                if (!empty($slideChildren)) {
                    $GLOBALS['impulse_inside_slider'] = true;
                    foreach ($slideChildren as $sc) $renderChild($sc);
                    $GLOBALS['impulse_inside_slider'] = false;
                }
                echo '</div>';
            } else {
                // Frontend: simple content wrapper.
                $innerHgt = $heightMode === 'auto' ? 'min-height:' . $height . 'px' : 'height:100%';
                echo '<div style="' . esc_attr('position:relative;width:100%;' . $innerHgt . ';display:flex;flex-direction:column;align-items:stretch;justify-content:' . $justifyContent . ';box-sizing:border-box;z-index:2;' . $innerBgStyle) . '">';
                $GLOBALS['impulse_inside_slider'] = true;
                foreach ($slideChildren as $sc) $renderChild($sc);
                $GLOBALS['impulse_inside_slider'] = false;
                echo '</div>';
            }

            echo '</div>'; // .impulse-carousel-slide
        }
    }

    echo '</div></div></div>'; // .slides .track .carousel

    // Responsive height @media (frontend only).
    if (!$isEditor && function_exists('wp_add_inline_style')) {
        $tH = intval($settings['height_tablet'] ?? 0);
        $mH = intval($settings['height_mobile'] ?? 0);
        $resp = '';
        $prop = $heightMode === 'auto' ? 'min-height' : 'height';
        if ($tH > 0) $resp .= '@media(max-width:1024px){#' . $carouselId . ' .impulse-carousel-track{' . $prop . ':' . $tH . 'px!important}}';
        if ($mH > 0) $resp .= '@media(max-width:767px){#'  . $carouselId . ' .impulse-carousel-track{' . $prop . ':' . $mH . 'px!important}}';
        if ($resp !== '') wp_add_inline_style('instinctor-page-inline', $resp);
    }

    return ob_get_clean();
}
