<?php
namespace Simple_History\Loggers;

use Simple_History\Event_Details\Event_Details_Group;
use Simple_History\Event_Details\Event_Details_Group_Diff_Table_Formatter;
use Simple_History\Event_Details\Event_Details_Item;

/**
 * Logs edits to theme or plugin files done from Appearance -> Editor or Plugins -> Editor
 */
class File_Edits_Logger extends Logger {
	/** @var string Logger slug */
	public $slug = 'FileEditsLogger';

	/**
	 * Get array with information about this logger
	 *
	 * @return array<string, mixed>
	 */
	public function get_info() {
		return array(
			'name'        => _x( 'File edits Logger', 'Logger: FileEditsLogger', 'simple-history' ),
			'description' => __( 'Logs edits to theme and plugin files', 'simple-history' ),
			'capability'  => 'manage_options',
			'messages'    => array(
				'theme_file_edited'  => __( 'Edited file "{file_name}" in theme "{theme_name}"', 'simple-history' ),
				'plugin_file_edited' => __( 'Edited file "{file_name}" in plugin "{plugin_name}"', 'simple-history' ),
			),
			'labels'      => array(
				'search' => array(
					'label'     => _x( 'Edited theme and plugin files', 'Plugin logger: file edits', 'simple-history' ),
					'label_all' => _x( 'All file edits', 'Plugin logger: file edits', 'simple-history' ),
					'options'   => array(
						_x( 'Edited theme files', 'Plugin logger: file edits', 'simple-history' ) => array(
							'theme_file_edited',
						),
						_x( 'Edited plugin files', 'Plugin logger: file edits', 'simple-history' ) => array(
							'plugin_file_edited',
						),
					),
				),
			),
		);
	}

	/**
	 * Called when logger is loaded.
	 */
	public function loaded() {
		// Capture old file contents before WordPress saves the edit.
		// Priority 0 runs before WP's handler at priority 1.
		add_action( 'wp_ajax_edit-theme-plugin-file', array( $this, 'on_file_edit_ajax' ), 0 );
	}

	/**
	 * Capture file contents before the AJAX edit handler saves.
	 *
	 * Runs at priority 0 on wp_ajax_edit-theme-plugin-file,
	 * before WordPress's own handler which writes the file.
	 */
	public function on_file_edit_ajax() {
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$file = isset( $_POST['file'] ) ? wp_unslash( $_POST['file'] ) : '';

		// Validate file path (same check WordPress core uses).
		if ( ! $file || validate_file( $file ) !== 0 ) {
			return;
		}

		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$new_contents = isset( $_POST['newcontent'] ) ? wp_unslash( $_POST['newcontent'] ) : '';

		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$plugin = isset( $_POST['plugin'] ) ? wp_unslash( $_POST['plugin'] ) : '';
		// phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
		$theme = isset( $_POST['theme'] ) ? wp_unslash( $_POST['theme'] ) : '';

		// Gate on the same capabilities WP core's handler enforces, so this
		// priority-0 hook can't be used by lower-privileged users to trigger
		// file reads before core's own check runs.
		if ( $plugin && current_user_can( 'edit_plugins' ) && validate_file( $plugin ) === 0 ) {
			$this->capture_plugin_file_edit( $file, $plugin, $new_contents );
		} elseif ( $theme && current_user_can( 'edit_themes' ) && validate_file( $theme ) === 0 ) {
			$this->capture_theme_file_edit( $file, $theme, $new_contents );
		}
	}

	/**
	 * Capture and log a plugin file edit.
	 *
	 * @param string $file Relative file name.
	 * @param string $plugin Plugin file path.
	 * @param string $new_contents New file contents.
	 */
	private function capture_plugin_file_edit( $file, $plugin, $new_contents ) {
		$file_full_path = WP_PLUGIN_DIR . '/' . $file;

		$plugin_info = get_plugin_data( WP_PLUGIN_DIR . '/' . $plugin );

		// phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
		$old_contents = file_get_contents( $file_full_path );

		if ( $old_contents === false || $old_contents === $new_contents ) {
			return;
		}

		$this->info_message(
			'plugin_file_edited',
			array(
				'file_name'         => $file,
				'plugin_name'       => $plugin_info['Name'] ?? '',
				'plugin_version'    => $plugin_info['Version'] ?? '',
				'old_file_contents' => $old_contents,
				'new_file_contents' => $new_contents,
				'_occasionsID'      => self::class . "/file-edit/plugin/$plugin/$file",
			)
		);
	}

	/**
	 * Capture and log a theme file edit.
	 *
	 * @param string $file Relative file name.
	 * @param string $stylesheet Theme stylesheet slug.
	 * @param string $new_contents New file contents.
	 */
	private function capture_theme_file_edit( $file, $stylesheet, $new_contents ) {
		$theme = wp_get_theme( $stylesheet );

		if ( ! $theme->exists() ) {
			return;
		}

		$file_full_path = $theme->get_stylesheet_directory() . '/' . $file;

		// phpcs:ignore WordPressVIPMinimum.Performance.FetchingRemoteData.FileGetContentsUnknown
		$old_contents = file_get_contents( $file_full_path );

		if ( $old_contents === false || $old_contents === $new_contents ) {
			return;
		}

		$this->info_message(
			'theme_file_edited',
			array(
				'theme_name'            => $theme->name,
				'theme_stylesheet_path' => $theme->get_stylesheet(),
				'theme_stylesheet_dir'  => $theme->get_stylesheet_directory(),
				'file_name'             => $file,
				'file_dir'              => $file_full_path,
				'old_file_contents'     => $old_contents,
				'new_file_contents'     => $new_contents,
				'_occasionsID'          => self::class . "/file-edit/theme/$stylesheet/$file",
			)
		);
	}

	/**
	 * Get output for row details
	 *
	 * @param object $row Log row.
	 * @return Event_Details_Group|string
	 */
	public function get_log_row_details_output( $row ) {
		$context     = $row->context;
		$message_key = $context['_message_key'] ?? null;

		if ( ! $message_key ) {
			return '';
		}

		return ( new Event_Details_Group() )
			->set_formatter( new Event_Details_Group_Diff_Table_Formatter() )
			->add_items(
				[
					new Event_Details_Item(
						[ 'new_file_contents', 'old_file_contents' ],
						__( 'File contents', 'simple-history' ),
					),
				]
			);
	}
}
