<?php
declare(strict_types=1);

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

/**
 * Divider element renderer (shared between frontend and editor).
 *
 * This is the first extraction in the renderer-unification refactor
 * (see docs/renderer-architecture.md). Before this, the divider was
 * implemented twice — once in templates/impulse-page.php and once in
 * admin/views/impulse-preview.php — and the two copies had drifted.
 * Every bug fix had to be applied to both, and behaviour silently
 * diverged when only one was updated.
 *
 * The two pre-extraction copies had several intentional/accidental
 * differences that this function preserves via the $context argument
 * (frontend vs editor). Where the differences are clearly accidental
 * (e.g. divergent defaults: 'saber' vs 'solid'), the FRONTEND value
 * is the canonical one — that's what users see in production. The
 * editor preview switches to match on this commit; the visual change
 * is documented and intentional.
 *
 * Pre-extraction differences and their resolution:
 *   - default style: frontend 'saber' / editor 'solid' → unified to 'saber'
 *   - default thickness: frontend 2 / editor 1 → unified to 2
 *   - default color: frontend '#00aaff' / editor '#e0e0e0' → unified to '#00aaff'
 *   - settings read: frontend direct / editor via impulse_get_responsive
 *     → unified to direct read (editor already resolves per-device via
 *     impulse_resolve_responsive_settings upstream, so impulse_get_responsive
 *     was a no-op on top of that)
 *   - wrapper ID: frontend emits #imp-div-<id> / editor omits → unified
 *     to always emit (needed for the per-device @media targeting in
 *     frontend, harmless in editor)
 *   - per-device @media CSS: frontend emits via impulse_responsive_css /
 *     editor skips → preserved (editor renders per device server-side, so
 *     no @media needed). Gated on $context.
 *
 * Editor-only side effect: emitting per-device @media CSS into
 * `instinctor-page-inline` requires the impulse-page-inline style handle
 * to be registered. That happens in the frontend template's bootstrap;
 * the editor uses the same handle (registered in admin/views/impulse-preview.php)
 * so the call is safe in both contexts as long as we only emit on
 * $context === 'frontend'.
 *
 * @param array|object $el     Element tree node. The frontend passes an array
 *                             (`$el['id']`, `$el['settings']`); the editor
 *                             passes an object (`$el->id`, `$el->settings`).
 *                             This function normalizes both shapes at entry.
 * @param array        $settings Resolved settings. In editor context this is
 *                             already passed through impulse_resolve_responsive_settings;
 *                             in frontend context it is the raw element settings.
 * @param string       $context  'frontend' or 'editor'.
 *
 * @return string Inner HTML (no .impulse-el wrapper).
 *
 * @sideeffect In frontend context, calls wp_add_inline_style() to register
 *             per-device responsive CSS on the 'instinctor-page-inline' handle.
 */
