<?php
/**
 * PHP Code Improvement Examples
 * 6 Practical Ways to Enhance Your WordPress Plugin Code
 */

// =============================================================================
// 1. ADD TYPE HINTS
// =============================================================================
// Tip: Add parameter and return type declarations for better IDE support and error catching

// BEFORE - No type hints
private function get_updates( $current_version ) {
	return array_filter(
		array(
			array(
				'version' => '0.1.1',
				'file'    => '00101.php',
			),
		),
		function ( $val ) use ( $current_version ) {
			return version_compare( $val['version'], $current_version, 'gt' );
		}
	);
}

private function apply_update( $update ) {
	$error = include plugin_dir_path( PLUGIN_PATH ) . '/includes/database/updates/' . $update['file'];
	if ( $error ) {
		new Database_Update_Error_Notice( $error );
	}
}

// AFTER - With type hints
private function get_updates( string $current_version ): array {
	return array_filter(
		array(
			array(
				'version' => '0.1.1',
				'file'    => '00101.php',
			),
		),
		function ( array $val ) use ( string $current_version ): bool {
			return version_compare( $val['version'], $current_version, 'gt' );
		}
	);
}

private function apply_update( array $update ): bool {
	$error = include plugin_dir_path( PLUGIN_PATH ) . '/includes/database/updates/' . $update['file'];
	if ( $error ) {
		new Database_Update_Error_Notice( $error );
		return false;
	}
	return true;
}

// =============================================================================
// 2. USE EXCEPTIONS INSTEAD OF JUST LOGGING
// =============================================================================
// Tip: Replace silent error logging with proper exception handling

// BEFORE - Silent error logging
public static function drop_tables() {
	Options::update_option( 'database_version', '0.0.0' );
	global $wpdb;
	
	foreach ( array(
		"DROP TABLE IF EXISTS {$wpdb->prefix}plugin_name_example",
	) as $sql ) {
		$wpdb->query( $sql );
		if ( $wpdb->last_error ) {
			// Just logs - calling code doesn't know about failure
			error_log( sprintf( 'Bw-Winners-Network Plugin MySql Error: %s', $wpdb->last_error ) );
		}
	}
}

// AFTER - With exception handling
public static function drop_tables(): void {
	try {
		Options::update_option( 'database_version', '0.0.0' );
		global $wpdb;
		
		foreach ( array(
			"DROP TABLE IF EXISTS {$wpdb->prefix}plugin_name_example",
		) as $sql ) {
			$wpdb->query( $sql );
			if ( $wpdb->last_error ) {
				throw new Database_Exception( 
					sprintf( 'Failed to drop table: %s', $wpdb->last_error ),
					$sql 
				);
			}
		}
	} catch ( Database_Exception $e ) {
		error_log( 'Bw-Winners-Network Database Error: ' . $e->getMessage() );
		// Could also trigger admin notice, send to monitoring service, etc.
		throw $e; // Re-throw so calling code can handle
	}
}

// =============================================================================
// 3. USE CLASS CONSTANTS FOR TABLE NAMES
// =============================================================================
// Tip: Define database table names as class constants to avoid magic strings

// BEFORE - Magic strings
class Database {

	public static function drop_tables() {
		global $wpdb;
		foreach ( array(
			// Magic string - hard to maintain
			"DROP TABLE IF EXISTS {$wpdb->prefix}plugin_name_example",
		) as $sql ) {
			$wpdb->query( $sql );
		}
	}
	
	// If you had other methods, you'd repeat the table name
	public function insert_example_data() {
		global $wpdb;
		$wpdb->insert( "{$wpdb->prefix}plugin_name_example", $data );
	}
}

// AFTER - With constants
class Database {

	// Define table names as constants
	private const TABLE_EXAMPLE = 'plugin_name_example';
	private const TABLE_SETTINGS = 'plugin_name_settings';
	
	public static function drop_tables(): void {
		global $wpdb;
		
		$tables = array(
			self::get_table_name( self::TABLE_EXAMPLE ),
			self::get_table_name( self::TABLE_SETTINGS ),
		);
		
		foreach ( $tables as $table ) {
			$sql = "DROP TABLE IF EXISTS {$table}";
			$wpdb->query( $sql );
		}
	}
	
	// Helper method to get full table name with prefix
	private static function get_table_name( string $table ): string {
		global $wpdb;
		return $wpdb->prefix . $table;
	}
	
	public function insert_example_data( array $data ): bool {
		global $wpdb;
		return $wpdb->insert( 
			self::get_table_name( self::TABLE_EXAMPLE ), 
			$data 
		);
	}
}

// =============================================================================
// 4. DEPENDENCY INJECTION VS STATIC CALLS
// =============================================================================
// Tip: Inject dependencies instead of using static calls for better testing

// BEFORE - Static dependencies
class Database {

	public function __construct() {
		add_action( 'admin_init', array( $this, 'update' ) );
	}
	
	public function update(): void {
		// Hard dependency on Options class
		$current_version = Options::get_option( 'database_version' );
		
		foreach ( $this->get_updates( $current_version ) as $update ) {
			if ( $this->apply_update( $update ) ) {
				break;
			}
			// Another static call
			Options::update_option( 'database_version', $update['version'] );
		}
	}
}

