<?php
/**
 * RSM Spark News — WordPress admin dashboard widget.
 *
 * @package Rsmspark_News
 */

defined( 'ABSPATH' ) || exit;

if ( defined( 'RSMSPARK_NEWS_MODULE_LOADED' ) || function_exists( 'rsmspark_news_bootstrap' ) ) {
	return;
}

define( 'RSMSPARK_NEWS_MODULE_LOADED', true );

/**
 * Bootstrap the Spark News dashboard widget once per request.
 *
 * @param array<string, string> $args {
 *     @type string $asset_url  Base URL for CSS/images.
 *     @type string $asset_path Base filesystem path for images.
 * }
 * @return void
 */
function rsmspark_news_bootstrap( $args = array() ) {
	if ( defined( 'RSMSPARK_NEWS_BOOTSTRAPPED' ) ) {
		return;
	}

	if ( ! is_admin() ) {
		return;
	}

	if ( ! rsmspark_news_should_display() ) {
		return;
	}

	$args = wp_parse_args(
		(array) $args,
		array(
			'asset_url'  => '',
			'asset_path' => '',
		)
	);

	$asset_url  = trailingslashit( (string) $args['asset_url'] );
	$asset_path = trailingslashit( (string) $args['asset_path'] );

	if ( '' === $asset_url || '' === $asset_path ) {
		return;
	}

	define( 'RSMSPARK_NEWS_BOOTSTRAPPED', true );
	define( 'RSMSPARK_NEWS_ASSET_URL', $asset_url );
	define( 'RSMSPARK_NEWS_ASSET_PATH', $asset_path );

	add_action( 'wp_dashboard_setup', 'rsmspark_news_register_widget' );
	add_action( 'admin_enqueue_scripts', 'rsmspark_news_enqueue_assets' );
}

/**
 * Whether the RSMSpark ecosystem is active on this site.
 *
 * @return bool
 */
function rsmspark_news_should_display() {
	static $should_display = null;

	if ( null !== $should_display ) {
		return $should_display;
	}

	$should_display = false;

	if ( function_exists( 'wp_get_theme' ) ) {
		$theme = wp_get_theme();
		foreach ( array( $theme->get_template(), $theme->get_stylesheet() ) as $slug ) {
			$slug = sanitize_key( (string) $slug );
			if ( '' !== $slug && 0 === strpos( $slug, 'openrsm-' ) ) {
				$should_display = true;
				break;
			}
		}
	}

	if ( ! $should_display ) {
		$plugins = array();

		if ( function_exists( 'get_option' ) ) {
			$plugins = (array) get_option( 'active_plugins', array() );
		}

		if ( is_multisite() && function_exists( 'get_site_option' ) ) {
			$network_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
			$plugins         = array_merge( $plugins, array_keys( $network_plugins ) );
		}

		foreach ( $plugins as $plugin ) {
			$plugin = (string) $plugin;
			if ( '' === $plugin ) {
				continue;
			}

			$folder = strtok( $plugin, '/' );
			$folder = sanitize_key( (string) $folder );

			if ( '' !== $folder && 0 === strpos( $folder, 'rsm-' ) ) {
				$should_display = true;
				break;
			}
		}
	}

	return $should_display;
}

/**
 * @return void
 */
function rsmspark_news_register_widget() {
	wp_add_dashboard_widget(
		'rsm_spark_news',
		__( 'RSM Spark News', 'rsm-content-widgets' ),
		'rsmspark_news_render_widget'
	);
}

/**
 * @param string $hook_suffix Admin screen hook suffix.
 * @return void
 */
function rsmspark_news_enqueue_assets( $hook_suffix ) {
	if ( 'index.php' !== $hook_suffix ) {
		return;
	}

	$css = RSMSPARK_NEWS_ASSET_PATH . 'css/rsm-spark-news-admin.css';
	if ( ! is_readable( $css ) ) {
		return;
	}

	wp_enqueue_style(
		'rsm-spark-news-admin',
		RSMSPARK_NEWS_ASSET_URL . 'css/rsm-spark-news-admin.css',
		array( 'dashicons' ),
		(string) filemtime( $css )
	);
}

/**
 * @return void
 */
function rsmspark_news_render_widget() {
	$items = rsmspark_news_get_feed_items();
	$logo  = rsmspark_news_logo_url();

	echo '<div class="rsm-spark-news">';

	if ( '' !== $logo ) {
		echo '<div class="rsm-spark-news__brand">';
		echo '<img src="' . esc_url( $logo ) . '" alt="" class="rsm-spark-news__logo" width="212" height="97" decoding="async" />';
		echo '</div>';
	}

	if ( empty( $items ) ) {
		echo '<p class="rsm-spark-news__empty">' . esc_html__( 'No recent posts are available right now.', 'rsm-content-widgets' ) . '</p>';
	} else {
		echo '<ul class="rsm-spark-news__list">';
		foreach ( $items as $item ) {
			echo '<li class="rsm-spark-news__item">';
			echo '<a class="rsm-spark-news__title" href="' . esc_url( $item['link'] ) . '">' . esc_html( $item['title'] ) . '</a>';
			if ( '' !== $item['date'] ) {
				echo '<span class="rsm-spark-news__date">' . esc_html( $item['date'] ) . '</span>';
			}
			echo '</li>';
		}
		echo '</ul>';
	}

	echo '<div class="rsm-spark-news__footer">';
	rsmspark_news_footer_link( 'admin-posts', __( 'Blog', 'rsm-content-widgets' ), 'https://rsmspark.com/blog/' );
	rsmspark_news_footer_link( 'book-alt', __( 'Documentation', 'rsm-content-widgets' ), 'https://rsmspark.com/documentation/' );
	rsmspark_news_footer_link( 'sos', __( 'Support', 'rsm-content-widgets' ), 'https://rsmspark.com/support/' );
	echo '</div>';
	echo '</div>';
}

