<?php
if (!defined('ABSPATH')) exit;

/**
 * SSR: aria-label, ce_pd_render_block (Frontend-HTML) und has_block-Helfer.
 *
 * Layouts: straight, stacked, pie, pieStairs, wave, pyramid. Add-on-Plugins koennen die Layout-Liste
 * über den Filter `ce_pd_allowed_layouts` und das `$config`-Array über
 * `ce_pd_render_config` erweitern.
 */

/**
 * Kurze, für Screenreader verständliche Beschreibung des Diagrammtyps.
 *
 * @param string $layout     straight|stacked|pie (kann via Filter erweitert sein)
 * @param string $projection 2d|3d
 */
function ce_pd_diagram_region_aria_label($layout, $projection = '3d') {
    $is_2d = $projection === '2d';

    $custom = apply_filters('ce_pd_diagram_region_aria_label', '', $layout, $projection);
    if (is_string($custom) && $custom !== '') {
        return $custom;
    }

    switch ($layout) {
        case 'stacked':
            return $is_2d
                ? __('Stacked column chart, flat 2D view. Hover or tap bars for details.', 'pop3d-charts')
                : __('Stacked column chart, 3D view. Hover or tap bars for details.', 'pop3d-charts');
        case 'pie':
        case 'pieStairs':
            return $is_2d
                ? __('Pie chart, flat 2D view. Hover or tap slices for details.', 'pop3d-charts')
                : __('Pie chart, 3D view. Hover or tap slices for details.', 'pop3d-charts');
        case 'wave':
            return $is_2d
                ? __('Wave chart, flat 2D view. Hover or tap points for details.', 'pop3d-charts')
                : __('Wave chart, 3D view. Hover or tap points for details.', 'pop3d-charts');
        case 'pyramid':
            return __('Pyramid infographic, 3D view. Hover or tap segments for details.', 'pop3d-charts');
        case 'straight':
        default:
            return $is_2d
                ? __('Column bar chart, flat 2D view. Hover or tap bars for details.', 'pop3d-charts')
                : __('Column bar chart, 3D view. Hover or tap bars for details.', 'pop3d-charts');
    }
}