// AFTER - With dependency injection
class Database {

	private Options $options;
	
	// Inject dependencies through constructor
	public function __construct( Options $options = null ) {
		// Default to global instance if none provided
		$this->options = $options ?? new Options();
		add_action( 'admin_init', array( $this, 'update' ) );
	}
	
	public function update(): void {
		// Use injected dependency
		$current_version = $this->options->get_option( 'database_version' );
		
		foreach ( $this->get_updates( $current_version ) as $update ) {
			if ( ! $this->apply_update( $update ) ) {
				break; // Fixed logic
			}
			// Use injected dependency
			$this->options->update_option( 'database_version', $update['version'] );
		}
	}
}

// =============================================================================
// 5. SINGLE RESPONSIBILITY PRINCIPLE
// =============================================================================
// Tip: Split classes so each handles one responsibility

// BEFORE - Multiple responsibilities
class Database {
	// Responsibility 1: Database updates
	public function update() { /* ... */ }
	private function apply_update() { /* ... */ }
	private function get_updates() { /* ... */ }
	
	// Responsibility 2: Table management
	public static function drop_tables() { /* ... */ }
	
	// This class is doing too many things!
}

// AFTER - Separated responsibilities

// Handles only database updates
class Database_Updater {

	private Options $options;
	
	public function __construct( Options $options ) {
		$this->options = $options;
		add_action( 'admin_init', array( $this, 'update' ) );
	}
	
	public function update(): void {
		$current_version = $this->options->get_option( 'database_version' );
		
		foreach ( $this->get_updates( $current_version ) as $update ) {
			if ( ! $this->apply_update( $update ) ) {
				break;
			}
			$this->options->update_option( 'database_version', $update['version'] );
		}
	}
	
	private function apply_update( array $update ): bool { /* ... */ }
	private function get_updates( string $current_version ): array { /* ... */ }
}

// Handles only table operations
class Database_Schema {

	private const TABLE_EXAMPLE = 'plugin_name_example';
	
	public static function drop_tables(): void { /* ... */ }
	public static function create_tables(): void { /* ... */ }
	private static function get_table_name( string $table ): string { /* ... */ }
}

// Main coordinator class
class Database {

	private Database_Updater $updater;
	private Database_Schema $schema;
	
	public function __construct() {
		$this->updater = new Database_Updater( new Options() );
		$this->schema = new Database_Schema();
	}
}

// =============================================================================
// 6. ADD SECURITY VALIDATION
// =============================================================================
// Tip: Add validation for file paths to prevent security vulnerabilities

// BEFORE - No validation
private function apply_update( $update ) {
	// Dangerous: No validation of file path!
	$error = include plugin_dir_path( PLUGIN_PATH ) . 
	         '/includes/database/updates/' . 
	         $update['file'];
	
	if ( $error ) {
		new Database_Update_Error_Notice( $error );
	}
}

// AFTER - With validation
private function apply_update( array $update ): bool {

	// Validate the update array structure
	if ( ! isset( $update['file'] ) || ! is_string( $update['file'] ) ) {
		error_log( 'Invalid update structure: missing or invalid file' );
		return false;
	}
	
	// Validate filename (only allow alphanumeric, dots, dashes)
	if ( ! preg_match( '/^[a-zA-Z0-9._-]+\.php$/', $update['file'] ) ) {
		error_log( 'Invalid update filename: ' . $update['file'] );
		return false;
	}
	
	// Build and validate the full path
	$updates_dir = plugin_dir_path( PLUGIN_PATH ) . '/includes/database/updates/';
	$file_path = $updates_dir . $update['file'];
	
	// Ensure the file is within our updates directory (prevent path traversal)
	$real_updates_dir = realpath( $updates_dir );
	$real_file_path = realpath( $file_path );
	
	if ( ! $real_file_path || strpos( $real_file_path, $real_updates_dir ) !== 0 ) {
		error_log( 'Security violation: update file outside allowed directory' );
		return false;
	}
	
	// Check if file exists
	if ( ! file_exists( $file_path ) ) {
		error_log( 'Update file not found: ' . $file_path );
		return false;
	}
	
	// Now safely include the file
	$error = include $file_path;
	
	if ( $error ) {
		new Database_Update_Error_Notice( $error );
		return false;
	}
	
	return true;
}

// =============================================================================
// BENEFITS OF THESE IMPROVEMENTS:
// =============================================================================
/*
1. Type Hints:
   - IDE autocompletion and error detection
   - Catch type-related bugs before runtime
   - Self-documenting code

2. Exception Handling:
   - Calling code can decide how to handle errors
   - Better error context and debugging
   - More professional error handling

3. Constants:
   - Easy to change table names in one place
   - No typos in table names
   - IDE autocompletion for table names

4. Dependency Injection:
   - Much easier to unit test (can mock dependencies)
   - More flexible - can swap implementations
   - Follows SOLID principles

5. Single Responsibility:
   - Easier to test individual components
   - Clearer code organization
   - Better maintainability

6. Security Validation:
   - Prevents path traversal attacks
   - Validates file exists before including
   - Better error messages for debugging
*/