/**
 * @param string $icon  Dashicon slug.
 * @param string $label Link label.
 * @param string $url   Destination URL.
 * @return void
 */
function rsmspark_news_footer_link( $icon, $label, $url ) {
	echo '<a class="rsm-spark-news__footer-link" href="' . esc_url( $url ) . '" target="_blank" rel="noopener noreferrer">';
	echo '<span class="dashicons dashicons-' . esc_attr( $icon ) . '" aria-hidden="true"></span>';
	echo '<span>' . esc_html( $label ) . '</span>';
	echo '</a>';
}

/**
 * @return string
 */
function rsmspark_news_logo_url() {
	$candidates = array(
		'images/rsm-spark-news-logo.webp',
		'images/rsm-spark-news-logo.png',
		'images/rsm-spark-news-logo.jpg',
		'images/rsm-spark-news-logo.svg',
	);

	foreach ( $candidates as $relative ) {
		$path = RSMSPARK_NEWS_ASSET_PATH . $relative;
		if ( is_readable( $path ) ) {
			return RSMSPARK_NEWS_ASSET_URL . $relative;
		}
	}

	return '';
}

/**
 * Feed URL for the dashboard news widget.
 *
 * @return string
 */
function rsmspark_news_feed_url() {
	return (string) apply_filters( 'rsmspark_news_feed_url', 'https://rsmspark.com/feed/' );
}

/**
 * SimplePie cache lifetime (seconds) for the Spark News fetch.
 *
 * Kept shorter than our own refresh window so our stale-while-revalidate
 * cadence controls freshness instead of the 12h WordPress core default.
 *
 * @return int
 */
function rsmspark_news_feed_cache_lifetime() {
	return 6 * HOUR_IN_SECONDS;
}

/**
 * Fetch and parse the newest feed items from the remote source.
 *
 * @return array<int, array{title:string,link:string,date:string}>|WP_Error
 */
function rsmspark_news_fetch_feed_items() {
	if ( ! function_exists( 'fetch_feed' ) ) {
		require_once ABSPATH . WPINC . '/feed.php';
	}

	add_filter( 'wp_feed_cache_transient_lifetime', 'rsmspark_news_feed_cache_lifetime' );
	$feed = fetch_feed( rsmspark_news_feed_url() );
	remove_filter( 'wp_feed_cache_transient_lifetime', 'rsmspark_news_feed_cache_lifetime' );

	if ( is_wp_error( $feed ) ) {
		return $feed;
	}

	$max   = $feed->get_item_quantity( 4 );
	$rss   = $feed->get_items( 0, $max );
	$items = array();

	foreach ( (array) $rss as $item ) {
		$title = trim( wp_strip_all_tags( (string) $item->get_title() ) );
		$link  = (string) $item->get_permalink();
		$date  = $item->get_date( get_option( 'date_format' ) );

		if ( '' === $title || '' === $link ) {
			continue;
		}

		$items[] = array(
			'title' => $title,
			'link'  => $link,
			'date'  => is_string( $date ) ? $date : '',
		);
	}

	return $items;
}

/**
 * Get feed items using a stale-while-revalidate cache.
 *
 * Reliability model:
 * - Attempts a refresh at most once per refresh window (default 12h) on
 *   dashboard load — new posts appear within that window.
 * - While the feed is unreachable, retries at most once per retry window
 *   (default 1h) instead of on every dashboard load.
 * - Serves the last successful items for up to the max-age window (default
 *   14 days) so a temporary outage never blanks the widget or loses content.
 * - Items with no successful refresh for longer than the max-age window are
 *   dropped, so genuinely stale news is not shown indefinitely.
 *
 * Stored in the `rsmspark_news_cache` option (non-autoloaded) so the
 * last-known-good payload survives transient eviction and object-cache flushes.
 *
 * @return array<int, array{title:string,link:string,date:string}>
 */
function rsmspark_news_get_feed_items() {
	$refresh_interval = (int) apply_filters( 'rsmspark_news_refresh_interval', 12 * HOUR_IN_SECONDS );
	$retry_interval   = (int) apply_filters( 'rsmspark_news_retry_interval', HOUR_IN_SECONDS );
	$max_age          = (int) apply_filters( 'rsmspark_news_max_age', 14 * DAY_IN_SECONDS );

	$now   = time();
	$cache = get_option( 'rsmspark_news_cache', array() );
	if ( ! is_array( $cache ) ) {
		$cache = array();
	}

	$items      = isset( $cache['items'] ) && is_array( $cache['items'] ) ? $cache['items'] : array();
	$fetched_at = isset( $cache['fetched_at'] ) ? (int) $cache['fetched_at'] : 0;
	$checked_at = isset( $cache['checked_at'] ) ? (int) $cache['checked_at'] : 0;

	$have_items = ! empty( $items );
	$due        = ! $have_items || ( $now - $fetched_at ) >= $refresh_interval;
	$throttled  = ( $now - $checked_at ) < $retry_interval;

	if ( $due && ! $throttled ) {
		$fresh = rsmspark_news_fetch_feed_items();

		if ( ! is_wp_error( $fresh ) && ! empty( $fresh ) ) {
			$items      = $fresh;
			$fetched_at = $now;
		}

		$cache = array(
			'items'      => $items,
			'fetched_at' => $fetched_at,
			'checked_at' => $now,
		);
		update_option( 'rsmspark_news_cache', $cache, false );
	}

	if ( $fetched_at > 0 && ( $now - $fetched_at ) >= $max_age ) {
		return array();
	}

	return $items;
}
