<?php
/**
 * Plugin activator.
 *
 * Runs on plugin activation to set up necessary database tables,
 * directories, and default options.
 *
 * @package Antimanual_Builder
 * @since   1.0.0
 */

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Class AMBLD_Activator
 *
 * Handles all plugin activation tasks.
 *
 * @since 1.0.0
 */
class AMBLD_Activator {

	/**
	 * Run activation tasks.
	 *
	 * Creates upload directories, sets default options,
	 * and flushes rewrite rules.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	public static function activate() {
		self::create_upload_directories();
		self::set_default_options();
		self::create_tables();

		$page_integration = new AMBLD_Page_Integration();
		$page_integration->register_meta();

		$component_pt = new AMBLD_Post_Type_Builder_Component();
		$component_pt->register();

		// Flush rewrite rules after registering post types.
		flush_rewrite_rules();

		// Store the plugin version for future upgrade checks.
		update_option( 'ambld_version', AMBLD_VERSION );

		// Set activation timestamp.
		if ( ! get_option( 'ambld_activated_at' ) ) {
			update_option( 'ambld_activated_at', current_time( 'mysql' ) );
		}

		// Schedule the seed corpus embed in the background. We never embed
		// synchronously inside the activation request — embedding the shipped
		// corpus is ~60–150 API calls and would either time out activation or
		// freeze it for tens of seconds. The cron handler bails gracefully if
		// the parent embedder isn't configured yet, retries with exponential
		// backoff, and reschedules itself for up to MAX_SEED_RETRIES.
		if ( class_exists( 'AMBLD_Rag_Indexer' ) ) {
			AMBLD_Rag_Indexer::schedule_seed_embed( 30 );
		}
	}

	/**
	 * Create upload directories for generated assets.
	 *
	 * Creates the directory structure:
	 *   wp-content/uploads/antimanual-builder/
	 *   wp-content/uploads/antimanual-builder/css/
	 *   wp-content/uploads/antimanual-builder/js/
	 *   wp-content/uploads/antimanual-builder/images/
	 *   wp-content/uploads/am-builder/css/
	 *
	 * @since  1.0.0
	 * @return void
	 */
	private static function create_upload_directories() {
		$upload_dir = wp_upload_dir();
		$base_dir   = trailingslashit( $upload_dir['basedir'] ) . AMBLD_UPLOAD_DIR;

		$directories = array(
			$base_dir,
			$base_dir . '/css',
			$base_dir . '/js',
			$base_dir . '/images',
			trailingslashit( $upload_dir['basedir'] ) . 'am-builder',
			trailingslashit( $upload_dir['basedir'] ) . 'am-builder/css',
		);

		foreach ( $directories as $dir ) {
			if ( ! file_exists( $dir ) ) {
				wp_mkdir_p( $dir );
			}

			// Create an index.php to prevent directory listing.
			$index_file = trailingslashit( $dir ) . 'index.php';
			if ( ! file_exists( $index_file ) ) {
				file_put_contents( $index_file, '<?php // Silence is golden.' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			}

			// Defense-in-depth: deny PHP execution within these upload
			// directories on Apache. We only ever write .css / .js / .json
			// / .bin / image files here, so any .php file present is
			// either a misconfiguration or an attempt to drop a webshell.
			// nginx admins must achieve the same via their server block
			// (`location ~ /antimanual-builder/.*\.php { return 403; }`).
			$htaccess_file = trailingslashit( $dir ) . '.htaccess';
			if ( ! file_exists( $htaccess_file ) ) {
				$htaccess = "# Antimanual Builder — deny PHP execution in uploads.\n";
				$htaccess .= "<FilesMatch \"\\.(php|phtml|phar|php3|php4|php5|php7|php8|pl|cgi|sh|asp|aspx)$\">\n";
				$htaccess .= "  Require all denied\n";
				$htaccess .= "  # Apache 2.2 fallback:\n";
				$htaccess .= "  <IfModule !mod_authz_core.c>\n";
				$htaccess .= "    Order allow,deny\n";
				$htaccess .= "    Deny from all\n";
				$htaccess .= "  </IfModule>\n";
				$htaccess .= "</FilesMatch>\n";
				file_put_contents( $htaccess_file, $htaccess ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents
			}
		}
	}

	/**
	 * Set default plugin options.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	private static function set_default_options() {
		$defaults = array(
			'ambld_general'        => array(
				'post_types'  => array( 'page', 'post' ),
				'role_access' => array( 'administrator', 'editor' ),
				'css_output'  => 'file',
				'js_output'   => 'file',
			),
		);

		foreach ( $defaults as $option_name => $option_value ) {
			if ( false === get_option( $option_name ) ) {
				add_option( $option_name, $option_value );
			}
		}
	}

	/**
	 * Create custom database tables if needed.
	 *
	 * Creates a table for tracking generated assets and their versions.
	 *
	 * @since  1.0.0
	 * @return void
	 */
	private static function create_tables() {
		global $wpdb;

		$table_name      = $wpdb->prefix . 'ambld_assets';
		$charset_collate = $wpdb->get_charset_collate();

		$sql = "CREATE TABLE IF NOT EXISTS {$table_name} (
			id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
			post_id bigint(20) unsigned NOT NULL,
			asset_type varchar(10) NOT NULL DEFAULT 'css',
			file_path varchar(500) NOT NULL,
			file_url varchar(500) NOT NULL,
			version varchar(32) NOT NULL,
			created_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
			updated_at datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
			PRIMARY KEY (id),
			KEY post_id (post_id),
			KEY asset_type (asset_type)
		) {$charset_collate};";

		require_once ABSPATH . 'wp-admin/includes/upgrade.php';
		dbDelta( $sql );

		// RAG chunk store. Lives in its own class to keep the schema with the
		// component that owns it; activator just triggers creation on install.
		if ( class_exists( 'AMBLD_Rag_Store' ) ) {
			AMBLD_Rag_Store::create_table();
		}
	}
}
