<?php
/**
 * WordPress FileSystem compatibility class.
 *
 * @since 4.2
 */

namespace WPO\IPS\Compatibility;

defined( 'ABSPATH' ) or exit;

if ( ! class_exists( '\\WPO\\IPS\\Compatibility\\FileSystem' ) ) :

class FileSystem {

	/**
	 * Default filesystem method.
	 */
	public const FILESYSTEM_DEFAULT = 'php';

	/**
	 * Available filesystem methods.
	 */
	public const FILESYSTEM_METHODS = array( 'php', 'wp' );

	/**
	 * Filesystem method enabled.
	 * @var string
	 */
	public string $system_enabled = self::FILESYSTEM_DEFAULT;

	/**
	 * Suppress errors.
	 * @var bool
	 */
	public bool $suppress_errors = false;

	/**
	 * WP_Filesystem instance.
	 * @var \WP_Filesystem_Direct|null
	 */
	public ?\WP_Filesystem_Direct $wp_filesystem = null;

	/**
	 * Singleton instance.
	 * @var self|null
	 */
	protected static ?self $_instance = null;

	/**
	 * Singleton instance.
	 * @return self
	 */
	public static function instance() {
		if ( is_null( self::$_instance ) ) {
			self::$_instance = new self();
		}
		return self::$_instance;
	}

	/**
	 * Constructor
	 */
	public function __construct() {
		$this->suppress_errors = apply_filters( 'wpo_wcpdf_file_system_suppress_errors', true );
		$debug_settings        = get_option( 'wpo_wcpdf_settings_debug', array() );

		if ( ! is_array( $debug_settings ) ) {
			$debug_settings = array();
		}

		$debug_settings['file_system_method'] = apply_filters( // Allow overriding the filesystem method via filter
			'wpo_wcpdf_filesystem_method',
			$debug_settings['file_system_method'] ?? self::FILESYSTEM_DEFAULT
		);

		$this->system_enabled = in_array( $debug_settings['file_system_method'], self::FILESYSTEM_METHODS, true )
			? $debug_settings['file_system_method']
			: self::FILESYSTEM_DEFAULT;

		if ( $this->is_wp_filesystem() ) {
			$this->initialize_wp_filesystem();
		} elseif ( ! defined( 'FS_CHMOD_FILE' ) ) {
			define( 'FS_CHMOD_FILE', ( fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) );
		}
	}

	/**
	 * Check if WP_Filesystem is enabled.
	 * @return bool
	 */
	public function is_wp_filesystem(): bool {
		return ( 'wp' === $this->system_enabled );
	}

	/**
	 * Check if PHP file functions are being used.
	 * @return bool
	 */
	public function is_php_filesystem(): bool {
		return ( 'php' === $this->system_enabled );
	}

	/**
	 * Initialize WP_Filesystem
	 * @return void
	 */
	public function initialize_wp_filesystem(): void {
		// Force the use of the direct filesystem method if not already defined
		if ( ! defined( 'FS_METHOD' ) ) {
			define( 'FS_METHOD', 'direct' );
		}

		require_once ABSPATH . 'wp-admin/includes/file.php';

		global $wp_filesystem;

		if ( ! WP_Filesystem() || ! $wp_filesystem || ! is_a( $wp_filesystem, '\WP_Filesystem_Direct' ) ) {
			wcpdf_log_error( 'WP_Filesystem initialization failed or not using direct method. Falling back to PHP methods.', 'warning' );
			$this->system_enabled = $this->change_setting_value( 'php' );
			return;
		}

		$this->wp_filesystem = $wp_filesystem;
	}

	/**
	 * Change the filesystem setting value
	 * @param string $method
	 * @return string
	 */
	protected function change_setting_value( string $method ): string {
		$debug_settings                       = get_option( 'wpo_wcpdf_settings_debug', array() );
		$debug_settings['file_system_method'] = in_array( $method, self::FILESYSTEM_METHODS, true ) ? $method : self::FILESYSTEM_DEFAULT;

		update_option( 'wpo_wcpdf_settings_debug', $debug_settings );

		return $debug_settings['file_system_method'];
	}