function impulse_element_render_divider($el, array $settings, string $context): string {
    // Normalize $el shape — frontend hands us an array, editor an object.
    // Both pre-extraction renderers used the same field names but different
    // access syntax (`$el['id']` vs `$el->id`). Normalize to array so the
    // rest of the function reads cleanly.
    if (is_object($el)) $el = (array) $el;
    // Read settings. Defaults unified to the FRONTEND values (production
    // reality). Editor preview that previously fell back to different defaults
    // now matches what ships.
    $divStyle      = $settings['style'] ?? 'saber';
    $width         = intval($settings['width'] ?? 100);
    $thickness     = intval($settings['thickness'] ?? 2);
    $color         = $settings['color'] ?? '#00aaff';
    $positionAlign = $settings['position_align'] ?? 'center';
    $label         = trim((string) ($settings['label'] ?? ''));
    $tilt          = max(0, min(30, intval($settings['tilt'] ?? 0)));
    $fadeEdges     = !empty($settings['fade_edges']);
    $orientation   = ($settings['orientation'] ?? 'horizontal') === 'vertical' ? 'vertical' : 'horizontal';
    $isVertical    = ($orientation === 'vertical');

    // Position within the parent flex/block context. The parent is usually
    // `.impulse-container` (flex-direction:column) which has a global
    // `.impulse-container > * { align-self:stretch }` rule — that stretch
    // overrides the divider's width AND consumes any free space the
    // position_align auto-margins were supposed to use. position_align is
    // ALWAYS horizontal (left/center/right of the section) regardless of
    // divider orientation — it shifts the gutter on the cross-axis of the
    // column flex. Emit an explicit `align-self` to escape the stretch
    // rule; auto-margins are kept as a fallback for non-flex parents
    // (cells, columns blocks).
    $_alignSelfMap = ['left' => 'flex-start', 'center' => 'center', 'right' => 'flex-end'];
    $_alignSelf    = $_alignSelfMap[$positionAlign] ?? 'center';
    if ($isVertical) {
        // Vertical: outer is a fixed-width gutter (sized to fit the line's
        // thickness + glow halo). Uses an absolute pixel height because
        // the divider's parent in a flex column has auto height — % of an
        // auto height collapses to ~0px, leaving the divider invisible.
        // container-type:size lets the rotated inner read parent dims via
        // 100cqh / 100cqw.
        $heightPx = max(40, min(2000, intval($settings['height_px'] ?? 200)));
        $vGutter  = max(40, $thickness * 14 + 24);
        // display:block (not inline-block) — auto margins don't resolve on
        // inline-block, so `margin-left:auto` would have collapsed to 0 and
        // the gutter would stay glued to the left edge. Block-level with
        // explicit width respects auto-margin centring/edge-pinning;
        // container-type:size still works on display:block.
        $containerStyle  = 'height:' . $heightPx . 'px;width:' . $vGutter . 'px;max-width:100%;display:block;container-type:size;';
        $containerStyle .= 'align-self:' . $_alignSelf . ';';
        if ($positionAlign === 'left')       $containerStyle .= 'margin-left:0;margin-right:auto;';
        elseif ($positionAlign === 'right')  $containerStyle .= 'margin-left:auto;margin-right:0;';
        else                                 $containerStyle .= 'margin-left:auto;margin-right:auto;';
    } else {
        $containerStyle  = 'width:' . $width . '%;';
        $containerStyle .= 'align-self:' . $_alignSelf . ';';
        if ($positionAlign === 'left')       $containerStyle .= 'margin-left:0;margin-right:auto;';
        elseif ($positionAlign === 'right')  $containerStyle .= 'margin-left:auto;margin-right:0;';
        else                                 $containerStyle .= 'margin-left:auto;margin-right:auto;';
    }

    // Outer is the label's positioning context and the perspective host
    // for the tilted inner.
    $outerStyle = 'position:relative;overflow:visible;' . $containerStyle;
    if ($tilt > 0) $outerStyle .= 'perspective:1200px;';

    // Inner carries the rotate + fade mask so the label (rendered on top)
    // stays readable. For vertical: rotate -90deg with size swapped via
    // container queries so all existing horizontal effects (electric,
    // scan, neon, aurora…) flow vertically without per-effect rewrites.
    if ($isVertical) {
        // Centre the line vertically in the pre-rotation box. Without this,
        // the line sits at the box's top edge (y=0) and after the -90°
        // rotation lands at the LEFT edge of the visible gutter instead of
        // the middle — visible as a divider stuck to the narrow cell's
        // left wall.
        $innerStyle = 'position:absolute;top:50%;left:50%;width:100cqh;height:100cqw;transform:translate(-50%,-50%) rotate(-90deg);transform-origin:center;display:flex;flex-direction:column;justify-content:center;';
    } else {
        $innerStyle = 'position:relative;';
        if ($tilt > 0) $innerStyle .= 'transform:rotateY(' . $tilt . 'deg);transform-origin:center center;';
    }
    if ($fadeEdges) {
        $innerStyle .= '-webkit-mask-image:linear-gradient(90deg,transparent 0%,#000 12%,#000 88%,transparent 100%);'
                     . 'mask-image:linear-gradient(90deg,transparent 0%,#000 12%,#000 88%,transparent 100%);';
    }

    // Unique id for the wrapper. Required by the per-device @media CSS
    // emitted below (frontend context) — without an id, the slider's
    // mobile/tablet overrides would save to DB but never reach the DOM
    // because the inline `width:Npx` on the outer wrapper wins on every
    // device.
    $divId = 'imp-div-' . preg_replace('/[^A-Za-z0-9_-]/', '', (string) ($el['id'] ?? '0'));

    // Build HTML.
    ob_start();
    echo '<div id="' . esc_attr($divId) . '" class="impulse-divider" data-orientation="' . esc_attr($orientation) . '" style="' . esc_attr($outerStyle) . '">';
    echo '<div class="impulse-divider-inner" style="' . esc_attr($innerStyle) . '">';
    // impulse_render_divider_line is currently defined in BOTH renderers
    // with subtly divergent keyframe definitions. That divergence is a
    // separate cleanup queued for after the case-statement extraction is
    // complete. For now, this function relies on whichever copy was loaded
    // by the calling renderer's bootstrap.
    impulse_render_divider_line($divStyle, $thickness, esc_attr($color), $settings);
    echo '</div>';
    if ($label !== '') {
        // Label overlays the centre. Uses CSS var so users can override the
        // backing colour via a custom-CSS section variable (e.g.
        // --impulse-divider-label-bg). Defaults to a near-solid warm white
        // that reads on most page backgrounds.
        $labelStyle = 'position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);'
                    . 'font-size:11px;font-weight:600;letter-spacing:2px;text-transform:uppercase;'
                    . 'padding:4px 14px;border-radius:2px;background:var(--impulse-divider-label-bg,rgba(0,0,0,0.75));'
                    . 'color:var(--impulse-divider-label-fg,#fff);pointer-events:none;white-space:nowrap;z-index:4;';
        echo '<span class="impulse-divider-label" style="' . esc_attr($labelStyle) . '">' . esc_html($label) . '</span>';
    }
    echo '</div>';
    $html = ob_get_clean();

    // Per-device responsive CSS. width applies to horizontal dividers
    // (container-width %); height_px applies to vertical dividers (gutter
    // height in px). thickness/tilt are orientation-agnostic. Only emitted
    // in frontend context — the editor preview reloads its iframe with a
    // device=mobile|tablet|desktop query parameter, so the renderer is
    // called fresh per device and resolves the settings server-side.
    if ($context === 'frontend' && function_exists('impulse_responsive_css') && function_exists('wp_add_inline_style')) {
        $divRespMap = [
            'thickness' => ['--impulse-divider-thickness', 'px'],
            'tilt'      => function ($v) {
                $vi = max(0, min(30, intval($v)));
                return $vi > 0 ? 'transform:rotateY(' . $vi . 'deg);' : 'transform:none;';
            },
        ];
        if ($isVertical) {
            $divRespMap['height_px'] = ['height', 'px'];
        } else {
            $divRespMap['width'] = ['width', '%'];
        }
        $divResp = impulse_responsive_css($settings, '#' . $divId, $divRespMap);
        if ($divResp) wp_add_inline_style('instinctor-page-inline', $divResp);
    }

    return $html;
}
