<?php
/**
 * Base widget helpers.
 *
 * @package AuraBuilder
 */

declare(strict_types=1);

namespace AuraBuilder\Widgets;

use AuraBuilder\Document\Node;

abstract class WidgetBase implements WidgetInterface {

	public function get_icon(): string {
		return 'box';
	}

	public function get_category(): string {
		return 'basic';
	}

	public function is_container(): bool {
		return false;
	}

	/**
	 * @return array<string, mixed>
	 */
	public function get_defaults(): array {
		return array();
	}

	/**
	 * @return list<array<string, mixed>>
	 */
	public function get_controls(): array {
		return array();
	}

	/**
	 * @param array<string, mixed> $settings
	 * @return array<string, mixed>
	 */
	public function sanitize_settings( array $settings ): array {
		$defaults = $this->get_defaults();
		$out      = array();
		foreach ( $defaults as $key => $default ) {
			$val = $settings[ $key ] ?? $default;
			if ( is_string( $default ) ) {
				$out[ $key ] = sanitize_text_field( (string) $val );
			} elseif ( is_int( $default ) ) {
				$out[ $key ] = (int) $val;
			} elseif ( is_bool( $default ) ) {
				$out[ $key ] = (bool) $val;
			} else {
				$out[ $key ] = $val;
			}
		}
		// Custom label shown in layers / selection chrome (any widget).
		if ( isset( $settings['display_name'] ) && is_string( $settings['display_name'] ) ) {
			$name = sanitize_text_field( $settings['display_name'] );
			if ( $name !== '' ) {
				$out['display_name'] = $name;
			}
		}
		return $out;
	}

	/**
	 * @param array<string, mixed> $context
	 */
	abstract public function render( Node $node, array $context = array() ): string;

	protected function align_style( string $align ): string {
		$align = in_array( $align, array( 'left', 'center', 'right' ), true ) ? $align : 'left';
		return 'text-align:' . $align . ';';
	}

	/**
	 * Sanitize object-position: "X% Y%" from focal picker, or CSS keywords.
	 */
	protected static function sanitize_object_position( $raw, string $fallback = '50% 50%' ): string {
		$pos = strtolower( trim( (string) $raw ) );
		$pos = preg_replace( '/\s+/', ' ', $pos ) ?? $fallback;
		if ( $pos === '' ) {
			return $fallback;
		}
		if ( preg_match( '/^(-?\d+(?:\.\d+)?)\s*%?\s+(-?\d+(?:\.\d+)?)\s*%?$/', $pos, $pm ) ) {
			return round( max( 0, min( 100, (float) $pm[1] ) ) ) . '% '
				. round( max( 0, min( 100, (float) $pm[2] ) ) ) . '%';
		}
		$ok = array(
			'center center',
			'center top',
			'center bottom',
			'left center',
			'right center',
			'left top',
			'right top',
			'left bottom',
			'right bottom',
			'top',
			'bottom',
			'left',
			'right',
			'center',
		);
		if ( ! in_array( $pos, $ok, true ) ) {
			return $fallback;
		}
		if ( $pos === 'top' || $pos === 'bottom' ) {
			return 'center ' . $pos;
		}
		if ( $pos === 'left' || $pos === 'right' ) {
			return $pos . ' center';
		}
		if ( $pos === 'center' ) {
			return 'center center';
		}
		return $pos;
	}

	/**
	 * Cascaded desktop←tablet←mobile for front render (mobile-first max cascade for inline leaf).
	 * Front CSS class handles media queries; inline leaf uses desktop as base + note that
	 * full responsive is via StyleEngine on the wrapper.
	 *
	 * @return array<string, mixed>
	 */
	protected function cascaded_styles( Node $node ): array {
		$out = array();
		foreach ( array( 'desktop', 'tablet', 'mobile' ) as $bp ) {
			$bag = $node->styles[ $bp ] ?? null;
			if ( ! is_array( $bag ) ) {
				continue;
			}
			foreach ( $bag as $k => $v ) {
				if ( $v !== '' && $v !== null ) {
					$out[ $k ] = $v;
				}
			}
		}
		// Prefer desktop for inline leaf (media on wrapper covers responsive).
		// Re-apply desktop last so desktop is default; StyleEngine class does MQ.
		$desk = is_array( $node->styles['desktop'] ?? null ) ? $node->styles['desktop'] : array();
		foreach ( $desk as $k => $v ) {
			if ( $v !== '' && $v !== null && ! isset( $out[ $k ] ) ) {
				$out[ $k ] = $v;
			}
		}
		// Actually for inline we want desktop primary:
		$out = $desk;
		return is_array( $out ) ? $out : array();
	}

	/**
	 * Leaf inline styles: only non-color layout (align/margin).
	 * Color / font-size / background come from StyleEngine (+ media queries)
	 * so tablet/mobile can override without fighting desktop !important inline.
	 *
	 * @param array<string, mixed> $extra Extra declarations (e.g. text-align).
	 */
	protected function leaf_style_attr( Node $node, array $extra = array() ): string {
		// Keep only structural extras (align, margin) — no colors here.
		// Colors/fonts: StyleEngine CSS (+ media queries) so mobile/tablet work on the real page.
		$parts = array();
		foreach ( $extra as $decl ) {
			if ( is_string( $decl ) && $decl !== '' ) {
				$parts[] = $decl;
			}
		}
		if ( ! $parts ) {
			return '';
		}
		// Do not esc_attr the whole string (would break CSS). Escape each value carefully.
		$safe = array();
		foreach ( $parts as $p ) {
			$safe[] = preg_replace( '/[^\w\s\-#.,%()\/"\':;!]/', '', $p ) ?? '';
		}
		return ' style="' . esc_attr( implode( ';', array_filter( $safe ) ) ) . '"';
	}
}