	/**
	 * Get file contents
	 * @param string $filename
	 * @return string|bool
	 */
	public function get_contents( string $filename ) {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->get_contents( $filename ) :
			( $this->suppress_errors ? @file_get_contents( $filename ) : file_get_contents( $filename ) );
	}

	/**
	 * Check if file is readable
	 * @param string $filename
	 * @return bool
	 */
	public function is_readable( string $filename ): bool {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->is_readable( $filename ) :
			( $this->suppress_errors ? @is_readable( $filename ) : is_readable( $filename ) );
	}

	/**
	 * Write file contents
	 * @param string $filename
	 * @param string $contents
	 * @param int|false $mode
	 * @return int|bool
	 */
	public function put_contents( string $filename, string $contents, $mode = false ) {
		if ( empty( $filename ) || empty( $contents ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->put_contents( $filename, $contents, $mode ) :
			( $this->suppress_errors ? @file_put_contents( $filename, $contents ) : file_put_contents( $filename, $contents ) );
	}

	/**
	 * Output a file directly to the response.
	 *
	 * @param string $filename
	 * @return bool
	 */
	public function output_file( string $filename ): bool {
		if ( empty( $filename ) ) {
			return false;
		}

		// WP_Filesystem_Direct doesn't provide a streaming API, so fall back to get_contents().
		if ( $this->is_wp_filesystem() ) {
			$contents = $this->wp_filesystem->get_contents( $filename );
			if ( false === $contents ) {
				return false;
			}

			echo $contents; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
			return true;
		}

		$handle = $this->suppress_errors ? @fopen( $filename, 'rb' ) : fopen( $filename, 'rb' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fopen
		if ( false === $handle ) {
			return false;
		}

		while ( ! feof( $handle ) ) {
			$buffer = fread( $handle, 8192 ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fread
			if ( false === $buffer ) {
				fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
				return false;
			}
			echo $buffer; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
		}

		fclose( $handle ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_fclose
		return true;
	}

	/**
	 * Check if file exists
	 * @param string $filename
	 * @return bool
	 */
	public function exists( string $filename ): bool {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->exists( $filename ) :
			( $this->suppress_errors ? @file_exists( $filename ) : file_exists( $filename ) );
	}

	/**
	 * Check if directory exists
	 * @param string $filename
	 * @return bool
	 */
	public function is_dir( string $filename ): bool {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->is_dir( $filename ) :
			( $this->suppress_errors ? @is_dir( $filename ) : is_dir( $filename ) );
	}

	/**
	 * Create a directory
	 * @param string $path
	 * @return bool
	 */
	public function mkdir( string $path ): bool {
		if ( empty( $path ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->mkdir( $path ) :
			( $this->suppress_errors ? @mkdir( $path ) : mkdir( $path ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir
	}

	/**
	 * Check if file is writable
	 * @param string $filename
	 * @return bool
	 */
	public function is_writable( string $filename ): bool {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->is_writable( $filename ) :
			( 'Windows' === PHP_OS_FAMILY ?
				wp_is_writable( $filename ) :
				( $this->suppress_errors ? @is_writable( $filename ) : is_writable( $filename ) ) // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_is_writable
			);
	}

	/**
	 * Delete a file
	 * @param string $filename
	 * @param bool $recursive
	 * @return bool
	 */
	public function delete( string $filename, bool $recursive = false ): bool {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->delete( $filename, $recursive ) :
			( $this->suppress_errors ? @unlink( $filename ) : unlink( $filename ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink
	}

	/**
	 * Get directory listing
	 * @param string $path
	 * @return array|bool
	 */
	public function dirlist( string $path ) {
		if ( empty( $path ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->dirlist( $path ) :
			( $this->suppress_errors ? @scandir( $path ) : scandir( $path ) );
	}

	/**
	 * Get file modification time
	 * @param string $filename
	 * @return int|bool
	 */
	public function mtime( string $filename ) {
		if ( empty( $filename ) ) {
			return false;
		}
		return $this->is_wp_filesystem() ?
			$this->wp_filesystem->mtime( $filename ) :
			( $this->suppress_errors ? @filemtime( $filename ) : filemtime( $filename ) );
	}

}

endif; // Class exists check
