<?php
/**
 * Frontend sunrise and sunset widget.
 *
 * @package SolarDawn_Sunrise_Sunset_Widget
 */

defined( 'ABSPATH' ) || exit;

if ( ! class_exists( 'SolarDawn_Sun_Widget' ) ) {
	/**
	 * Handles shortcode rendering, location resolution, sun calculations, and widget output.
	 */
	class SolarDawn_Sun_Widget {
		/**
		 * Singleton instance.
		 *
		 * @var SolarDawn_Sun_Widget|null
		 */
		private static $instance = null;

		/**
		 * Saved plugin settings.
		 *
		 * @var array
		 */
		private $settings = array();

		/**
		 * Get singleton instance.
		 *
		 * @return SolarDawn_Sun_Widget
		 */
		public static function instance() {
			if ( null === self::$instance ) {
				self::$instance = new self();
			}

			return self::$instance;
		}

		/**
		 * Constructor.
		 */
		private function __construct() {
			$this->settings = solardawn_sun_widget_get_settings();

			add_shortcode( 'solardawn_sunrise_sunset', array( $this, 'render_shortcode' ) );
		}

		/**
		 * Render the shortcode.
		 *
		 * @param array $atts Shortcode attributes.
		 * @return string
		 */
		public function render_shortcode( $atts ) {
			$this->settings = solardawn_sun_widget_get_settings();
			$atts           = shortcode_atts(
				array(
					'country'     => '',
					'state'       => '',
					'city'        => '',
					'lat'         => '',
					'lon'         => '',
					'timezone'    => '',
					'title'       => '',
				),
				(array) $atts,
				'solardawn_sunrise_sunset'
			);

			$this->enqueue_assets();

			$location = $this->get_location_from_settings_or_shortcode( $atts );
			$data     = $this->calculate_sun_data( $location['lat'], $location['lon'], $location['timezone'] );

			if ( is_wp_error( $data ) ) {
				return $this->render_error( __( 'Sunrise and sunset times are temporarily unavailable. Please try again later.', 'solardawn-sunrise-sunset-widget' ) );
			}

			$title = '' !== trim( (string) $atts['title'] ) ? sanitize_text_field( $atts['title'] ) : $this->settings['title'];

			return $this->render_widget_html(
				$data,
				array(
					'title'    => $title,
					'location' => $location,
				)
			);
		}

		/**
		 * Resolve location from shortcode attributes or saved settings.
		 *
		 * @param array $atts Shortcode attributes.
		 * @return array
		 */
		public function get_location_from_settings_or_shortcode( $atts ) {
			$country_attr = isset( $atts['country'] ) ? sanitize_text_field( $atts['country'] ) : '';
			$state_attr   = isset( $atts['state'] ) ? sanitize_text_field( $atts['state'] ) : '';
			$city_attr    = isset( $atts['city'] ) ? sanitize_text_field( $atts['city'] ) : '';
			$lat_attr     = isset( $atts['lat'] ) ? trim( (string) $atts['lat'] ) : '';
			$lon_attr     = isset( $atts['lon'] ) ? trim( (string) $atts['lon'] ) : '';
			$tz_attr      = isset( $atts['timezone'] ) ? sanitize_text_field( $atts['timezone'] ) : '';

			if ( '' !== $lat_attr && '' !== $lon_attr ) {
				$country  = '' !== $country_attr ? $this->resolve_country_name( $country_attr ) : '';
				$fallback = '' !== $country ? solardawn_get_country_coordinates( $country ) : solardawn_get_country_coordinates( $this->settings['country'] );
				$lat      = $this->sanitize_coordinate( $lat_attr, 'lat', $fallback['lat'] );
				$lon      = $this->sanitize_coordinate( $lon_attr, 'lon', $fallback['lon'] );
				$timezone = $this->sanitize_timezone( '' !== $tz_attr ? $tz_attr : $fallback['timezone'], $fallback['timezone'] );

				return array(
					'lat'           => $lat,
					'lon'           => $lon,
					'timezone'      => $timezone,
					'country'       => $country,
					'state'         => '',
					'city'          => '',
					'location_name' => '' !== $country ? solardawn_get_country_location_name( $country ) : __( 'Manual Location', 'solardawn-sunrise-sunset-widget' ),
				);
			}

			if ( '' !== $country_attr ) {
				$details = solardawn_find_location_details( $country_attr, $state_attr, $city_attr );

				return array(
					'lat'           => $details['lat'],
					'lon'           => $details['lon'],
					'timezone'      => '' !== $tz_attr ? $this->sanitize_timezone( $tz_attr, $details['timezone'] ) : $details['timezone'],
					'country'       => $details['country'],
					'state'         => $details['state'],
					'city'          => $details['city'],
					'location_name' => $details['location_name'],
				);
			}

			$mode = isset( $this->settings['location_mode'] ) ? $this->settings['location_mode'] : 'country';

			if ( 'manual' === $mode ) {
				$fallback = solardawn_get_country_coordinates( $this->settings['country'] );
				$lat      = $this->sanitize_coordinate( $this->settings['lat'], 'lat', $fallback['lat'] );
				$lon      = $this->sanitize_coordinate( $this->settings['lon'], 'lon', $fallback['lon'] );
				$timezone = $this->sanitize_timezone( $this->settings['timezone'], $fallback['timezone'] );

				return array(
					'lat'           => $lat,
					'lon'           => $lon,
					'timezone'      => $timezone,
					'country'       => '',
					'state'         => '',
					'city'          => '',
					'location_name' => __( 'Manual Location', 'solardawn-sunrise-sunset-widget' ),
				);
			}

			if ( 'detect' === $mode ) {
				$country = $this->solardawn_detect_visitor_country();
				$coords  = solardawn_get_country_coordinates( $country );

				return array(
					'lat'           => $coords['lat'],
					'lon'           => $coords['lon'],
					'timezone'      => $coords['timezone'],
					'country'       => $country,
					'state'         => '',
					'city'          => '',
					'location_name' => solardawn_get_country_location_name( $country ),
				);
			}

			$details       = solardawn_find_location_details(
				isset( $this->settings['country'] ) ? $this->settings['country'] : 'Sri Lanka',
				isset( $this->settings['state'] ) ? $this->settings['state'] : '',
				isset( $this->settings['city'] ) ? $this->settings['city'] : ''
			);
			$lat           = $this->sanitize_coordinate( isset( $this->settings['lat'] ) ? $this->settings['lat'] : $details['lat'], 'lat', $details['lat'] );
			$lon           = $this->sanitize_coordinate( isset( $this->settings['lon'] ) ? $this->settings['lon'] : $details['lon'], 'lon', $details['lon'] );
			$timezone      = $this->sanitize_timezone( isset( $this->settings['timezone'] ) ? $this->settings['timezone'] : $details['timezone'], $details['timezone'] );
			$location_name = ! empty( $this->settings['location_name'] ) ? sanitize_text_field( $this->settings['location_name'] ) : $details['location_name'];

			return array(
				'lat'           => $lat,
				'lon'           => $lon,
				'timezone'      => $timezone,
				'country'       => $details['country'],
				'state'         => $details['state'],
				'city'          => $details['city'],
				'location_name' => $location_name,
			);
		}

		/**
		 * Calculate sun data for the provided location.
		 *
		 * @param float|string $lat      Latitude.
		 * @param float|string $lon      Longitude.
		 * @param string       $timezone Timezone identifier.
		 * @return array|WP_Error
		 */
		public function calculate_sun_data( $lat = null, $lon = null, $timezone = null ) {
			return $this->solardawn_calculate_sunrise_sunset_data( $lat, $lon, $timezone );
		}

		/**
		 * Calculate sunrise, sunset, twilight, and related daylight data locally.
		 *
		 * @param float|string $lat      Latitude.
		 * @param float|string $lon      Longitude.
		 * @param string       $timezone Timezone identifier.
		 * @return array|WP_Error
		 */
		public function solardawn_calculate_sunrise_sunset_data( $lat, $lon, $timezone ) {
			$fallback = solardawn_get_country_coordinates( 'Sri Lanka' );
			$lat      = $this->sanitize_coordinate( $lat, 'lat', $fallback['lat'] );
			$lon      = $this->sanitize_coordinate( $lon, 'lon', $fallback['lon'] );
			$timezone = $this->sanitize_timezone( $timezone, $fallback['timezone'] );
			$date     = $this->get_current_date( $timezone );
			$settings = solardawn_sun_widget_get_settings();

			$cache_hours = isset( $settings['cache_hours'] ) ? absint( $settings['cache_hours'] ) : 12;
			$cache_hours = max( 1, min( 168, $cache_hours ) );
			$cache_key   = 'solardawn_sun_' . md5( 'local|' . $lat . '|' . $lon . '|' . $timezone . '|' . $date );
			$cached      = get_transient( $cache_key );

			if ( false !== $cached && is_array( $cached ) ) {
				return $cached;
			}

			try {
				$tz       = new DateTimeZone( $timezone );
				$datetime = new DateTime( $date . ' 12:00:00', $tz );
			} catch ( Exception $e ) {
				return new WP_Error( 'solardawn_calculation_unavailable', __( 'Sun data unavailable.', 'solardawn-sunrise-sunset-widget' ) );
			}

			$sun_info = date_sun_info( $datetime->getTimestamp(), $lat, $lon );

			if ( ! is_array( $sun_info ) || empty( $sun_info['sunrise'] ) || empty( $sun_info['sunset'] ) ) {
				return new WP_Error( 'solardawn_calculation_unavailable', __( 'Sun data unavailable.', 'solardawn-sunrise-sunset-widget' ) );
			}

			$sunrise = $this->safe_sun_timestamp( $sun_info, 'sunrise' );
			$sunset  = $this->safe_sun_timestamp( $sun_info, 'sunset' );

			if ( ! $sunrise || ! $sunset ) {
				return new WP_Error( 'solardawn_calculation_unavailable', __( 'Sun data unavailable.', 'solardawn-sunrise-sunset-widget' ) );
			}

			$civil_begin        = $this->safe_sun_timestamp( $sun_info, 'civil_twilight_begin' );
			$civil_end          = $this->safe_sun_timestamp( $sun_info, 'civil_twilight_end' );
			$nautical_begin     = $this->safe_sun_timestamp( $sun_info, 'nautical_twilight_begin' );
			$nautical_end       = $this->safe_sun_timestamp( $sun_info, 'nautical_twilight_end' );
			$astronomical_begin = $this->safe_sun_timestamp( $sun_info, 'astronomical_twilight_begin' );
			$astronomical_end   = $this->safe_sun_timestamp( $sun_info, 'astronomical_twilight_end' );
			$solar_noon         = $this->safe_sun_timestamp( $sun_info, 'transit' );

			$data = array(
				'success' => true,
				'date'    => $date,
				'results' => array(
					'sunrise'                   => $this->format_sun_timestamp( $sunrise, $timezone ),
					'sunset'                    => $this->format_sun_timestamp( $sunset, $timezone ),
					'solar_noon'                => $solar_noon ? $this->format_sun_timestamp( $solar_noon, $timezone ) : '',
					'day_length'                => $this->format_day_length( $sunset - $sunrise ),
					'civil_twilight_begin'      => $this->format_sun_timestamp( $civil_begin, $timezone ),
					'civil_twilight_end'        => $this->format_sun_timestamp( $civil_end, $timezone ),
					'nautical_twilight_begin'   => $this->format_sun_timestamp( $nautical_begin, $timezone ),
					'nautical_twilight_end'     => $this->format_sun_timestamp( $nautical_end, $timezone ),
					'astronomical_twilight_begin' => $this->format_sun_timestamp( $astronomical_begin, $timezone ),
					'astronomical_twilight_end' => $this->format_sun_timestamp( $astronomical_end, $timezone ),
					'golden_hour_morning'       => array(
						'start' => $this->format_sun_timestamp( $sunrise, $timezone ),
						'end'   => $this->format_sun_timestamp( $this->offset_timestamp( $sunrise, 3600, $sunset ), $timezone ),
					),
					'golden_hour_evening'       => array(
						'start' => $this->format_sun_timestamp( $this->offset_timestamp( $sunset, -3600, $sunrise ), $timezone ),
						'end'   => $this->format_sun_timestamp( $sunset, $timezone ),
					),
					'blue_hour_morning'         => array(
						'start' => $this->format_sun_timestamp( $civil_begin, $timezone ),
						'end'   => $this->format_sun_timestamp( $sunrise, $timezone ),
					),
					'blue_hour_evening'         => array(
						'start' => $this->format_sun_timestamp( $sunset, $timezone ),
						'end'   => $this->format_sun_timestamp( $civil_end, $timezone ),
					),
				),
			);

			$data = $this->sanitize_data( $data );
			set_transient( $cache_key, $data, $cache_hours * HOUR_IN_SECONDS );

			return $data;
		}

		/**
		 * Safely read a timestamp from date_sun_info().
		 *
		 * @param array  $sun_info Sun information array.
		 * @param string $key      Timestamp key.
		 * @return int|null
		 */
		private function safe_sun_timestamp( $sun_info, $key ) {
			if ( ! isset( $sun_info[ $key ] ) || ! is_numeric( $sun_info[ $key ] ) ) {
				return null;
			}

			return (int) $sun_info[ $key ];
		}

		/**
		 * Format a sun timestamp in the selected timezone.
		 *
		 * @param int|null $timestamp Timestamp.
		 * @param string   $timezone  Timezone identifier.
		 * @return string
		 */
		private function format_sun_timestamp( $timestamp, $timezone ) {
			if ( empty( $timestamp ) ) {
				return '';
			}

			try {
				return wp_date( 'h:i A', (int) $timestamp, new DateTimeZone( $timezone ) );
			} catch ( Exception $e ) {
				return '';
			}
		}

		/**
		 * Offset a timestamp while respecting a daylight boundary.
		 *
		 * @param int $timestamp Timestamp.
		 * @param int $seconds   Offset seconds.
		 * @param int $boundary  Boundary timestamp.
		 * @return int
		 */
		private function offset_timestamp( $timestamp, $seconds, $boundary ) {
			$offset = (int) $timestamp + (int) $seconds;

			if ( $seconds > 0 ) {
				return min( $offset, (int) $boundary );
			}

			return max( $offset, (int) $boundary );
		}

		/**
		 * Format day length from seconds.
		 *
		 * @param int $seconds Duration in seconds.
		 * @return string
		 */
		private function format_day_length( $seconds ) {
			$seconds = max( 0, (int) $seconds );
			$hours   = floor( $seconds / HOUR_IN_SECONDS );
			$minutes = floor( ( $seconds % HOUR_IN_SECONDS ) / MINUTE_IN_SECONDS );

			return sprintf(
				/* translators: 1: hours, 2: minutes. */
				__( '%1$dh %2$dm', 'solardawn-sunrise-sunset-widget' ),
				$hours,
				$minutes
			);
		}

		/**
		 * Detect visitor country using ipapi.co when enabled.
		 *
		 * The full IP address is never stored. The transient key contains only a salted hash.
		 *
		 * @return string
		 */
		public function solardawn_detect_visitor_country() {
			$default_country = $this->resolve_country_name( $this->settings['country'] );

			if ( empty( $this->settings['location_mode'] ) || 'detect' !== $this->settings['location_mode'] ) {
				return $default_country;
			}

			$visitor_ip      = $this->get_visitor_ip();

			if ( '' === $visitor_ip ) {
				return $default_country;
			}

			$cache_key = 'solardawn_geo_' . substr( hash( 'sha256', wp_salt( 'auth' ) . '|' . $visitor_ip ), 0, 32 );
			$cached    = get_transient( $cache_key );

			if ( is_string( $cached ) && '' !== $cached ) {
				return $this->resolve_country_name( $cached );
			}

			$response = wp_remote_get(
				'https://ipapi.co/json/',
				array(
					'timeout'     => 3,
					'redirection' => 2,
					'headers'     => array(
						'Accept' => 'application/json',
					),
				)
			);

			if ( is_wp_error( $response ) || 200 !== (int) wp_remote_retrieve_response_code( $response ) ) {
				return $default_country;
			}

			$data = json_decode( wp_remote_retrieve_body( $response ), true );

			if ( ! is_array( $data ) || empty( $data['country_name'] ) ) {
				return $default_country;
			}

			$country = $this->resolve_country_name( sanitize_text_field( $data['country_name'] ) );
			set_transient( $cache_key, $country, 12 * HOUR_IN_SECONDS );

			return $country;
		}

		/**
		 * Render widget HTML.
		 *
		 * @param array $data    Calculated sun data.
		 * @param array $context Rendering context.
		 * @return string
		 */
		public function render_widget_html( $data, $context = array() ) {
			$settings    = solardawn_sun_widget_get_settings();
			$theme       = isset( $settings['theme'] ) ? sanitize_html_class( $settings['theme'] ) : 'sunrise';
			$theme       = in_array( $theme, array( 'light', 'dark', 'sunrise' ), true ) ? $theme : 'sunrise';
			$title       = isset( $context['title'] ) ? sanitize_text_field( $context['title'] ) : $settings['title'];
			$location    = isset( $context['location'] ) && is_array( $context['location'] ) ? $context['location'] : array();
			$results     = isset( $data['results'] ) && is_array( $data['results'] ) ? $data['results'] : array();
			$date        = isset( $data['date'] ) ? sanitize_text_field( $data['date'] ) : $this->get_current_date( isset( $location['timezone'] ) ? $location['timezone'] : 'UTC' );

			$sunrise      = $this->get_text_value( $results, array( 'sunrise' ) );
			$sunset       = $this->get_text_value( $results, array( 'sunset' ) );
			$solar_noon   = $this->get_text_value( $results, array( 'solar_noon', 'solarNoon' ) );
			$day_length   = $this->get_text_value( $results, array( 'day_length', 'dayLength' ) );
			$display_name = $this->format_location_display( $location );
			$date_label   = $this->format_date_label( $date, isset( $location['timezone'] ) ? $location['timezone'] : 'UTC' );
			$details      = $this->build_detail_items( $results, $settings );

			ob_start();
			?>
			<div class="<?php echo esc_attr( 'solardawn-sun-widget solardawn-sun-widget--' . $theme ); ?>" data-solardawn-sun-widget>
				<div class="solardawn-sun-widget__header">
					<div class="solardawn-sun-widget__heading">
						<?php if ( '' !== $title ) : ?>
							<h3 class="solardawn-sun-widget__title"><?php echo esc_html( $title ); ?></h3>
						<?php endif; ?>
						<div class="solardawn-sun-widget__location">
							<?php echo wp_kses( $this->get_icon( 'pin' ), $this->get_svg_kses_rules() ); ?>
							<strong><?php echo esc_html( $display_name ); ?></strong>
						</div>
						<?php if ( $this->is_enabled( $settings['show_date'] ) ) : ?>
							<div class="solardawn-sun-widget__date">
								<?php echo wp_kses( $this->get_icon( 'calendar' ), $this->get_svg_kses_rules() ); ?>
								<span><?php echo esc_html( $date_label ); ?></span>
							</div>
						<?php endif; ?>
					</div>
					<div class="solardawn-sun-widget__header-mark" aria-hidden="true">
						<?php echo wp_kses( $this->get_icon( 'sun' ), $this->get_svg_kses_rules() ); ?>
					</div>
				</div>

				<div class="solardawn-sun-widget__hero">
					<div class="solardawn-sun-widget__event">
						<div class="solardawn-sun-widget__event-icon solardawn-sun-widget__event-icon--sunrise">
							<?php echo wp_kses( $this->get_icon( 'sunrise' ), $this->get_svg_kses_rules() ); ?>
						</div>
						<div>
							<span class="solardawn-sun-widget__event-label"><?php esc_html_e( 'Sunrise', 'solardawn-sunrise-sunset-widget' ); ?></span>
							<strong class="solardawn-sun-widget__event-time"><?php echo esc_html( $sunrise ); ?></strong>
						</div>
					</div>
					<div class="solardawn-sun-widget__hero-divider" aria-hidden="true"></div>
					<div class="solardawn-sun-widget__event">
						<div class="solardawn-sun-widget__event-icon solardawn-sun-widget__event-icon--sunset">
							<?php echo wp_kses( $this->get_icon( 'sunset' ), $this->get_svg_kses_rules() ); ?>
						</div>
						<div>
							<span class="solardawn-sun-widget__event-label"><?php esc_html_e( 'Sunset', 'solardawn-sunrise-sunset-widget' ); ?></span>
							<strong class="solardawn-sun-widget__event-time"><?php echo esc_html( $sunset ); ?></strong>
						</div>
					</div>
				</div>

				<?php if ( ! empty( $details ) ) : ?>
					<div class="solardawn-sun-widget__details">
						<?php foreach ( $details as $item ) : ?>
							<div class="solardawn-sun-widget__detail">
								<span class="solardawn-sun-widget__detail-icon solardawn-sun-widget__detail-icon--<?php echo esc_attr( $item['icon'] ); ?>">
									<?php echo wp_kses( $this->get_icon( $item['icon'] ), $this->get_svg_kses_rules() ); ?>
								</span>
								<span class="solardawn-sun-widget__detail-label"><?php echo esc_html( $item['label'] ); ?></span>
								<strong class="solardawn-sun-widget__detail-value"><?php echo esc_html( $item['value'] ); ?></strong>
							</div>
						<?php endforeach; ?>

						<?php if ( $this->is_enabled( $settings['show_golden_hour'] ) ) : ?>
							<div class="solardawn-sun-widget__tip">
								<span class="solardawn-sun-widget__tip-icon"><?php echo wp_kses( $this->get_icon( 'bulb' ), $this->get_svg_kses_rules() ); ?></span>
								<div>
									<strong><?php esc_html_e( 'Did you know?', 'solardawn-sunrise-sunset-widget' ); ?></strong>
									<p><?php esc_html_e( 'Golden hour is the best time for photography and soft natural lighting.', 'solardawn-sunrise-sunset-widget' ); ?></p>
								</div>
							</div>
						<?php endif; ?>
					</div>
				<?php endif; ?>
			</div>
			<?php

			return ob_get_clean();
		}

		/**
		 * Render a graceful frontend error message.
		 *
		 * @param string $message Message to display.
		 * @return string
		 */
		public function render_error( $message ) {
			$this->enqueue_assets();

			return '<div class="solardawn-sun-widget solardawn-sun-widget--error" data-solardawn-sun-widget><p>' . esc_html( $message ) . '</p></div>';
		}

		/**
		 * Enqueue frontend assets.
		 *
		 * @return void
		 */
		private function enqueue_assets() {
			wp_enqueue_style(
				'solardawn-sun-widget-frontend',
				SOLARDAWN_SUN_WIDGET_URL . 'assets/css/frontend.css',
				array(),
				SOLARDAWN_SUN_WIDGET_VERSION
			);

			wp_enqueue_script(
				'solardawn-sun-widget-frontend',
				SOLARDAWN_SUN_WIDGET_URL . 'assets/js/frontend.js',
				array(),
				SOLARDAWN_SUN_WIDGET_VERSION,
				true
			);
		}

		/**
		 * Build visible detail rows according to settings.
		 *
		 * @param array $results  Sun result payload.
		 * @param array $settings Plugin settings.
		 * @return array
		 */
		private function build_detail_items( $results, $settings ) {
			$items = array();

			$day_length = $this->get_text_value( $results, array( 'day_length', 'dayLength' ) );
			if ( $this->is_enabled( $settings['show_day_length'] ) && '' !== $day_length ) {
				$items[] = array(
					'icon'  => 'sun',
					'label' => __( 'Day Length', 'solardawn-sunrise-sunset-widget' ),
					'value' => $day_length,
				);
			}

			$solar_noon = $this->get_text_value( $results, array( 'solar_noon', 'solarNoon' ) );
			if ( $this->is_enabled( $settings['show_solar_noon'] ) && '' !== $solar_noon ) {
				$items[] = array(
					'icon'  => 'sparkle',
					'label' => __( 'Solar Noon', 'solardawn-sunrise-sunset-widget' ),
					'value' => $solar_noon,
				);
			}

			if ( $this->is_enabled( $settings['show_golden_hour'] ) ) {
				$golden_morning = $this->format_range(
					$this->get_raw_value( $results, array( 'golden_hour_morning', 'goldenHourMorning' ) ),
					$this->get_text_value( $results, array( 'golden_hour_morning_start' ) ),
					$this->get_text_value( $results, array( 'golden_hour_morning_end' ) )
				);
				$golden_evening = $this->format_range(
					$this->get_raw_value( $results, array( 'golden_hour_evening', 'goldenHourEvening' ) ),
					$this->get_text_value( $results, array( 'golden_hour_evening_start' ) ),
					$this->get_text_value( $results, array( 'golden_hour_evening_end' ) )
				);

				if ( '' !== $golden_morning ) {
					$items[] = array(
						'icon'  => 'sparkle',
						'label' => __( 'Golden Hour (AM)', 'solardawn-sunrise-sunset-widget' ),
						'value' => $golden_morning,
					);
				}

				if ( '' !== $golden_evening ) {
					$items[] = array(
						'icon'  => 'sparkle',
						'label' => __( 'Golden Hour (PM)', 'solardawn-sunrise-sunset-widget' ),
						'value' => $golden_evening,
					);
				}
			}

			if ( $this->is_enabled( $settings['show_blue_hour'] ) ) {
				$blue_morning = $this->format_range(
					$this->get_raw_value( $results, array( 'blue_hour_morning', 'blueHourMorning' ) ),
					$this->get_text_value( $results, array( 'blue_hour_morning_start' ) ),
					$this->get_text_value( $results, array( 'blue_hour_morning_end' ) )
				);
				$blue_evening = $this->format_range(
					$this->get_raw_value( $results, array( 'blue_hour_evening', 'blueHourEvening' ) ),
					$this->get_text_value( $results, array( 'blue_hour_evening_start' ) ),
					$this->get_text_value( $results, array( 'blue_hour_evening_end' ) )
				);

				if ( '' !== $blue_morning ) {
					$items[] = array(
						'icon'  => 'clock',
						'label' => __( 'Blue Hour (AM)', 'solardawn-sunrise-sunset-widget' ),
						'value' => $blue_morning,
					);
				}

				if ( '' !== $blue_evening ) {
					$items[] = array(
						'icon'  => 'clock',
						'label' => __( 'Blue Hour (PM)', 'solardawn-sunrise-sunset-widget' ),
						'value' => $blue_evening,
					);
				}
			}

			if ( $this->is_enabled( $settings['show_twilight'] ) ) {
				$twilight_rows = array(
					array(
						'label' => __( 'Civil Twilight', 'solardawn-sunrise-sunset-widget' ),
						'value' => $this->format_range( '', $this->get_text_value( $results, array( 'civil_twilight_begin' ) ), $this->get_text_value( $results, array( 'civil_twilight_end' ) ) ),
					),
					array(
						'label' => __( 'Nautical Twilight', 'solardawn-sunrise-sunset-widget' ),
						'value' => $this->format_range( '', $this->get_text_value( $results, array( 'nautical_twilight_begin' ) ), $this->get_text_value( $results, array( 'nautical_twilight_end' ) ) ),
					),
					array(
						'label' => __( 'Astronomical Twilight', 'solardawn-sunrise-sunset-widget' ),
						'value' => $this->format_range( '', $this->get_text_value( $results, array( 'astronomical_twilight_begin' ) ), $this->get_text_value( $results, array( 'astronomical_twilight_end' ) ) ),
					),
				);

				foreach ( $twilight_rows as $row ) {
					if ( '' !== $row['value'] ) {
						$items[] = array(
							'icon'  => 'twilight',
							'label' => $row['label'],
							'value' => $row['value'],
						);
					}
				}
			}

			return $items;
		}

		/**
		 * Check if a setting is enabled.
		 *
		 * @param mixed $value Setting value.
		 * @return bool
		 */
		private function is_enabled( $value ) {
			return in_array( $value, array( true, 1, '1', 'yes', 'on', 'true' ), true );
		}

		/**
		 * Sanitize latitude or longitude.
		 *
		 * @param mixed        $value    Coordinate value.
		 * @param string       $type     lat or lon.
		 * @param float|string $fallback Fallback value.
		 * @return float
		 */
		private function sanitize_coordinate( $value, $type, $fallback ) {
			if ( ! is_numeric( $value ) ) {
				return (float) $fallback;
			}

			$value = round( (float) $value, 6 );

			if ( 'lat' === $type && ( $value < -90 || $value > 90 ) ) {
				return (float) $fallback;
			}

			if ( 'lon' === $type && ( $value < -180 || $value > 180 ) ) {
				return (float) $fallback;
			}

			return $value;
		}

		/**
		 * Sanitize timezone.
		 *
		 * @param string $timezone Timezone identifier.
		 * @param string $fallback Fallback timezone.
		 * @return string
		 */
		private function sanitize_timezone( $timezone, $fallback = 'UTC' ) {
			$timezone = sanitize_text_field( (string) $timezone );
			$fallback = sanitize_text_field( (string) $fallback );

			if ( in_array( $timezone, timezone_identifiers_list(), true ) ) {
				return $timezone;
			}

			if ( in_array( $fallback, timezone_identifiers_list(), true ) ) {
				return $fallback;
			}

			return 'UTC';
		}

		/**
		 * Resolve a country to a known country name.
		 *
		 * @param string $country Country name.
		 * @return string
		 */
		private function resolve_country_name( $country ) {
			$country   = trim( sanitize_text_field( (string) $country ) );
			$record    = solardawn_get_location_country_record( $country );

			return ! empty( $record['name'] ) ? $record['name'] : 'Sri Lanka';
		}

		/**
		 * Get visitor IP without storing it.
		 *
		 * @return string
		 */
		private function get_visitor_ip() {
			$keys = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR' );

			foreach ( $keys as $key ) {
				if ( empty( $_SERVER[ $key ] ) ) {
					continue;
				}

				$value = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) );

				if ( false !== strpos( $value, ',' ) ) {
					$parts = explode( ',', $value );
					$value = trim( $parts[0] );
				}

				if ( filter_var( $value, FILTER_VALIDATE_IP ) ) {
					return $value;
				}
			}

			return '';
		}

		/**
		 * Get current date for cache keys and calculations.
		 *
		 * @param string $timezone Timezone identifier.
		 * @return string
		 */
		private function get_current_date( $timezone ) {
			$timezone = $this->sanitize_timezone( $timezone, 'UTC' );

			try {
				$tz = new DateTimeZone( $timezone );
				return wp_date( 'Y-m-d', null, $tz );
			} catch ( Exception $e ) {
				return wp_date( 'Y-m-d' );
			}
		}

		/**
		 * Format a date label.
		 *
		 * @param string $date     Date string.
		 * @param string $timezone Timezone identifier.
		 * @return string
		 */
		private function format_date_label( $date, $timezone ) {
			$date     = sanitize_text_field( $date );
			$timezone = $this->sanitize_timezone( $timezone, 'UTC' );

			try {
				$dt = new DateTime( $date . ' 12:00:00', new DateTimeZone( $timezone ) );
				return wp_date( get_option( 'date_format' ) . ' - l', $dt->getTimestamp(), new DateTimeZone( $timezone ) );
			} catch ( Exception $e ) {
				return sanitize_text_field( $date );
			}
		}

		/**
		 * Format location display text.
		 *
		 * @param array $location Location context.
		 * @return string
		 */
		private function format_location_display( $location ) {
			$location_name = isset( $location['location_name'] ) ? sanitize_text_field( $location['location_name'] ) : '';
			$country       = isset( $location['country'] ) ? sanitize_text_field( $location['country'] ) : '';

			if ( '' !== $location_name && '' !== $country ) {
				return $location_name . ', ' . $country;
			}

			if ( '' !== $country ) {
				return $country;
			}

			if ( '' !== $location_name ) {
				return $location_name;
			}

			return __( 'Selected Location', 'solardawn-sunrise-sunset-widget' );
		}

		/**
		 * Get first scalar text value from a list of possible keys.
		 *
		 * @param array $source Source array.
		 * @param array $keys   Candidate keys.
		 * @return string
		 */
		private function get_text_value( $source, $keys ) {
			$value = $this->get_raw_value( $source, $keys );

			if ( is_scalar( $value ) ) {
				return sanitize_text_field( (string) $value );
			}

			return '';
		}

		/**
		 * Get first raw value from a list of possible keys.
		 *
		 * @param array $source Source array.
		 * @param array $keys   Candidate keys.
		 * @return mixed
		 */
		private function get_raw_value( $source, $keys ) {
			foreach ( $keys as $key ) {
				if ( isset( $source[ $key ] ) ) {
					return $source[ $key ];
				}
			}

			return '';
		}

		/**
		 * Format a start/end range.
		 *
		 * @param mixed  $range Range array or string.
		 * @param string $start Start time fallback.
		 * @param string $end   End time fallback.
		 * @return string
		 */
		private function format_range( $range, $start = '', $end = '' ) {
			if ( is_array( $range ) ) {
				$start = isset( $range['start'] ) ? sanitize_text_field( $range['start'] ) : $start;
				$end   = isset( $range['end'] ) ? sanitize_text_field( $range['end'] ) : $end;
			} elseif ( is_scalar( $range ) && '' !== trim( (string) $range ) ) {
				return sanitize_text_field( (string) $range );
			}

			$start = sanitize_text_field( (string) $start );
			$end   = sanitize_text_field( (string) $end );

			if ( '' !== $start && '' !== $end ) {
				return $start . ' - ' . $end;
			}

			return '';
		}

		/**
		 * Recursively sanitize a data payload.
		 *
		 * @param mixed $value Value to sanitize.
		 * @return mixed
		 */
		private function sanitize_data( $value ) {
			if ( is_array( $value ) ) {
				$clean = array();

				foreach ( $value as $key => $item ) {
					$clean_key           = is_string( $key ) ? sanitize_text_field( $key ) : $key;
					$clean[ $clean_key ] = $this->sanitize_data( $item );
				}

				return $clean;
			}

			if ( is_scalar( $value ) ) {
				return sanitize_text_field( (string) $value );
			}

			return '';
		}

		/**
		 * Return inline SVG icon markup.
		 *
		 * @param string $name Icon name.
		 * @return string
		 */
		private function get_icon( $name ) {
			$icons = array(
				'pin'      => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 21s7-6.1 7-12a7 7 0 0 0-14 0c0 5.9 7 12 7 12z"></path><circle cx="12" cy="9" r="2.5"></circle></svg>',
				'calendar' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3" y="5" width="18" height="16" rx="2"></rect><path d="M8 3v4M16 3v4M3 10h18"></path></svg>',
				'sun'      => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v3M12 19v3M4.2 4.2l2.1 2.1M17.7 17.7l2.1 2.1M2 12h3M19 12h3M4.2 19.8l2.1-2.1M17.7 6.3l2.1-2.1"></path></svg>',
				'sunrise'  => '<svg viewBox="0 0 64 64" aria-hidden="true" focusable="false"><path d="M10 45h44"></path><path d="M18 39a14 14 0 0 1 28 0"></path><path d="M32 10v13M21 17l5 8M43 17l-5 8M14 28l8 4M50 28l-8 4M25 11l7-7 7 7"></path></svg>',
				'sunset'   => '<svg viewBox="0 0 64 64" aria-hidden="true" focusable="false"><path d="M10 45h44"></path><path d="M18 39a14 14 0 0 1 28 0"></path><path d="M32 8v13M21 13l5 8M43 13l-5 8M14 28l8 4M50 28l-8 4M25 18l7 7 7-7"></path></svg>',
				'sparkle'  => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 2l2.4 7.6L22 12l-7.6 2.4L12 22l-2.4-7.6L2 12l7.6-2.4L12 2z"></path><path d="M5 3v4M3 5h4M19 17v4M17 19h4"></path></svg>',
				'clock'    => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>',
				'twilight' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 17h16"></path><path d="M7 15a5 5 0 0 1 10 0"></path><path d="M6 8l2 2M18 8l-2 2M12 4v4"></path><path d="M6 20h12"></path></svg>',
				'bulb'     => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M9 18h6M10 22h4"></path><path d="M8 14a7 7 0 1 1 8 0c-.7.5-1 1.2-1 2H9c0-.8-.3-1.5-1-2z"></path></svg>',
				'external' => '<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M14 3h7v7"></path><path d="M10 14L21 3"></path><path d="M21 14v5a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5"></path></svg>',
			);

			return isset( $icons[ $name ] ) ? $icons[ $name ] : $icons['sun'];
		}

		/**
		 * Allowed SVG tags and attributes for internal icons.
		 *
		 * @return array
		 */
		private function get_svg_kses_rules() {
			return array(
				'svg'    => array(
					'viewBox'     => true,
					'viewbox'     => true,
					'aria-hidden' => true,
					'focusable'   => true,
				),
				'path'   => array(
					'd' => true,
				),
				'circle' => array(
					'cx' => true,
					'cy' => true,
					'r'  => true,
				),
				'rect'   => array(
					'x'      => true,
					'y'      => true,
					'width'  => true,
					'height' => true,
					'rx'     => true,
				),
			);
		}
	}
}
