<?php
/**
 * Shared logging component - the canonical Logs tab UI + its actions. The
 * toolbar (log level + Save, Clear log, Download log), the info line, and the
 * dark terminal viewer (newest first, per-line level colours) -
 * identical-by-construction across every PlugUpp plugin.
 *
 * Fully self-contained: renders its own inline styles (shared `plugupp-log-*`
 * classes; two consumers never render on the same admin page, so the literal
 * names cannot collide) and registers its own admin-post handlers:
 *
 *   {prefix}_save_log_level - saves ONLY the log level into the consumer's
 *       settings option (whitelist-sanitised here; other keys preserved).
 *   {prefix}_clear_log / {prefix}_download_log - clear + plain-text download.
 *       A consumer's pre-existing handlers under these names must be REMOVED
 *       when adopting (double registration would run both).
 *
 * Consumer contract (all via DOIG_Log::boot config):
 *   'option_key'    - the settings option holding log_level
 *   'settings_slug' - the settings page slug (the tab redirect target)
 *   The consumer's Logs tab body becomes one call:
 *   DOIG_Log_UI::render_tab();
 *
 * Lineage: extracted from the shared render engine's class-engine-log-ui.php
 * (2026-07-05); the warning level was added at extraction.
 *
 * CANONICAL SOURCE - edit here in _shared/shared-logging/src/, re-run sync.sh.
 *
 * @package dynamic-og-image-generator
 */

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

class DOIG_Log_UI {

	/** Allowed log levels (mirrors the logger). */
	const LEVELS = array( 'off', 'error', 'warning', 'info', 'debug' );

	/** Component version - the cache-busting key for the inline stylesheet.
	 * Bump when css() changes. (Plugin Check requires a non-null version on
	 * wp_register_style; the styles only change when this component does, so
	 * the component version is the correct key.) */
	const VERSION = '0.1.1';

	/** Wired from DOIG_Log::boot(). */
	public static function init() {
		add_action( 'admin_post_' . self::action( 'save_log_level' ), array( __CLASS__, 'handle_save_level' ) );
		add_action( 'admin_post_' . self::action( 'clear_log' ), array( __CLASS__, 'handle_clear' ) );
		add_action( 'admin_post_' . self::action( 'download_log' ), array( __CLASS__, 'handle_download' ) );
		add_action( 'admin_enqueue_scripts', array( __CLASS__, 'enqueue_styles' ) );
	}

	/**
	 * Enqueue the tab's styles on the consumer's settings screen, per the
	 * wp_enqueue/wp_add_inline_style requirement (WP.org review, LIHA
	 * 2026-07-06) - never echo a raw <style> tag. Registered against a
	 * src-less handle; the whole stylesheet is inline. Enqueued for every tab
	 * of the settings page (the CSS is tiny and page-scoped).
	 */
	public static function enqueue_styles() {
		// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only page routing.
		$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
		if ( '' === $page || $page !== (string) DOIG_Log::config( 'settings_slug' ) ) {
			return;
		}
		wp_register_style( 'plugupp-log-ui', false, array(), self::VERSION );
		wp_enqueue_style( 'plugupp-log-ui' );
		wp_add_inline_style( 'plugupp-log-ui', self::css() );
	}

	/** The tab's stylesheet (shared plugupp-log-* classes; see class docblock). */
	private static function css() {
		return '.plugupp-log-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin:1em 0}'
			. '.plugupp-log-inline-form{display:inline-flex;align-items:center;gap:6px;margin:0}'
			. '.plugupp-log-viewer{background:#1d2327;color:#c3c4c7;font-family:Menlo,Consolas,monospace;font-size:12px;line-height:1.5;padding:12px;border-radius:4px;max-height:460px;overflow:auto;white-space:pre-wrap;word-break:break-word}'
			. '.plugupp-log-line{display:block}'
			. '.plugupp-log-error{color:#f86368}'
			. '.plugupp-log-warning{color:#dba617}'
			. '.plugupp-log-info{color:#c3c4c7}'
			. '.plugupp-log-debug{color:#72aee6}'
			. '.plugupp-log-empty{color:#8c8f94}';
	}

