<?php
/**
 * Dashboard widgets registry.
 *
 * @package Noesia_Smart_Waiter
 */

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

if ( ! class_exists( 'Noesia_SWA_Dashboard_Widgets' ) ) {
	/**
	 * Central registry/composer for dashboard widgets.
	 */
	class Noesia_SWA_Dashboard_Widgets {

		/**
		 * Registered widgets.
		 *
		 * @var array<string,array<string,mixed>>
		 */
		protected $widgets = array();

		/**
		 * Whether external providers have been resolved.
		 *
		 * @var bool
		 */
		protected $did_bootstrap = false;

		/**
		 * Constructor.
		 */
		public function __construct() {
			$this->register_default_areas();
		}

		/**
		 * Register a widget.
		 *
		 * Supports both:
		 * - register_widget( array( ... ) )
		 * - register_widget( 'widget-id', array( ... ) )
		 *
		 * @param array<string,mixed>|string $widget Widget definition array or widget ID.
		 * @param array<string,mixed>        $args   Optional widget definition when first param is ID.
		 * @return bool
		 */
		public function register_widget( $widget, $args = array() ) {
			if ( is_string( $widget ) ) {
				$args         = is_array( $args ) ? $args : array();
				$args['id']   = $widget;
				$widget       = $args;
			}

			if ( ! is_array( $widget ) ) {
				return false;
			}

			$id = isset( $widget['id'] ) ? sanitize_key( (string) $widget['id'] ) : '';

			if ( '' === $id ) {
				return false;
			}

			$area = isset( $widget['area'] ) ? sanitize_key( (string) $widget['area'] ) : 'main';

			$normalized = array(
				'id'            => $id,
				'title'         => isset( $widget['title'] ) ? sanitize_text_field( (string) $widget['title'] ) : ucfirst( str_replace( '-', ' ', $id ) ),
				'area'          => $area,
				'priority'      => isset( $widget['priority'] ) ? (int) $widget['priority'] : ( isset( $widget['position'] ) ? (int) $widget['position'] : 10 ),
				'position'      => isset( $widget['position'] ) ? (int) $widget['position'] : ( isset( $widget['priority'] ) ? (int) $widget['priority'] : 10 ),
				'callback'      => isset( $widget['callback'] ) && is_callable( $widget['callback'] ) ? $widget['callback'] : null,
				'callback_args' => isset( $widget['callback_args'] ) && is_array( $widget['callback_args'] ) ? $widget['callback_args'] : array(),
				'owner'         => isset( $widget['owner'] ) ? sanitize_key( (string) $widget['owner'] ) : ( isset( $widget['source'] ) ? sanitize_key( (string) $widget['source'] ) : 'core' ),
				'source'        => isset( $widget['source'] ) ? sanitize_key( (string) $widget['source'] ) : ( isset( $widget['owner'] ) ? sanitize_key( (string) $widget['owner'] ) : 'core' ),
				'tier'          => isset( $widget['tier'] ) ? sanitize_key( (string) $widget['tier'] ) : 'free',
				'enabled'       => ! isset( $widget['enabled'] ) ? true : (bool) $widget['enabled'],
				'context'       => isset( $widget['context'] ) && is_array( $widget['context'] ) ? $widget['context'] : array(),
				'dependencies'  => isset( $widget['dependencies'] ) && is_array( $widget['dependencies'] ) ? $widget['dependencies'] : array(),
				'capability'    => isset( $widget['capability'] ) ? sanitize_key( (string) $widget['capability'] ) : 'manage_options',
			);

			if ( ! in_array( $normalized['tier'], array( 'free', 'pro' ), true ) ) {
				$normalized['tier'] = 'free';
			}

			$this->widgets[ $id ] = $normalized;

			return true;
		}

		/**
		 * Register multiple widgets.
		 *
		 * @param array<int|string,mixed> $widgets Widget definitions.
		 * @return void
		 */
		public function register_widgets( $widgets ) {
			if ( ! is_array( $widgets ) ) {
				return;
			}

			foreach ( $widgets as $id => $widget ) {
				if ( is_array( $widget ) ) {
					if ( ! isset( $widget['id'] ) && is_string( $id ) ) {
						$widget['id'] = $id;
					}

					$this->register_widget( $widget );
				}
			}
		}

		/**
		 * Remove a widget.
		 *
		 * @param string $id Widget ID.
		 * @return void
		 */
		public function unregister_widget( $id ) {
			$id = sanitize_key( (string) $id );

			if ( isset( $this->widgets[ $id ] ) ) {
				unset( $this->widgets[ $id ] );
			}
		}

		/**
		 * Get widgets, optionally filtered by area.
		 *
		 * @param string $area Optional area.
		 * @return array<int,array<string,mixed>>
		 */
		public function get_widgets( $area = '' ) {
			$this->bootstrap_external_widgets();

			$area    = sanitize_key( (string) $area );
			$widgets = array_values( $this->widgets );

			if ( '' !== $area ) {
				$widgets = array_values(
					array_filter(
						$widgets,
						static function ( $widget ) use ( $area ) {
							return isset( $widget['area'] ) && $area === $widget['area'];
						}
					)
				);
			}

			usort(
				$widgets,
				static function ( $a, $b ) {
					$priority_a = isset( $a['priority'] ) ? (int) $a['priority'] : 10;
					$priority_b = isset( $b['priority'] ) ? (int) $b['priority'] : 10;

					if ( $priority_a === $priority_b ) {
						$id_a = isset( $a['id'] ) ? (string) $a['id'] : '';
						$id_b = isset( $b['id'] ) ? (string) $b['id'] : '';

						return strcmp( $id_a, $id_b );
					}

					return $priority_a <=> $priority_b;
				}
			);

			return $widgets;
		}

		/**
		 * Backward-compatible alias used by legacy dashboard code.
		 *
		 * @param string $area Area slug.
		 * @return array<int,array<string,mixed>>
		 */
		public function get_widgets_by_area( $area ) {
			return $this->get_widgets( $area );
		}

		/**
		 * Render all widgets for an area.
		 *
		 * @param string              $area    Area slug.
		 * @param array<string,mixed> $context Optional render context.
		 * @return void
		 */
		public function render_area( $area, $context = array() ) {
			$this->bootstrap_external_widgets();

			$area    = sanitize_key( (string) $area );
			$widgets = $this->get_widgets( $area );

			if ( empty( $widgets ) ) {
				return;
			}

			foreach ( $widgets as $widget ) {
				if ( empty( $widget['enabled'] ) ) {
					continue;
				}

				$this->render_widget_card( $widget, is_array( $context ) ? $context : array() );
			}
		}

		/**
		 * Return known areas.
		 *
		 * @return array<int,string>
		 */
		public function get_areas() {
			$this->bootstrap_external_widgets();

			$areas = array();

			foreach ( $this->widgets as $widget ) {
				if ( empty( $widget['area'] ) ) {
					continue;
				}

				$areas[] = (string) $widget['area'];
			}

			$areas = array_values( array_unique( $areas ) );
			sort( $areas );

			return $areas;
		}

		/**
		 * Render widget wrapper.
		 *
		 * @param array<string,mixed> $widget  Widget config.
		 * @param array<string,mixed> $context Optional render context.
		 * @return void
		 */
		protected function render_widget_card( $widget, $context = array() ) {
			$capability = isset( $widget['capability'] ) ? (string) $widget['capability'] : 'manage_options';

			if ( ! current_user_can( $capability ) ) {
				return;
			}

			echo '<div class="noesia-swa-card noesia-swa-dashboard-widget" data-noesia-swa-widget="' . esc_attr( $widget['id'] ) . '">';

			if ( ! empty( $widget['title'] ) ) {
				echo '<div class="noesia-swa-card-header"><h3>' . esc_html( $widget['title'] ) . '</h3></div>';
			}

			echo '<div class="noesia-swa-card-body">';

			if ( ! empty( $widget['callback'] ) && is_callable( $widget['callback'] ) ) {
				$this->invoke_widget_callback( $widget, is_array( $context ) ? $context : array() );
			} else {
				echo '<p>' . esc_html__( 'This dashboard widget is registered but no render callback is attached yet.', 'noesia-smart-waiter' ) . '</p>';
			}

			echo '</div>';
			echo '</div>';
		}

		/**
		 * Invoke widget callback with backward-compatible argument handling.
		 *
		 * @param array<string,mixed> $widget  Widget config.
		 * @param array<string,mixed> $context Optional render context.
		 * @return void
		 */
		protected function invoke_widget_callback( $widget, $context = array() ) {
			$callback = $widget['callback'];

			if ( ! empty( $widget['callback_args'] ) && is_array( $widget['callback_args'] ) ) {
				call_user_func_array( $callback, $widget['callback_args'] );
				return;
			}

			$merged_context = array();

			if ( ! empty( $widget['context'] ) && is_array( $widget['context'] ) ) {
				$merged_context = $widget['context'];
			}

			if ( ! empty( $context ) ) {
				$merged_context = array_merge( $merged_context, $context );
			}

			$parameter_count = $this->get_callback_parameter_count( $callback );

			if ( 0 === $parameter_count ) {
				call_user_func( $callback );
				return;
			}

			if ( 1 === $parameter_count ) {
				call_user_func( $callback, $widget );
				return;
			}

			if ( 2 === $parameter_count ) {
				call_user_func( $callback, $widget, $merged_context );
				return;
			}

			call_user_func( $callback, $widget, $merged_context, $this );
		}

		/**
		 * Determine callback parameter count safely.
		 *
		 * @param callable $callback Callback.
		 * @return int
		 */
		protected function get_callback_parameter_count( $callback ) {
			try {
				if ( is_array( $callback ) && isset( $callback[0], $callback[1] ) ) {
					$reflection = new ReflectionMethod( $callback[0], (string) $callback[1] );
					return (int) $reflection->getNumberOfParameters();
				}

				if ( is_string( $callback ) && false !== strpos( $callback, '::' ) ) {
					$reflection = new ReflectionMethod( $callback );
					return (int) $reflection->getNumberOfParameters();
				}

				$reflection = new ReflectionFunction( $callback );
				return (int) $reflection->getNumberOfParameters();
			} catch ( Exception $exception ) {
				return 3;
			}
		}

		/**
		 * Seed default dashboard areas as placeholders.
		 *
		 * @return void
		 */
		protected function register_default_areas() {
			$areas = array( 'hero', 'top', 'main', 'news', 'footer' );

			foreach ( $areas as $index => $area ) {
				$this->register_widget(
					array(
						'id'       => 'area-' . $area,
						'title'    => '',
						'area'     => $area,
						'priority' => ( $index + 1 ) * 1000,
						'callback' => null,
						'owner'    => 'core',
						'tier'     => 'free',
						'enabled'  => false,
					),
				);
			}
		}

		/**
		 * Bootstrap external widget providers once.
		 *
		 * @return void
		 */
		protected function bootstrap_external_widgets() {
			if ( $this->did_bootstrap ) {
				return;
			}

			$this->did_bootstrap = true;

			/**
			 * Allow modules / legacy adapters to register dashboard widgets.
			 *
			 * @param Noesia_SWA_Dashboard_Widgets $this Registry instance.
			 */
			do_action( 'noesia_swa_register_dashboard_widgets', $this );

			$filtered = apply_filters( 'noesia_swa_dashboard_widgets', array(), $this );

			if ( is_array( $filtered ) ) {
				$this->register_widgets( $filtered );
			}

			do_action( 'noesia_swa_dashboard_widgets_loaded', $this );
		}
	}
}
