<?php
namespace DIFL\Server\Modules\AdvancedBarCounter;

use \DIFL\Server\Traits\ModuleUtil;
use \DIFL\Server\Traits\HasProps;
use ET\Builder\Framework\DependencyManagement\Interfaces\DependencyInterface;
use ET\Builder\Packages\Module\Layout\Components\StyleCommon\CommonStyle;
use ET\Builder\Packages\Module\Options\Css\CssStyle;


class AdvancedBarCounter implements DependencyInterface {
	public $slug       = 'difl_advancedbarcounter';
	public $child_slug = 'difl_advancedbarcounteritem';
	public $vb_support = 'on';
	public $icon_path;
	use ModuleUtil;
	use HasProps;

	protected $config_file = 'AdvancedBarCounter';

	protected $module_credits = array(
		'module_uri' => '',
		'author'     => 'DiviFlash',
		'author_uri' => '',
	);

	public static function custom_css() {
		return \WP_Block_Type_Registry::get_instance()->get_registered( 'difl/advancedbarcounter' )->customCssFields;
	}

	public function get_common_styles( $args ) {
		$attrs      = $args['attrs'] ?? [];
		$attrs      = $this->get_merged_attrs( $args );
		$attrs      = self::build_attr_for_styles( $attrs );
		$attrs      = $this->get_default_value_for_styles( $attrs );
		$orderClass = $args['orderClass'] ?? '';

		$styles        = [];
		$common_styles = [];

		$common_styles[] = CssStyle::style( [
			'selector'  => $orderClass,
			'attr'      => $attrs['css'] ?? [],
			'cssFields' => self::custom_css(),
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => $orderClass,
			'attr'                => $attrs['abc_bar_layout']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$value = $props['attrValue'] ?? '';
				return $value ? "--df-abc-bar-type: {$value};" : '';
			},
		] );