	// -------------------------------------------------------------------------
	// Config helpers
	// -------------------------------------------------------------------------

	private static function action( $suffix ) {
		return DOIG_Log::key( $suffix );
	}

	/** The Logs tab URL on the consumer's settings page. */
	private static function tab_url() {
		return add_query_arg(
			array( 'page' => (string) DOIG_Log::config( 'settings_slug' ), 'tab' => 'logs' ),
			admin_url( 'admin.php' )
		);
	}

	// -------------------------------------------------------------------------
	// The tab
	// -------------------------------------------------------------------------

	/** Render the whole Logs tab (styles, notices, toolbar, info line, viewer). */
	public static function render_tab() {
		// The current level comes through the consumer's configured getter (the
		// same one the logger itself uses).
		$getter = DOIG_Log::config( 'log_level_getter' );
		$level  = is_callable( $getter ) ? (string) call_user_func( $getter ) : 'info';
		$size   = DOIG_Logger::size();

		// Styles are enqueued via admin_enqueue_scripts (enqueue_styles()) -
		// never echoed here.

		// Self-contained result notices (set by the redirects below).
		// phpcs:disable WordPress.Security.NonceVerification.Recommended -- display-only flags from our own redirects.
		if ( isset( $_GET['log_level_saved'] ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Log level saved.', 'dynamic-og-image-generator' ) . '</p></div>';
		}
		if ( isset( $_GET['log_cleared'] ) ) {
			echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__( 'Log cleared.', 'dynamic-og-image-generator' ) . '</p></div>';
		}
		// phpcs:enable WordPress.Security.NonceVerification.Recommended

		// Toolbar row: level + Save | Clear log | Download log.
		echo '<div class="plugupp-log-toolbar">';

		echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="plugupp-log-inline-form">';
		wp_nonce_field( self::action( 'save_log_level' ) );
		echo '<input type="hidden" name="action" value="' . esc_attr( self::action( 'save_log_level' ) ) . '" />';
		echo '<label for="plugupp-log-level">' . esc_html__( 'Log level:', 'dynamic-og-image-generator' ) . '</label> ';
		echo '<select id="plugupp-log-level" name="log_level">';
		$labels = array(
			'off'     => __( 'Off', 'dynamic-og-image-generator' ),
			'error'   => __( 'Errors only', 'dynamic-og-image-generator' ),
			'warning' => __( 'Errors and warnings', 'dynamic-og-image-generator' ),
			'info'    => __( 'Info (recommended)', 'dynamic-og-image-generator' ),
			'debug'   => __( 'Debug (everything)', 'dynamic-og-image-generator' ),
		);
		foreach ( $labels as $value => $label ) {
			echo '<option value="' . esc_attr( $value ) . '"';
			selected( $level, $value );
			echo '>' . esc_html( $label ) . '</option>';
		}
		echo '</select> ';
		submit_button( __( 'Save', 'dynamic-og-image-generator' ), 'secondary', 'submit', false );
		echo '</form>';

		// Clear (with confirm) + Download.
		echo '<form method="post" action="' . esc_url( admin_url( 'admin-post.php' ) ) . '" class="plugupp-log-inline-form" onsubmit="return confirm(\'' . esc_js( __( 'Clear the log file?', 'dynamic-og-image-generator' ) ) . '\');">';
		wp_nonce_field( self::action( 'clear_log' ) );
		echo '<input type="hidden" name="action" value="' . esc_attr( self::action( 'clear_log' ) ) . '" />';
		submit_button( __( 'Clear log', 'dynamic-og-image-generator' ), 'delete', 'submit', false );
		echo '</form>';

		$dl = wp_nonce_url( admin_url( 'admin-post.php?action=' . self::action( 'download_log' ) ), self::action( 'download_log' ) );
		echo '<a class="button" href="' . esc_url( $dl ) . '">' . esc_html__( 'Download log', 'dynamic-og-image-generator' ) . '</a>';
		echo '</div>';

		// Info line.
		echo wp_kses_post( '<p class="description">' . sprintf(
			/* translators: %s: human-readable file size */
			esc_html__( 'Log file size: %s. Showing last 200 entries, newest first. Rotates at 500 KB.', 'dynamic-og-image-generator' ),
			'<strong>' . esc_html( $size > 0 ? size_format( $size ) : __( '0 B (empty)', 'dynamic-og-image-generator' ) ) . '</strong>'
		) . '</p>' );

		// Terminal viewer, newest first.
		$tail  = DOIG_Logger::tail( 200 );
		$lines = '' === $tail ? array() : array_reverse( explode( "\n", $tail ) );

		echo '<div class="plugupp-log-viewer">';
		if ( empty( $lines ) ) {
			echo '<div class="plugupp-log-empty">' . esc_html__( 'The log is empty.', 'dynamic-og-image-generator' ) . '</div>';
		} else {
			foreach ( $lines as $line ) {
				if ( '' === trim( $line ) ) {
					continue;
				}
				$cls = 'plugupp-log-line';
				if ( preg_match( '/\]\s\[(ERROR|WARNING|INFO|DEBUG)\]/', $line, $m ) ) {
					$cls .= ' plugupp-log-' . strtolower( $m[1] );
				}
				echo '<div class="' . esc_attr( $cls ) . '">' . esc_html( $line ) . '</div>';
			}
		}
		echo '</div>';
	}

	// -------------------------------------------------------------------------
	// Actions (admin-post; capability + nonce on each)
	// -------------------------------------------------------------------------

	private static function guard( $nonce_action ) {
		if ( ! current_user_can( 'manage_options' ) ) {
			wp_die( esc_html__( 'You do not have permission to do this.', 'dynamic-og-image-generator' ) );
		}
		check_admin_referer( $nonce_action );
	}

	/**
	 * Save ONLY the log level into the consumer's settings option, preserving
	 * every other key. Whitelist-sanitised here, so it never depends on the
	 * consumer's full-form sanitise pipeline.
	 */
	public static function handle_save_level() {
		self::guard( self::action( 'save_log_level' ) );

		// phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified in guard().
		$level = isset( $_POST['log_level'] ) ? sanitize_key( wp_unslash( $_POST['log_level'] ) ) : 'info';
		if ( ! in_array( $level, self::LEVELS, true ) ) {
			$level = 'info';
		}

		$option_key = (string) DOIG_Log::config( 'option_key' );
		if ( '' !== $option_key ) {
			$settings              = (array) get_option( $option_key, array() );
			$settings['log_level'] = $level;
			update_option( $option_key, $settings );
		}

		wp_safe_redirect( add_query_arg( 'log_level_saved', '1', self::tab_url() ) );
		exit;
	}

	/** Clear the log file. */
	public static function handle_clear() {
		self::guard( self::action( 'clear_log' ) );
		DOIG_Logger::clear();
		wp_safe_redirect( add_query_arg( 'log_cleared', '1', self::tab_url() ) );
		exit;
	}

	/** Stream the log file as a plain-text download (<slug>-YYYYMMDD-HHMMSS.log). */
	public static function handle_download() {
		self::guard( self::action( 'download_log' ) );

		$contents = DOIG_Logger::contents();
		nocache_headers();
		header( 'Content-Type: text/plain; charset=utf-8' );
		header( 'Content-Disposition: attachment; filename="' . DOIG_Log::slug() . '-' . gmdate( 'Ymd-His' ) . '.log"' );
		echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- plain-text file download, not HTML.
		exit;
	}
}
