<?php

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

/**
 * Core plugin class. Bootstraps all components and registers hooks.
 */
class DOIG_Plugin {

	/**
	 * Root URL for the user-facing documentation site. No trailing slash;
	 * docs_url() appends the path and the UTM querystring.
	 */
	const DOCS_BASE_URL = 'https://docs.plugupp.com/dynamic-og-image-generator';

	/**
	 * Build a URL into the public documentation site, with a UTM tag that
	 * identifies which UI surface drove the click.
	 *
	 * Centralised here so every "Docs" / "Documentation" / "Learn more"
	 * link in the plugin uses the same base URL and the same UTM shape.
	 * If the docs site ever moves, this is the only edit required.
	 *
	 * @param string $path    Path under the docs root (no leading slash
	 *                        required; '' for the docs home page). May
	 *                        include a fragment, e.g. 'features/screenshot-settings#fallback-image'.
	 * @param string $surface Short tag for the UI element that owns the
	 *                        link - 'plugin_action_link', 'plugin_row_meta',
	 *                        'settings_tab', 'logs_tab', 'premium_tab',
	 *                        etc. Becomes utm_medium so analytics can tell
	 *                        which surface drives traffic.
	 * @return string Fully-qualified URL with utm querystring.
	 */
	public static function docs_url( $path = '', $surface = 'plugin' ) {
		$url = self::DOCS_BASE_URL;
		if ( '' !== $path ) {
			$url .= '/' . ltrim( $path, '/' );
		}
		return add_query_arg(
			array(
				'utm_source'   => 'plugin',
				'utm_medium'   => $surface,
				'utm_campaign' => 'docs_link',
			),
			$url
		);
	}

	/**
	 * plugupp.com with the standard brand UTMs. Cross-plugin convention -
	 * see `_standards/docs/CONVENTIONS.md` "PlugUpp branding: header logo
	 * + footer byline".
	 *
	 * @param string $medium utm_medium surface identifier.
	 * @return string
	 */
	public static function brand_url( $medium ) {
		return add_query_arg(
			array(
				'utm_source'   => 'plugin',
				'utm_medium'   => sanitize_key( $medium ),
				'utm_campaign' => 'brand_link',
			),
			'https://plugupp.com/'
		);
	}

	/**
	 * Standard page header: H1 title + right-aligned PlugUpp logo linking
	 * to plugupp.com (cross-plugin standard). The logo is bundled in
	 * `assets/img/logo-plugupp.svg` so the admin page never fetches a
	 * remote asset on render - see the brand-link rationale in
	 * `_standards/docs/CONVENTIONS.md` "PlugUpp branding".
	 *
	 * @param string $title  Page title. Caller passes the localised
	 *                       string (which differs between the free and
	 *                       premium builds because the premium title
	 *                       carries a "Premium" suffix).
	 * @param string $medium utm_medium identifier for the brand link.
	 */
	public static function render_page_header( $title, $medium ) {
		echo '<div class="doig-page-head">';
		echo '<h1>' . esc_html( $title ) . '</h1>';
		echo '<a class="doig-brand-logo" href="' . esc_url( self::brand_url( $medium ) ) . '" target="_blank" rel="noopener" aria-label="PlugUpp">';
		echo '<img src="' . esc_url( DOIG_PLUGIN_URL . 'assets/img/logo-plugupp.svg' ) . '" alt="PlugUpp" />';
		echo '</a>';
		echo '</div>';

		// Tell WordPress where admin notices belong. Without this marker,
		// wp-admin/js/common.js falls back to inserting notices as the next
		// sibling of the first <h1> inside .wrap - which, because our H1
		// lives inside the .doig-page-head flex container, places the
		// notice INSIDE that flex container as a third flex child between
		// the H1 and the PlugUpp logo. The result is a broken header row.
		// With this marker, WordPress inserts notices immediately after it
		// (outside the flex container), matching the standard placement
		// used on built-in admin pages like Plugins and Posts.
		echo '<hr class="wp-header-end">';
	}

	/**
	 * Standard admin footer: plugin name + version + "by PlugUpp" byline.
	 * Cross-plugin standard; see `_standards/docs/CONVENTIONS.md`.
	 *
	 * @param string $medium utm_medium identifier for the brand link.
	 */
	public static function render_admin_footer( $medium = 'admin_footer' ) {
		echo '<p class="doig-admin-footer">'
			. esc_html( sprintf( 'PlugUpp Dynamic OG Image Generator v%s', defined( 'DOIG_VERSION' ) ? DOIG_VERSION : '' ) )
			. ' ' . esc_html__( 'by', 'dynamic-og-image-generator' ) . ' '
			. '<a href="' . esc_url( self::brand_url( $medium ) ) . '" target="_blank" rel="noopener">PlugUpp</a>'
			. '</p>';
	}