		/* Border radius → clip inner bars/track to the rounded corners. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => $orderClass,
			'attr'                => $attrs['module']['decoration']['border'] ?? null,
			'declarationFunction' => function( $props ) {
				$r   = $props['attrValue']['radius'] ?? [];
				$has = isset( $r['topLeft'] ) || isset( $r['topRight'] ) || isset( $r['bottomLeft'] ) || isset( $r['bottomRight'] );
				return $has ? 'overflow: hidden !important;' : '';
			},
		] );

		/* Parent-level fill-animation timing. CSS custom properties inherit
		   down to each .df_abc_item; the CSS-only fill animation reads them
		   from the registered `@property` + `transition` rules in styles.scss.
		   Scoped to `{orderClass} .df_abc_container` so the dynamic value
		   wins over the base defaults declared directly on `.df_abc_container`
		   (which otherwise beat inheritance). */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_animation_speed']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-fill-duration: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_animation_delay']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-fill-delay: {$v};" : '';
			},
		] );

		/* Icon / Image Size — emitted as a CSS custom property on
		   .df_abc_container so BOTH renderings consume it:
		     - icon glyph: SCSS sets `.df_abc_item_icon .et-pb-icon { font-size: var(...) }`
		     - image:      SCSS sets `.df_abc_item_icon_image img { width: var(...) }`
		   The 14px fallback in var() mirrors the module.json default so a
		   fresh module shows the same size on both rendering paths even
		   before any user input flows through. Byte-identical to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_icon_size']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-icon-size: {$v};" : '';
			},
		] );

		/* Icon Color — applies to the inner .et-pb-icon glyph (not the
		   image variant; PNG/SVG aren't recolored by CSS). Child can override
		   per-item via abc_item_icon_color. Hover is keyed to `.df_abc_item:hover`
		   (the item) — like the title — so it triggers on item hover, not the
		   tiny glyph and not whole-module hover. (VB hover preview is left to
		   Divi's own `.et_vb_hover` handling — no hover selector emitted there.) */
		$common_styles[] = CommonStyle::style( [
			/* Singular `selector` duplicated from selectors.desktop.value
			   purely to silence StyleTrait.php:69's unguarded
			   `$args['selector']` read — the framework prefers `selectors`
			   when both are present (StyleTrait.php:89), so this has no
			   effect on the emitted CSS. Mirrors Divi core's BlurbModule
			   pattern. */
			'selector'  => "{$orderClass} .df_abc_item_icon .et-pb-icon",
			'selectors' => [
				'desktop' => [
					'value' => "{$orderClass} .df_abc_item_icon .et-pb-icon",
					'hover' => "{$orderClass} .df_abc_item:hover .df_abc_item_icon .et-pb-icon",
				],
			],
			'attr'      => $attrs['abc_icon_color']['innerContent'] ?? [],
			'property'  => 'color',
		] );

		/* Multi-step text direction (responsive). Value is a CSS writing-mode
		   keyword (horizontal-tb | vertical-rl) consumed by SCSS via
		   writing-mode: var(--df-abc-mstd). Defaults set in module.json:
		   desktop/tablet horizontal, phone vertical. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_multi_step_text_direction']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-mstd: {$v};" : '';
			},
		] );

		/* Base Bar Thickness + Fill Bar Thickness — emitted as CSS vars on
		   .df_abc_container so both the wrap height AND the fill sizing /
		   positioning in SCSS read them from the cascade. The SCSS layout
		   uses max(0px, (base - fill) / 2) to inset the fill on all sides
		   when fill < base (Case A) and leave it edge-to-edge with vertical
		   extension when fill >= base (Case B). One rule covers both via
		   max() clamp — no PHP branching needed. Must stay byte-identical
		   to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_bar_thickness']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-bar-thickness: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_fill_thickness']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-fill-thickness: {$v};" : '';
			},
		] );

		/* Marker Size — line-only, only active when Track Percent with Fill
		   is on AND style = marker. Responsive: the breakpoint-aware
		   `innerContent` lets CSS resolve a different size per MQ. Emitted on
		   `.df_abc_container` so the child's `.df_abc_bar_percent_marker`
		   inherits via the cascade. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_marker_size']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-marker-size: {$v};" : '';
			},
		] );

		/* Pin Size — diameter of the pin indicator (.df_abc_bar_percent_pin), shown
		   only for the "pin" percent style. Byte-identical to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_pin_size']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-pin-size: {$v};" : '';
			},
		] );

		/* Indicator Gap — vertical distance between the pin/bubble/tag indicator
		   and the bar. Responsive. Consumed by the pin/bubble/tag SCSS as
		   `margin-bottom: var(--df-abc-indicator-gap, 8px)` (and the mirror on
		   position_bottom). Byte-identical to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_indicator_gap']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-indicator-gap: {$v};" : '';
			},
		] );

		/* Segment Width + Height — four emissions total. Each attribute gets
		   two: literal CSS property on `.df_abc_item_segment` (spec), plus
		   a CSS variable on `.df_abc_container` so JS segment-count math
		   (computeSegmentCount in df-advancedbarcounter.js / the segmentCount
		   useEffect in the child Edit.js) reads width via getComputedStyle.
		   orderClass-first because parent's Divi wrapper sits OUTSIDE
		   `.df_abc_container.df_abc_type_segment`. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_type_segment .df_abc_item_segment",
			'attr'                => $attrs['abc_segment_width']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "width: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_type_segment .df_abc_item_segment",
			'attr'                => $attrs['abc_segment_height']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "height: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_segment_width']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-segment-width: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_segment_height']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-segment-height: {$v};" : '';
			},
		] );

		/* Total Segments — optional user override of the auto-computed count.
		   Emitted on `.df_abc_container`; JS reads it via getComputedStyle on
		   the wrap (inherits through the cascade). Empty / "auto" → no
		   emission → JS falls back to width-based math. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_segment_count']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				if ( $v === '' || $v === null || $v === 'auto' ) return '';
				return "--df-abc-segment-count: {$v};";
			},
		] );

		/* Circle Size — single attr drives both circle (width=height=N) and
		   half-circle (width=N, height=N/2) via the same --df-abc-circle-size
		   CSS variable that styles.scss already consumes. No SVG → no
		   markup change. Scoped to `{orderClass} .df_abc_container` so the
		   dynamic value wins over the base default on `.df_abc_container`. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_circle_size']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-circle-size: {$v};" : '';
			},
		] );

		/* Ring Thickness — drives both circle and half-circle via the two
		   CSS vars styles.scss consumes (--df-abc-circle-thickness on the
		   inner mask inset; --df-abc-half-circle-thickness in the half-circle
		   inner-diameter calc). Scoped to `{orderClass} .df_abc_container`
		   so the dynamic value wins over the base defaults on
		   `.df_abc_container`. No SVG element exists. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_circle_stroke']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null )
					? "--df-abc-circle-thickness: {$v}; --df-abc-half-circle-thickness: {$v};"
					: '';
			},
		] );

		/* Fill Stroke Width — emits its own CSS var so the fill <circle>
		   can have an independent stroke-width from the track. After the
		   half-circle SVG unification, both layouts use .df_abc_circle_fill;
		   styles.scss consumes it once. Track + inner-bg keep using the
		   base var --df-abc-circle-thickness. Mirrors Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_circle_fill_stroke']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null )
					? "--df-abc-circle-fill-thickness: {$v};"
					: '';
			},
		] );

		/* Inner Background Gap — gap between the track's inner edge and the
		   inner-bg outer edge. styles.scss subtracts it from the
		   --df-abc-inner-d formula in every alignment mode for both circle
		   and half-circle. Mirrors Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_inner_bg_gap']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null )
					? "--df-abc-inner-bg-gap: {$v};"
					: '';
			},
		] );

		/* Rotation Start — circle layout only. Injects --df-abc-rotation-start
		   on .df_abc_container so it inherits to every item's conic-gradient
		   (consumed in styles.scss via `from var(--df-abc-rotation-start, 0deg)`
		   for both the default-CW and direction-ccw rules). Half_circle is
		   NOT affected: abc_rotation_start.show_if gates the attribute to
		   layout=circle only, and half_circle's gradient uses `from 270deg`
		   structurally. Must stay byte-identical to the Styles.js emission. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_rotation_start']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-rotation-start: {$v}deg;" : '';
			},
		] );

		/* Stroke Cap — emits --df-abc-cap-style consumed by 3 SCSS selectors
		   (circle track, circle fill, half_circle paths). Must stay
		   byte-identical to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_stroke_cap']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-cap-style: {$v};" : '';
			},
		] );

		/* Base + Fill colors — parent-level defaults, emitted as the ITEM vars
		   directly on `.df_abc_item` (instead of the source vars on
		   `.df_abc_container`) so per-item hover works: the value selector ends
		   at `.df_abc_item`, so Divi auto-generates `… .df_abc_item:hover` for
		   the hover state (per item, every breakpoint). A child sets the same
		   item var on the same `.df_abc_item` and wins by emission order (printed
		   after the parent) — both resting and hover. Divi 5 resolves global
		   color tokens (gcid-*) to hex before the declarationFunction runs, so
		   $props['attrValue'] is the resolved color. FE-only hover; the VB
		   Styles.js still emits the source vars on `.df_abc_container`. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_item",
			'attr'                => $attrs['abc_base_color']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-item-track-color: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_item",
			'attr'                => $attrs['abc_fill_color']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-item-fill-color: {$v};" : '';
			},
		] );

		/* Indicator Background Color — applies when bar_percent_style is
		   pin / bubble / tag and Track Percent with Fill is on. Emits the
		   `--df-abc-percent-bg` CSS variable on .df_abc_container; SCSS
		   rules for `.df_abc_bar_percent_pin/bubble/tag` consume the var
		   on both `background` and the `::after` pointer's
		   `border-*-color`, so body + pointer change in lockstep. Empty
		   → no emission → SCSS fallback #4a4a4a applies. Child override
		   emits the same var on its own order class and wins via
		   specificity. Emitted on `.df_abc_item` (the value selector ends there)
		   so per-item hover auto-generates `… .df_abc_item:hover`. FE-only hover;
		   VB Styles.js still emits on `.df_abc_container`. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_item",
			'attr'                => $attrs['abc_percent_bg_color']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-percent-bg: {$v};" : '';
			},
		] );

		/* Default Multi-step Indicator Color — applies when abc_bar_layout
		   is multi_step. Emits `--df-abc-multi-step-indicator-color` on
		   `.df_abc_container`; SCSS rules for
		   `.df_abc_indicator_line / triangle / diamond
		   .df_abc_label_marker_indicator` consume the var with a fallback
		   chain to --df-abc-item-fill-color. Empty → no emission → SCSS
		   fallback applies. Child override emits the same var on its
		   `.df_abc_item` order class (deeper element) and wins via emission
		   order. Emitted on `.df_abc_item` (the value selector ends there) so
		   per-item hover auto-generates `… .df_abc_item:hover`. FE-only hover;
		   VB Styles.js still emits on `.df_abc_container`. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_item",
			'attr'                => $attrs['abc_multi_step_indicator_color']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-multi-step-indicator-color: {$v};" : '';
			},
		] );

		/* Multi-step indicator dimensions — Width/Height drive the line shape,
		   Size drives the triangle/diamond shapes. Emitted on `.df_abc_container`
		   so the `.df_abc_indicator_line/triangle/diamond .df_abc_label_marker_indicator`
		   SCSS rules consume them. Empty → no emission → SCSS fallback (1px/14px/14px).
		   Must stay byte-parallel to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_indicator_width']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-indicator-width: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_indicator_height']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-indicator-height: {$v};" : '';
			},
		] );

		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_indicator_size']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-indicator-size: {$v};" : '';
			},
		] );

		/* Max Value — drives every layout's bar fill geometry via SCSS
		   calc(var(--df-abc-item-percent) / var(--df-abc-max-value) * 100%).
		   Default 100 lives in SCSS on `.df_abc_container` so an unset attr
		   keeps existing behavior. Must stay byte-identical to Styles.js. */
		$common_styles[] = CommonStyle::style( [
			'selector'            => "{$orderClass} .df_abc_container",
			'attr'                => $attrs['abc_max_value']['innerContent'] ?? null,
			'declarationFunction' => function( $props ) {
				$v = $props['attrValue'] ?? '';
				return ( $v !== '' && $v !== null ) ? "--df-abc-max-value: {$v};" : '';
			},
		] );

		/* Content Alignment — text-align on .df_abc_item + flex on
		   .df_abc_content_wrap. justify → space-between; left/right/center →
		   flex-start/end/center via inline-flex. Mirrors Styles.js.

		   Dependency: only emit when abc_bar_layout is line or segment
		   (mirrors the show_if on the control in module.json). Stored attr
		   values left over from a prior line/segment selection must NOT
		   bleed into circle / half_circle / multi_step layouts. */
		$bar_layout = $attrs['abc_bar_layout']['innerContent']['desktop']['value'] ?? 'line';
		if ( in_array( $bar_layout, [ 'line', 'segment' ], true ) ) {
			$common_styles[] = CommonStyle::style( [
				'selector'            => "{$orderClass} .df_abc_item",
				'attr'                => $attrs['abc_content_alignment']['innerContent'] ?? null,
				'declarationFunction' => function( $props ) {
					$v   = $props['attrValue'] ?? '';
					$map = [ 'left' => 'left', 'right' => 'right', 'center' => 'center' ];
					return isset( $map[ $v ] ) ? "text-align: {$map[ $v ]};" : '';
				},
			] );
			$common_styles[] = CommonStyle::style( [
				'selector'            => "{$orderClass} .df_abc_content_wrap",
				'attr'                => $attrs['abc_content_alignment']['innerContent'] ?? null,
				'declarationFunction' => function( $props ) {
					$v = $props['attrValue'] ?? '';
					if ( $v === 'justify' ) return 'display: flex; flex-direction: row; justify-content: space-between; align-items: center; width: 100%;';
					if ( $v === 'left' )    return 'display: inline-flex; flex-direction: row; justify-content: flex-start; align-items: center;';
					if ( $v === 'right' )   return 'display: inline-flex; flex-direction: row; justify-content: flex-end; align-items: center;';
					if ( $v === 'center' )  return 'display: inline-flex; flex-direction: row; justify-content: center; align-items: center;';
					return '';
				},
			] );
		}

		foreach ( $common_styles as $common_style ) {
			if ( empty( $common_style ) ) {
				continue;
			}
			foreach ( $common_style as $style ) {
				$styles[] = $style;
			}
		}

		return $styles ?? [];
	}

	public function render( $attrs, $content, $render_slug ) {
		wp_enqueue_script( 'df-advancedbarcounter' );

		$bar_type            = $this->props['abc_bar_layout']                  ?? 'line';
		$label_position      = $this->props['abc_multi_step_position']        ?? 'top';
		$indicator_style     = $this->props['abc_multi_step_indicator_style'] ?? 'line';
		/* Stroke Alignment — circle only. Default 'center'. The class is
		   emitted unconditionally so SCSS selectors are stable; only the
		   circle layout has SCSS rules under `.df_abc_align_<value>`. */
		$stroke_alignment    = $this->props['abc_stroke_alignment']            ?? 'center';
		/* Fill direction — circle / half_circle only. Emits a container class
		   consumed by the SCSS that mirrors the ring for anti-clockwise.
		   Default 'clockwise' keeps the existing sweep. */
		$direction           = $this->props['abc_direction']                   ?? 'clockwise';
		/* Number-animation opt-in. Emits `df_abc_animate_number_on` on the
		   container; the frontend script reads it inside arm() to decide
		   whether to count up the `.df_abc_item_percent` spans (child markup)
		   in sync with the bar fill. Off → no class → JS skips entirely. */
		$animate_number      = ( $this->props['abc_animate_number']            ?? 'off' ) === 'on';
		/* Track Percent with Fill — line layout only. When on, the child
		   renders the percent number as an indicator inside .df_abc_item_bar.
		   We also force the animate-number class on so the existing
		   frontend animator counts up the indicator's number in sync with
		   the bar fill animation. */
		$track_percent_with_fill = ( $this->props['abc_track_percent_with_fill'] ?? 'off' ) === 'on';
		$animate_number_class = ( $animate_number || $track_percent_with_fill ) ? 'df_abc_animate_number_on' : '';
		$track_percent_class  = $track_percent_with_fill ? 'df_abc_track_percent_on' : '';
		/* Swap toggle — circle / half_circle only. Adds a class on the
		   container; SCSS does the visual flip via flex-direction. No DOM
		   reorder, so saved HTML keeps title-then-percent source order. */
		$swap_title_percent  = ( $this->props['abc_swap_title_percent']        ?? 'off' ) === 'on';
		$swap_class          = $swap_title_percent ? 'df_abc_swap_title_percent_on' : '';
		/* Re-animate on Scroll Back — adds a marker class on the container.
		   The frontend JS keeps its IntersectionObserver attached when this
		   class is present, replaying the bar fill (and number count-up)
		   each time the bar re-enters the viewport. Off → existing
		   single-shot behavior. */
		$reanimate_on_scroll_back = ( $this->props['abc_reanimate_on_scroll_back'] ?? 'off' ) === 'on';
		$reanimate_class          = $reanimate_on_scroll_back ? 'df_abc_reanimate_on' : '';

		/* Mirror Divi Blog's pattern (BlogModule.php:925-943): read the Layout
		   panel's display value from barCounterLayout.decoration.layout and
		   append the matching framework class. .et_grid_module / .et_flex_module
		   / .et_block_module carry the base display + grid-template + var(--*-gap)
		   CSS; the per-instance CSS only writes the variables. */
		$layout_display = $attrs['barCounterLayout']['decoration']['layout']['desktop']['value']['display'] ?? 'flex';
		$layout_class_map = [
			'block' => 'et_block_module',
			'flex'  => 'et_flex_module',
			'grid'  => 'et_grid_module',
		];
		$layout_class = $layout_class_map[ $layout_display ] ?? 'et_flex_module';

		/* Multi-step modifier classes — emitted unconditionally so SCSS
		   selectors are stable. They only have visible effect when
		   bar_layout = multi_step (where .df_abc_label_marker exists). */
		$multi_step_classes = sprintf(
			'df_abc_multi_step_pos_%s df_abc_indicator_%s',
			esc_attr( $label_position ),
			esc_attr( $indicator_style )
		);

		// Empty state — mirror the VB (Edit.js) and CircleInfo/BackgroundSwitcher FE:
		// when there are no child bars, $content is empty, so show the shared
		// .difl__empty placeholder instead of a blank container.
		if ( '' === trim( (string) $content ) ) {
			return '<div class="df_abc_container"><div class="difl__empty"><div class="difl__empty__message"><span class="difl__empty__message__text">No items yet. Add a new item to get started.</span></div></div></div>';
		}

		return sprintf(
			'<div class="df_abc_container %3$s df_abc_type_%2$s df_abc_align_%5$s df_abc_direction_%10$s %4$s %6$s %7$s %8$s %9$s">%1$s</div>',
			$content,
			esc_attr( $bar_type ),
			esc_attr( $layout_class ),
			$multi_step_classes,
			esc_attr( $stroke_alignment ),
			esc_attr( $animate_number_class ),
			esc_attr( $swap_class ),
			esc_attr( $track_percent_class ),
			esc_attr( $reanimate_class ),
			esc_attr( $direction )
		);
	}
}