function ce_pd_render_block($attributes, $content, $block = null) {
    if (!defined('CE_PD_BLOCK_RENDERED')) define('CE_PD_BLOCK_RENDERED', true);
    if (!is_array($attributes)) {
        $attributes = [];
    }

    $bars_raw = $attributes['bars'] ?? [];

    $free_layouts = ['straight', 'stacked', 'pie', 'pieStairs', 'wave', 'pyramid'];
    $allowed_layouts = apply_filters('ce_pd_allowed_layouts', $free_layouts);
    if (!is_array($allowed_layouts) || $allowed_layouts === []) {
        $allowed_layouts = $free_layouts;
    }
    $raw_layout = isset($attributes['defaultLayout']) ? (string) $attributes['defaultLayout'] : '';
    $layout     = in_array($raw_layout, $allowed_layouts, true) ? $raw_layout : 'straight';

    $max_bars = 1000;
    $bars     = ce_pd_sanitize_bars(array_slice($bars_raw, 0, $max_bars));
    $sort_order = in_array($attributes['sortOrder'] ?? '', ['value_asc', 'value_desc', 'headline_asc'], true) ? $attributes['sortOrder'] : 'custom';

    $grid_enabled     = $attributes['gridEnabled'] ?? true;
    $grid_line_color  = sanitize_hex_color($attributes['gridLineColor'] ?? '') ?: '#dcdcdc';
    $bg_type          = ce_pd_sanitize_background_type($attributes['backgroundType'] ?? 'grid');
    $bg_transparent   = $attributes['backgroundTransparent'] ?? false;
    $bg_color         = ce_pd_sanitize_hex_color_default($attributes['backgroundColor'] ?? '', '#f8f9fa');
    $gradient_floor   = ce_pd_sanitize_hex_color_default($attributes['gradientFloorColor'] ?? '', '#c0c1c2');
    $projection       = ($attributes['defaultProjection'] ?? '') === '2d' ? '2d' : '3d';
    /* Stairs-Donut: reines 3D-Layout (liegende Treppenform) — Projektion fix 3D. */
    if ('pieStairs' === $layout) {
        $projection = '3d';
    }

    $camera_preset_raw   = isset($attributes['cameraPreset']) ? (string) $attributes['cameraPreset'] : 'default';
    $camera_preset_allow = ['default', 'frog_right', 'elevated_right', 'frontal', 'elevated_left', 'frog_left'];
    $camera_preset       = in_array($camera_preset_raw, $camera_preset_allow, true) ? $camera_preset_raw : 'default';

    /* Start-Zoom = initialer userZoomFactorPersp (kleiner = näher). Range 0.5–2. */
    $start_zoom = isset($attributes['startZoom']) && is_numeric($attributes['startZoom'])
        ? (float) $attributes['startZoom']
        : 1.0;
    if (!is_finite($start_zoom) || $start_zoom <= 0) {
        $start_zoom = 1.0;
    }
    $start_zoom = min(2.0, max(0.5, $start_zoom));

    $always_colorful   = !empty($attributes['alwaysColorful']);
    $free_palettes     = ['vivid', 'theme', 'oceanSunset', 'purpleRaindrops', 'spectrum', 'magentaDream', 'twilight', 'accessible', 'material', 'corporate', 'monochrome', 'diverging'];
    $color_scheme_raw  = isset($attributes['colorScheme']) ? (string) $attributes['colorScheme'] : 'vivid';
    $allowed_palettes  = apply_filters('ce_pd_allowed_color_schemes', $free_palettes);
    if (!is_array($allowed_palettes) || $allowed_palettes === []) {
        $allowed_palettes = $free_palettes;
    }
    $color_scheme      = in_array($color_scheme_raw, $allowed_palettes, true) ? $color_scheme_raw : 'vivid';
    $default_data_color = sanitize_hex_color($attributes['defaultDataColor'] ?? '');
    if (!is_string($default_data_color)) {
        $default_data_color = '';
    }

    $ruler_enabled               = $attributes['rulerEnabled'] ?? true;
    $ruler_color_input           = isset($attributes['rulerColor']) ? (string) $attributes['rulerColor'] : '';
    $ruler_color_san             = sanitize_hex_color($ruler_color_input);
    $ruler_color_attr_for_config = $ruler_color_san ? $ruler_color_san : '';
    $ruler_color                 = $ruler_color_attr_for_config ?: '#858585';
    $ruler_style_raw             = isset($attributes['rulerStyle']) ? (string) $attributes['rulerStyle'] : '';
    $ruler_style                 = in_array($ruler_style_raw, ['softModern', 'dotGrid', 'tickBands', 'stripeOnly', 'caliper', 'gradient'], true) ? $ruler_style_raw : 'default';
    $bar_label_color_raw         = sanitize_hex_color($attributes['barLabelColor'] ?? '');
    $group_label_color_raw       = sanitize_hex_color($attributes['groupLabelColor'] ?? '');
    $bar_label_color             = $bar_label_color_raw ?: $ruler_color;
    $group_label_color           = $group_label_color_raw ?: $ruler_color;

    $interaction        = $attributes['interactionEnabled'] ?? true;
    $axis_y             = ce_pd_sanitize_axis_y($attributes['axisY'] ?? []);
    $diagram_max_by_bp  = ce_pd_sanitize_diagram_max_height_by_breakpoint($attributes['diagramMaxHeightByBreakpoint'] ?? null);
    $diagram_max_legacy = ce_pd_sanitize_diagram_max_height($attributes['diagramMaxHeight'] ?? '');
    $axis_x_label       = ce_pd_sanitize_axis_x_label($attributes['axisXLabel'] ?? '');
    $axis_y_label       = ce_pd_sanitize_axis_x_label($attributes['axisYLabel'] ?? '');

    $bar_thickness = isset($attributes['barThicknessScale']) ? (float) $attributes['barThicknessScale'] : 1.0;
    if (!is_finite($bar_thickness)) {
        $bar_thickness = 1.0;
    }
    $bar_thickness = min(1.7, max(0.4, $bar_thickness));

    $bar_opacity = isset($attributes['barOpacity']) ? (float) $attributes['barOpacity'] : 1.0;
    if (!is_finite($bar_opacity)) {
        $bar_opacity = 1.0;
    }
    $bar_opacity = min(1.0, max(0.1, $bar_opacity));

    $data_label_font_scale = isset($attributes['dataLabelFontScale']) ? (float) $attributes['dataLabelFontScale'] : 1.0;
    if (!is_finite($data_label_font_scale)) {
        $data_label_font_scale = 1.0;
    }
    $data_label_font_scale = min(2.0, max(0.5, $data_label_font_scale));

    $labels_under_bars    = !empty($attributes['labelsUnderBars']);
    $straight_bar_slots   = ce_pd_resolve_straight_bar_labels($attributes);
    $pie_slice_label      = ce_pd_resolve_pie_slice_label($attributes);
    $pie_angle_basis      = ce_pd_resolve_pie_angle_basis($attributes);
    $pie_inner_radius_raw = isset($attributes['pieInnerRadius']) ? (float) $attributes['pieInnerRadius'] : 0.5;
    $pie_inner_radius     = is_finite($pie_inner_radius_raw) ? min(0.85, max(0.0, $pie_inner_radius_raw)) : 0.5;

    $pie_thickness_raw = isset($attributes['pieThickness']) && is_numeric($attributes['pieThickness'])
        ? (float) $attributes['pieThickness']
        : 1.0;
    $pie_thickness     = is_finite($pie_thickness_raw) ? min(5.0, max(0.5, $pie_thickness_raw)) : 1.0;

    $circle_label_style_raw  = isset($attributes['circleLabelStyle']) ? (string) $attributes['circleLabelStyle'] : '';
    $circle_label_style      = in_array($circle_label_style_raw, ['shadow', 'pill', 'simple'], true) ? $circle_label_style_raw : 'simple';
    $pie_label_auto_contrast = !array_key_exists('pieLabelAutoContrast', $attributes)
        ? true
        : (bool) $attributes['pieLabelAutoContrast'];
    $pie_callout_line_length = isset($attributes['pieCalloutLineLength']) && is_numeric($attributes['pieCalloutLineLength'])
        ? (float) $attributes['pieCalloutLineLength']
        : 0.25;
    $pie_callout_line_length = min(0.6, max(0.1, $pie_callout_line_length));
    $pie_callout_font_scale  = isset($attributes['pieCalloutFontScale']) && is_numeric($attributes['pieCalloutFontScale'])
        ? (float) $attributes['pieCalloutFontScale']
        : 1.0;
    $pie_callout_font_scale  = min(3.0, max(0.3, $pie_callout_font_scale));
    $pie_callout_color       = sanitize_hex_color($attributes['pieCalloutColor'] ?? '') ?: '';

    $stair_step_height = isset($attributes['stairStepHeight']) && is_numeric($attributes['stairStepHeight'])
        ? (float) $attributes['stairStepHeight']
        : 0.18;
    $stair_step_height = min(0.8, max(0.0, $stair_step_height));

    $pyramid_slice_label = ce_pd_resolve_pyramid_slice_label($attributes);
    $pyramid_segment_gap = isset($attributes['pyramidSegmentGap']) && is_numeric($attributes['pyramidSegmentGap'])
        ? (float) $attributes['pyramidSegmentGap']
        : 0.0;
    $pyramid_segment_gap = min(0.08, max(0.0, $pyramid_segment_gap));
    $pyramid_segment_opacity = 1.0;
    $pyramid_inverted = !empty($attributes['pyramidInverted']);
    $pyramid_callout_line_length = isset($attributes['pyramidCalloutLineLength']) && is_numeric($attributes['pyramidCalloutLineLength'])
        ? (float) $attributes['pyramidCalloutLineLength']
        : 0.25;
    $pyramid_callout_line_length = min(0.6, max(0.1, $pyramid_callout_line_length));
    $pyramid_callout_font_scale = isset($attributes['pyramidCalloutFontScale']) && is_numeric($attributes['pyramidCalloutFontScale'])
        ? (float) $attributes['pyramidCalloutFontScale']
        : 1.0;
    $pyramid_callout_font_scale = min(2.0, max(0.5, $pyramid_callout_font_scale));
    $pyramid_callout_color = sanitize_hex_color($attributes['pyramidCalloutColor'] ?? '') ?: '';
    $pyramid_label_auto_contrast = !array_key_exists('pyramidLabelAutoContrast', $attributes)
        ? true
        : (bool) $attributes['pyramidLabelAutoContrast'];
    $pyramid_annotation = !empty($attributes['pyramidAnnotation'])
        || !empty($attributes['pyramidAnnotationLeft'])
        || !empty($attributes['pyramidAnnotationRight']);
    $pyramid_annotation_line_length = isset($attributes['pyramidAnnotationLineLength']) && is_numeric($attributes['pyramidAnnotationLineLength'])
        ? (float) $attributes['pyramidAnnotationLineLength']
        : 0.25;
    $pyramid_annotation_line_length = min(0.6, max(0.1, $pyramid_annotation_line_length));
    $pyramid_annotation_font_scale = isset($attributes['pyramidAnnotationFontScale']) && is_numeric($attributes['pyramidAnnotationFontScale'])
        ? (float) $attributes['pyramidAnnotationFontScale']
        : 1.0;
    $pyramid_annotation_font_scale = min(2.0, max(0.5, $pyramid_annotation_font_scale));

    $viewer_sort_enabled    = !empty($attributes['viewerSortEnabled']);
    $legend_enabled         = array_key_exists('legendEnabled', $attributes) ? (bool) $attributes['legendEnabled'] : true;
    $legend_visible_default = array_key_exists('legendVisibleDefault', $attributes) ? (bool) $attributes['legendVisibleDefault'] : true;
    $legend_position_raw    = isset($attributes['legendPosition']) ? $attributes['legendPosition'] : 'left';
    $legend_position        = in_array($legend_position_raw, ['left', 'bottom', 'hidden'], true) ? $legend_position_raw : 'left';
    $legend_text_color_raw  = sanitize_hex_color($attributes['legendTextColor'] ?? '') ?: '';
    $legend_text_color      = $legend_text_color_raw
        ? $legend_text_color_raw
        : (empty($bg_transparent) ? ce_pd_contrast_text_color($bg_color) : '');
    $legend_outside_chart   = !empty($attributes['legendOutsideChart']) && 'bottom' === $legend_position;
    $viewer_png_download    = !empty($attributes['viewerPngDownloadEnabled']);

    $chart_title    = sanitize_text_field($attributes['chartTitle'] ?? '');
    $chart_subtitle = sanitize_text_field($attributes['chartSubtitle'] ?? '');
    $chart_footnote = sanitize_text_field($attributes['chartFootnote'] ?? '');
    $header_align_raw = $attributes['headerAlign'] ?? 'left';
    $header_align     = in_array($header_align_raw, ['left', 'center', 'right'], true) ? $header_align_raw : 'left';
    $header_text_color_san = isset($attributes['headerTextColor']) && is_string($attributes['headerTextColor'])
        ? sanitize_hex_color($attributes['headerTextColor'])
        : false;
    $header_text_user = (is_string($header_text_color_san) && $header_text_color_san) ? $header_text_color_san : '';
    $header_color     = ($header_text_user !== '')
        ? $header_text_user
        : (empty($bg_transparent) ? ce_pd_contrast_text_color($bg_color) : '');

    $bar_corner_radius = isset($attributes['barCornerRadius']) ? (float) $attributes['barCornerRadius'] : 1.0;
    if (!is_finite($bar_corner_radius)) {
        $bar_corner_radius = 1.0;
    }
    $bar_gradient = !empty($attributes['barGradientEnabled']);
    $bar_corner_radius = min(1.0, max(0.0, $bar_corner_radius));

    $scene_y_scale = isset($attributes['sceneYScale']) ? (float) $attributes['sceneYScale'] : 1.0;
    if (!is_finite($scene_y_scale)) {
        $scene_y_scale = 1.0;
    }
    $scene_y_scale = min(3.0, max(0.2, $scene_y_scale));

    if (isset($attributes['seriesAxisXLabelTransform']) && is_array($attributes['seriesAxisXLabelTransform'])) {
        $series_axis_x_label_transform = $attributes['seriesAxisXLabelTransform'];
    } else {
        $series_axis_x_label_transform = null;
    }

    $config = [
        'bars'                  => $bars,
        'sortOrder'             => $sort_order,
        'gridEnabled'           => $grid_enabled,
        'gridLineColor'         => $grid_line_color,
        'backgroundType'        => $bg_type,
        'backgroundTransparent' => $bg_transparent,
        'backgroundColor'       => $bg_color,
        'gradientFloorColor'    => $gradient_floor,
        'defaultLayout'         => $layout,
        'defaultProjection'     => $projection,
        'cameraPreset'          => $camera_preset,
        'startZoom'             => $start_zoom,
        'alwaysColorful'        => $always_colorful,
        'colorScheme'           => $color_scheme,
        'themePalette'          => ce_pd_get_theme_palette_hex(),
        'ignoreBarColors'       => !empty($attributes['ignoreBarColors']),
        'defaultDataColor'      => $default_data_color,
        'rulerEnabled'          => $ruler_enabled,
        'rulerColor'            => $ruler_color,
        'rulerColorAttr'        => $ruler_color_attr_for_config,
        'rulerStyle'            => $ruler_style,
        'barLabelColor'         => $bar_label_color,
        'groupLabelColor'       => $group_label_color,

        'interactionEnabled'    => $interaction,
        'axisY'                 => $axis_y,
        'axisXLabel'            => $axis_x_label,
        'axisYLabel'            => $axis_y_label,
        'diagramMaxHeightByBreakpoint' => $diagram_max_by_bp,
        'diagramMaxHeightLegacy'       => $diagram_max_legacy,
        'barThicknessScale'     => $bar_thickness,
        'barOpacity'            => $bar_opacity,
        'dataLabelFontScale'    => $data_label_font_scale,
        'pieSliceLabel'         => $pie_slice_label,
        'pieAngleBasis'         => $pie_angle_basis,
        'pieInnerRadius'        => $pie_inner_radius,
        'pieThickness'          => $pie_thickness,
        'circleLabelStyle'      => $circle_label_style,
        'pieLabelAutoContrast'  => $pie_label_auto_contrast,
        'pieCalloutLineLength'  => $pie_callout_line_length,
        'pieCalloutFontScale'   => $pie_callout_font_scale,
        'pieCalloutColor'       => $pie_callout_color,
        'stairStepHeight'       => $stair_step_height,
        'labelsUnderBars'         => $labels_under_bars,
        'straightBarLabelAbove'   => $straight_bar_slots['above'],
        'straightBarLabelBelow'   => $straight_bar_slots['below'],
        'stackedBarSumAbove'      => ce_pd_resolve_stacked_bar_sum_above($attributes, $layout),
        'legendEnabled'         => (bool) $legend_enabled,
        'legendVisibleDefault'  => (bool) $legend_visible_default,
        'legendPosition'        => $legend_position,
        'legendOutsideChart'    => $legend_outside_chart,
        'legendTextColor'       => $legend_text_color,
        'numberLocale'          => ce_pd_normalize_bcp47_locale(determine_locale()),
        'i18nSource'            => __('Source:', 'pop3d-charts'),
        'i18nLoadError'         => __('Diagram could not be loaded. Please check your connection or try again later.', 'pop3d-charts'),
        'i18nContextLost'       => __('WebGL context was lost. The diagram will recover automatically.', 'pop3d-charts'),
        'i18nTapToInteract'     => __('Tap to interact', 'pop3d-charts'),
        'i18nFullscreenEnter'   => __('Enter fullscreen', 'pop3d-charts'),
        'i18nFullscreenExit'    => __('Exit fullscreen', 'pop3d-charts'),
        'i18nDataTableShow'     => __('Show data table', 'pop3d-charts'),
        'i18nDataTableClose'    => __('Close', 'pop3d-charts'),
        'i18nDataTableCaption'  => __('Diagram data', 'pop3d-charts'),
        'i18nDataTableColValue' => __('Value', 'pop3d-charts'),
        'i18nDataTableColTitle' => __('Title', 'pop3d-charts'),
        'i18nDataTableColDescription' => __('Description', 'pop3d-charts'),
        'i18nDataTableColSmallPrint' => __('Note', 'pop3d-charts'),
        'i18nDataTableColColor' => __('Color', 'pop3d-charts'),
        'i18nDataTableColGroup' => __('X group', 'pop3d-charts'),
        'i18nDataTableSearch'   => __('Search…', 'pop3d-charts'),
        'i18nLegendShow'        => __('Show legend', 'pop3d-charts'),
        'i18nLegendHide'        => __('Hide legend', 'pop3d-charts'),
        'viewerSortEnabled'            => $viewer_sort_enabled,
        'viewerSortGlyphUrl'           => ce_pd_asset_url('ce-diagram-viewer-sort-icon.svg'),
        'i18nViewerSortLabel'          => __('Sort order', 'pop3d-charts'),
        'i18nViewerSortAria'           => __('Sort chart rows', 'pop3d-charts'),
        'i18nSortCustom'               => __('Custom (manual)', 'pop3d-charts'),
        'i18nSortValueAsc'             => __('Value ascending', 'pop3d-charts'),
        'i18nSortValueDesc'            => __('Value descending', 'pop3d-charts'),
        'i18nSortHeadlineAsc'          => __('Label A–Z', 'pop3d-charts'),
        'viewerPngDownloadEnabled'     => (bool) $viewer_png_download,
        'i18nPngDownload'              => __('PNG Download', 'pop3d-charts'),
        'headerAlign'                  => $header_align,
        'headerTextColor'              => $header_text_user,
        'barCornerRadius'              => $bar_corner_radius,
        'barGradientEnabled'           => $bar_gradient,
        'sceneYScale'                  => $scene_y_scale,

        'seriesAxisXLabelTransform'    => $series_axis_x_label_transform,
        'waveStyle'                    => (isset($attributes['waveStyle']) && $attributes['waveStyle'] === 'line') ? 'line' : 'ribbon',
        'waveRibbonDepth'              => isset($attributes['waveRibbonDepth']) ? max(0.05, min(1.5, (float) $attributes['waveRibbonDepth'])) : 0.08,
        'waveRibbonOpacity'            => isset($attributes['waveRibbonOpacity']) ? max(0.1, min(1, (float) $attributes['waveRibbonOpacity'])) : 1,
        'waveAreaFill'                 => array_key_exists('waveAreaFill', $attributes) ? (bool) $attributes['waveAreaFill'] : true,
        'waveAreaFillOpacity'          => isset($attributes['waveAreaFillOpacity']) ? max(0, min(1, (float) $attributes['waveAreaFillOpacity'])) : 0.35,
        'waveBaselineValue'            => ce_pd_sanitize_wave_baseline_value($attributes['waveBaselineValue'] ?? 0),
        'waveGradientDirection'        => ce_pd_sanitize_wave_gradient_direction($attributes['waveGradientDirection'] ?? 'horizontal'),
        'waveLineThickness'            => isset($attributes['waveLineThickness']) ? max(0.2, min(2, (float) $attributes['waveLineThickness'])) : 1,
        'waveSmoothness'               => isset($attributes['waveSmoothness']) ? max(0, min(1, (float) $attributes['waveSmoothness'])) : 0.5,
        'waveShowPoints'               => array_key_exists('waveShowPoints', $attributes) ? (bool) $attributes['waveShowPoints'] : true,
        'wavePointSize'                => isset($attributes['wavePointSize']) ? max(0.3, min(3, (float) $attributes['wavePointSize'])) : 1,
        'waveMultiSeries'              => array_key_exists('waveMultiSeries', $attributes) ? (bool) $attributes['waveMultiSeries'] : true,
        'waveLabelAtNodes'             => ce_pd_resolve_label_mode($attributes['waveLabelAtNodes'] ?? '', ['off', 'value', 'headline'], 'off'),
        'waveLabelAlongFloor'          => ce_pd_resolve_label_mode($attributes['waveLabelAlongFloor'] ?? '', ['off', 'value', 'headline'], 'off'),
        'waveAnnotationLineLength'     => isset($attributes['waveAnnotationLineLength']) ? max(0.1, min(0.6, (float) $attributes['waveAnnotationLineLength'])) : 0.25,
        'waveAnnotationFontScale'      => isset($attributes['waveAnnotationFontScale']) ? max(0.5, min(2, (float) $attributes['waveAnnotationFontScale'])) : 1,
        'pyramidSegmentGap'            => $pyramid_segment_gap,
        'pyramidSegmentOpacity'        => $pyramid_segment_opacity,
        'pyramidInverted'              => $pyramid_inverted,
        'pyramidSliceLabel'            => $pyramid_slice_label,
        'pyramidCalloutLineLength'     => $pyramid_callout_line_length,
        'pyramidCalloutFontScale'      => $pyramid_callout_font_scale,
        'pyramidCalloutColor'          => $pyramid_callout_color,
        'pyramidLabelAutoContrast'     => $pyramid_label_auto_contrast,
        'pyramidAnnotation'            => $pyramid_annotation,
        'pyramidAnnotationLineLength'  => $pyramid_annotation_line_length,
        'pyramidAnnotationFontScale'   => $pyramid_annotation_font_scale,
    ];

    /**
     * Erweiterungs-Hook: Add-ons können Konfigurations-Felder anfügen.
     */
    $config = apply_filters('ce_pd_render_config', $config, $attributes, $layout);

    $block_id = 'ce-diagram-' . wp_unique_id();

    $ssr_min_h_vars = [ 'desktop' => '--ce-ssr-min-h-d', 'tablet' => '--ce-ssr-min-h-t', 'mobile' => '--ce-ssr-min-h-m' ];
    $wrapper_style_parts = [];
    $has_ssr_min_h       = false;
    foreach ( ['desktop', 'tablet', 'mobile'] as $bp_key ) {
        $entry = $diagram_max_by_bp[ $bp_key ];
        $val   = trim( (string) ( $entry['value'] ?? '' ) );
        if ( $val === '' ) {
            continue;
        }
        $n = (float) $val;
        if ( $n <= 0 || $n > 9999 ) {
            continue;
        }
        $css_unit = ( $entry['unit'] ?? 'px' ) === '%' ? 'vh' : ( ( $entry['unit'] ?? 'px' ) === 'em' ? 'em' : 'px' );
        $css_val  = $n . $css_unit;
        $wrapper_style_parts[] = $ssr_min_h_vars[ $bp_key ] . ':' . esc_attr( $css_val );
        $has_ssr_min_h         = true;
    }

    $aspect_hint_raw = isset( $attributes['aspectHint'] ) ? floatval( $attributes['aspectHint'] ) : 0;
    $aspect_hint     = ( is_finite( $aspect_hint_raw ) && $aspect_hint_raw > 0.1 ) ? min( 10.0, $aspect_hint_raw ) : 0;

    $wrapper_class = 'ce-diagram-wrapper'
        . ( $bg_transparent ? ' ce-diagram-wrapper--transparent-bg' : '' )
        . ( 'bottom' === $legend_position ? ' ce-diagram-wrapper--legend-bottom' : '' )
        . ( $legend_outside_chart ? ' ce-diagram-wrapper--legend-external' : '' )
        . ( $has_ssr_min_h ? ' ce-diagram-wrapper--ssr-min-h' : '' );

    $wrapper_attrs = [
        'id'                      => $block_id,
        'class'                   => $wrapper_class,
        'data-config'             => wp_json_encode($config),
        'data-ruler-color'       => $ruler_color,
        'data-bar-label-color'   => $bar_label_color,
        'data-group-label-color' => $group_label_color,
        'role'                    => 'region',
        'aria-label'              => ce_pd_diagram_region_aria_label($layout, $projection),
    ];
    if ( $aspect_hint > 0.1 ) {
        $wrapper_attrs['data-aspect-hint'] = number_format( $aspect_hint, 2, '.', '' );
        $wrapper_style_parts[]             = 'aspect-ratio:' . number_format( $aspect_hint, 4, '.', '' );
    }
    if ( $wrapper_style_parts ) {
        $wrapper_attrs['style'] = implode( ';', $wrapper_style_parts );
    }

    $projection_toggle_enabled = !array_key_exists('viewerProjectionToggleEnabled', $attributes)
        || (bool) $attributes['viewerProjectionToggleEnabled'];
    $projection_toggleable = $projection_toggle_enabled;
    /* Stairs-Donut: reines 3D-Layout, kein 2D/3D-Umschalter. */
    if ('pieStairs' === $layout) {
        $projection_toggleable = false;
    }
    $projection_toggleable = (bool) apply_filters('ce_pd_layout_projection_toggleable', $projection_toggleable, $layout);
    $initial_is_2d         = $projection === '2d';

    ob_start();
    ?>
    <div <?php
        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Core API returns escaped HTML attributes for the wrapper.
        echo get_block_wrapper_attributes($wrapper_attrs, $block);
    ?>>
        <?php
        $hf_style_parts = [];
        if ( $header_color ) $hf_style_parts[] = '--ce-header-color:' . esc_attr( $header_color );
        if ( $header_align !== 'left' ) $hf_style_parts[] = '--ce-header-align:' . esc_attr( $header_align );
        $hf_style = $hf_style_parts ? ' style="' . implode( ';', $hf_style_parts ) . '"' : '';
        ?>
        <?php if ( $chart_title !== '' || $chart_subtitle !== '' ) : ?>
        <div class="ce-diagram-header"<?php
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- style="" built from esc_attr() CSS variables only.
            echo $hf_style;
        ?>>
            <?php if ( $chart_title !== '' ) : ?><div class="ce-diagram-chart-title"><?php echo esc_html( $chart_title ); ?></div><?php endif; ?>
            <?php if ( $chart_subtitle !== '' ) : ?><div class="ce-diagram-chart-subtitle"><?php echo esc_html( $chart_subtitle ); ?></div><?php endif; ?>
        </div>
        <?php endif; ?>
        <div class="ce-diagram-loader" aria-live="polite" aria-busy="true">
            <div class="ce-diagram-loader-bar"></div>
        </div>
        <div class="ce-diagram-inner">
            <?php if ( $legend_enabled && 'hidden' !== $legend_position && ! $legend_outside_chart ) : ?>
                <aside class="ce-diagram-legend"
                       aria-label="<?php echo esc_attr( __( 'Legend', 'pop3d-charts' ) ); ?>"
                       data-ce-legend-aside="1"<?php echo $legend_visible_default ? '' : ' hidden'; ?><?php echo $legend_text_color ? ' style="--ce-legend-color:' . esc_attr( $legend_text_color ) . '"' : ''; ?>>
                </aside>
            <?php endif; ?>
            <div class="pop3d-charts" aria-hidden="true"></div>
            <div class="ce-diagram-icon-wrapper" role="group" aria-label="<?php esc_attr_e('Controls', 'pop3d-charts'); ?>">
                <div class="ce-diagram-icon-tooltip" role="tooltip">
                    <p><strong><?php esc_html_e('Scroll / pinch gesture', 'pop3d-charts'); ?></strong> <span class="ce-diagram-icon-tooltip-hint"><?php esc_html_e('Zoom in and out', 'pop3d-charts'); ?></span></p>
                    <p><strong><?php esc_html_e('Left mouse click drag', 'pop3d-charts'); ?></strong> <span class="ce-diagram-icon-tooltip-hint"><?php esc_html_e('Rotate the diagram', 'pop3d-charts'); ?></span></p>
                    <p><strong><?php esc_html_e('Right mouse click drag', 'pop3d-charts'); ?></strong> <span class="ce-diagram-icon-tooltip-hint"><?php esc_html_e('Pan camera position', 'pop3d-charts'); ?></span></p>
                    <p><strong><?php esc_html_e('Click / tap', 'pop3d-charts'); ?></strong> <span class="ce-diagram-icon-tooltip-hint"><?php esc_html_e('Show details on elements', 'pop3d-charts'); ?></span></p>
                </div>
                <img class="ce-diagram-icon"
                     src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-interaction-hint-icon.svg' ) ); ?>"
                     width="58"
                     height="17"
                     alt="<?php echo esc_attr__( 'Scroll and drag with mouse or tap with finger', 'pop3d-charts' ); ?>"
                     decoding="async"
                     draggable="false"/>
            </div>
        </div>
        <?php if ( $legend_enabled && $legend_outside_chart ) : ?>
            <aside class="ce-diagram-legend"
                   aria-label="<?php echo esc_attr( __( 'Legend', 'pop3d-charts' ) ); ?>"
                   data-ce-legend-aside="1"<?php echo $legend_visible_default ? '' : ' hidden'; ?><?php echo $legend_text_color ? ' style="--ce-legend-color:' . esc_attr( $legend_text_color ) . '"' : ''; ?>>
            </aside>
        <?php endif; ?>
        <div class="ce-diagram-top-actions">
            <button type="button"
                    class="ce-diagram-expand-btn ce-diagram-fullscreen-btn"
                    aria-pressed="false"
                    aria-label="<?php echo esc_attr__('Enter fullscreen', 'pop3d-charts'); ?>"
                    title="<?php echo esc_attr__('Enter fullscreen', 'pop3d-charts'); ?>">
                <span class="ce-diagram-expand-btn__label" aria-hidden="true"><?php echo esc_html__('Enter fullscreen', 'pop3d-charts'); ?></span>
                <span class="ce-diagram-expand-btn__icon" aria-hidden="true">
                    <img class="ce-diagram-fullscreen-icon ce-diagram-fullscreen-icon--enter" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-fullscreen-enter-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                    <img class="ce-diagram-fullscreen-icon ce-diagram-fullscreen-icon--exit" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-fullscreen-exit-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                </span>
            </button>
            <?php if ( $legend_enabled && 'hidden' !== $legend_position ) : ?>
            <button type="button"
                    class="ce-diagram-expand-btn ce-diagram-legend-toggle-btn"
                    aria-pressed="<?php echo $legend_visible_default ? 'true' : 'false'; ?>"
                    aria-label="<?php echo $legend_visible_default ? esc_attr__( 'Hide legend', 'pop3d-charts' ) : esc_attr__( 'Show legend', 'pop3d-charts' ); ?>"
                    title="<?php echo $legend_visible_default ? esc_attr__( 'Hide legend', 'pop3d-charts' ) : esc_attr__( 'Show legend', 'pop3d-charts' ); ?>">
                <span class="ce-diagram-expand-btn__label" aria-hidden="true"><?php echo esc_html__( 'Legend', 'pop3d-charts' ); ?></span>
                <span class="ce-diagram-expand-btn__icon" aria-hidden="true">
                    <img class="ce-diagram-legend-icon" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-legend-toggle-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                </span>
            </button>
            <?php endif; ?>
            <button type="button" class="ce-diagram-expand-btn ce-diagram-data-table-btn">
                <span class="ce-diagram-expand-btn__label" aria-hidden="true"><?php echo esc_html__('Show data table', 'pop3d-charts'); ?></span>
                <span class="ce-diagram-expand-btn__icon" aria-hidden="true">
                    <img class="ce-diagram-data-table-icon" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-data-table-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                </span>
            </button>
            <?php if ( $viewer_png_download ) : ?>
            <button type="button"
                    class="ce-diagram-expand-btn ce-diagram-png-download-btn"
                    aria-label="<?php echo esc_attr__( 'PNG Download', 'pop3d-charts' ); ?>"
                    title="<?php echo esc_attr__( 'PNG Download', 'pop3d-charts' ); ?>">
                <span class="ce-diagram-expand-btn__label" aria-hidden="true"><?php echo esc_html__( 'PNG Download', 'pop3d-charts' ); ?></span>
                <span class="ce-diagram-expand-btn__icon" aria-hidden="true">
                    <img class="ce-diagram-png-download-icon" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-png-download-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                </span>
            </button>
            <?php endif; ?>
            <?php if ( $projection_toggleable ) : ?>
            <button type="button"
                    class="ce-diagram-expand-btn ce-diagram-projection-btn"
                    aria-pressed="<?php echo $initial_is_2d ? 'true' : 'false'; ?>"
                    aria-label="<?php echo $initial_is_2d ? esc_attr__( 'Switch to 3D view', 'pop3d-charts' ) : esc_attr__( 'Switch to 2D view', 'pop3d-charts' ); ?>"
                    title="<?php echo $initial_is_2d ? esc_attr__( 'Switch to 3D view', 'pop3d-charts' ) : esc_attr__( 'Switch to 2D view', 'pop3d-charts' ); ?>"
                    data-i18n-to-2d="<?php echo esc_attr__( 'Switch to 2D view', 'pop3d-charts' ); ?>"
                    data-i18n-to-3d="<?php echo esc_attr__( 'Switch to 3D view', 'pop3d-charts' ); ?>"
                    data-i18n-flat-view="<?php echo esc_attr__( 'Flat view', 'pop3d-charts' ); ?>"
                    data-i18n-perspective="<?php echo esc_attr__( 'Perspective', 'pop3d-charts' ); ?>">
                <span class="ce-diagram-expand-btn__label" aria-hidden="true"><?php echo $initial_is_2d ? esc_html__( 'Perspective', 'pop3d-charts' ) : esc_html__( 'Flat view', 'pop3d-charts' ); ?></span>
                <span class="ce-diagram-expand-btn__icon" aria-hidden="true">
                    <img class="ce-diagram-projection-icon ce-diagram-projection-icon--to-2d" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-projection-2d-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                    <img class="ce-diagram-projection-icon ce-diagram-projection-icon--to-3d" src="<?php echo esc_url( ce_pd_asset_url( 'ce-diagram-projection-3d-icon.svg' ) ); ?>" width="24" height="24" alt="" decoding="async" draggable="false"/>
                </span>
            </button>
            <?php endif; ?>
        </div>
        <div class="ce-diagram-tooltip" role="status" aria-live="polite" aria-atomic="true"></div>
        <?php if ( $chart_footnote !== '' ) : ?>
        <footer class="ce-diagram-footnote"<?php
            // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- same controlled style fragment as the chart header.
            echo $hf_style;
        ?>><?php echo esc_html( $chart_footnote ); ?></footer>
        <?php endif; ?>
    </div>
    <?php
    return ob_get_clean();
}

function ce_pd_has_block($content = null) {
    if ($content === null) {
        $post = get_post();
        $content = $post ? $post->post_content : '';
    }
    return has_block('pop3d-charts/diagram', $content);
}