	/**
	 * Plugin initialisation — runs on plugins_loaded.
	 */
	public static function init() {
		// Translations are loaded automatically by WordPress 4.6+ for plugins
		// hosted on WordPress.org — no explicit load_plugin_textdomain() call
		// is required (and Plugin Check now flags it as redundant).

		// Log the proxy override at boot so the override fact appears in
		// support logs without requiring admin access. INFO rather than
		// ERROR because the override is a legitimate test-env feature
		// when it is set deliberately; the admin notice + sidebar badge
		// handle the "set by mistake" case.
		//
		// The override is true for the entire lifetime of the wp-config
		// constant, so logging it on every PHP boot (every page load,
		// every AJAX call, every cron tick) is pure noise. Throttle to
		// once per day via a transient so the support-log signal is
		// preserved without flooding the file.
		if ( DOIG_License::is_test_proxy() ) {
			$sentinel = 'doig_proxy_override_logged';
			if ( false === get_transient( $sentinel ) ) {
				DOIG_Logger::info(
					sprintf( 'Plugin booted with DOIG_PROXY_URL override: %s', DOIG_License::proxy_base_url() )
				);
				set_transient( $sentinel, time(), DAY_IN_SECONDS );
			}
		}

		// Initialise components
		// Note: DOIG_License has no init() — Freemius SDK self-initialises in the main plugin file.
		DOIG_Settings::init();
		DOIG_Cache::init();
		DOIG_Post_Meta::init();
		DOIG_Cron::init();
		DOIG_Proxy_Callback::init();
		DOIG_OG_Injector::init();
		DOIG_SEO_Compat::init();

		if ( is_admin() ) {
			DOIG_Admin::init();
			DOIG_Gutenberg::init();
		}
	}

	/**
	 * Runs on plugin activation.
	 */
	public static function activate() {
		// Create cache directory
		DOIG_Cache::create_directory();

		// Schedule daily refresh cron if not already scheduled.
		//
		// First fire is 24 hours after activation, not immediately. This
		// prevents a fresh install from queueing a screenshot for every
		// published page within seconds of activation (which happens when
		// the first admin page load coincides with this initial scheduled
		// slot). Gives the admin a full day to read the Enable-sitewide
		// warning, disable specific pages, or untick Enable before any
		// bulk sweep runs.
		//
		// The 'daily' recurrence still means every 24 hours after the first
		// fire. Existing installs with an already-scheduled slot are not
		// affected (the if-guard short-circuits).
		if ( ! wp_next_scheduled( 'doig_daily_refresh_check' ) ) {
			wp_schedule_event( time() + DAY_IN_SECONDS, 'daily', 'doig_daily_refresh_check' );
		}

		// 2.4.0 M5: cache-flip orphan sweep. Recurs weekly (the
		// 'weekly' interval is core since WP 5.4; DOIG's
		// Requires-WP is 6.0+ so it's always available). First fire
		// is 7 days after activation so a fresh install never runs
		// the sweep before the customer has had time to do anything,
		// matching the same first-fire-deferred posture as the daily
		// refresh above. Existing installs with an already-scheduled
		// slot are not affected (the if-guard short-circuits).
		if ( ! wp_next_scheduled( 'doig_orphan_sweep' ) ) {
			wp_schedule_event( time() + WEEK_IN_SECONDS, 'weekly', 'doig_orphan_sweep' );
		}

		// 2.4.1: dedicated 15-minute polling pass. The custom
		// 'doig_15min' interval is registered via the cron_schedules
		// filter in DOIG_Cron::init(), which runs on plugins_loaded
		// before WP-Cron consults the schedule, so the interval is
		// available here. First fire 15 minutes after activation;
		// existing installs with a slot already scheduled
		// short-circuit the guard.
		if ( ! wp_next_scheduled( 'doig_poll_pending' ) ) {
			wp_schedule_event( time() + ( 15 * MINUTE_IN_SECONDS ), 'doig_15min', 'doig_poll_pending' );
		}

		// Set default options if not already set
		if ( ! get_option( DOIG_OPTION_KEY ) ) {
			update_option( DOIG_OPTION_KEY, DOIG_Settings::defaults() );
		}
	}

	/**
	 * Runs on plugin deactivation.
	 */
	public static function deactivate() {
		wp_clear_scheduled_hook( 'doig_daily_refresh_check' );
		// Individual doig_take_screenshot events are cleared per-post; bulk clear here too
		wp_clear_scheduled_hook( 'doig_take_screenshot' );
		// Clear bulk queue processor and discard any pending queues
		wp_clear_scheduled_hook( 'doig_process_queue' );
		// 2.4.0 M5: cache-flip orphan sweep.
		wp_clear_scheduled_hook( 'doig_orphan_sweep' );
		// 2.4.1: dedicated polling pass.
		wp_clear_scheduled_hook( 'doig_poll_pending' );
		delete_option( DOIG_Cron::QUEUE_OPTION );
		delete_option( DOIG_Cron::RETRY_OPTION );
	}
}
