<?php
/**
 * Custom Fields Module
 *
 * Lets administrators build field groups and attach them to post types or
 * taxonomy terms via location rules. Values are stored as native post/term
 * meta. All hooks run only when the module is enabled.
 *
 * Definitions are stored in a single non-autoloaded option
 * ({@see Storage::OPTION}) for cleanup on uninstall.
 *
 * @package CustomFields
 * @since 1.0.0
 */

namespace CustomFields;

use WP_PowerSuite\Core\AbstractModule;
use WP_PowerSuite\Core\Contracts\DataLocationsInterface;

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

require_once __DIR__ . '/includes/config.php';
require_once __DIR__ . '/includes/class-storage.php';
require_once __DIR__ . '/includes/class-validator.php';
require_once __DIR__ . '/includes/class-rule-engine.php';
require_once __DIR__ . '/includes/class-location-rules.php';
require_once __DIR__ . '/includes/class-location-targets.php';
require_once __DIR__ . '/includes/class-relational-search.php';
require_once __DIR__ . '/includes/class-nested-fields.php';
require_once __DIR__ . '/includes/class-relationships.php';
require_once __DIR__ . '/includes/class-fields.php';
require_once __DIR__ . '/includes/class-locations.php';
require_once __DIR__ . '/includes/class-key-generator.php';
require_once __DIR__ . '/includes/class-meta-boxes.php';
require_once __DIR__ . '/includes/class-post-edit-form.php';
require_once __DIR__ . '/includes/class-term-fields.php';
require_once __DIR__ . '/includes/class-meta-keys.php';
require_once __DIR__ . '/includes/class-import-export.php';
require_once __DIR__ . '/includes/class-admin-columns.php';
require_once __DIR__ . '/includes/class-admin.php';
require_once __DIR__ . '/includes/class-field-group-list-table.php';
require_once __DIR__ . '/includes/class-field-output.php';
require_once __DIR__ . '/includes/class-rest-meta.php';
require_once __DIR__ . '/includes/class-editor-schema.php';
require_once __DIR__ . '/includes/class-block-editor.php';
require_once __DIR__ . '/includes/class-cpt-bridge.php';
require_once __DIR__ . '/includes/helpers.php';

/**
 * Custom Fields module bootstrap.
 */
class Module extends AbstractModule implements DataLocationsInterface {

	private const MODULE_ID      = 'custom-fields';
	private const MODULE_VERSION = '1.10.4';

	private ?Storage $storage = null;

	private ?Locations $locations = null;

	private ?Fields $fields = null;

	private ?MetaBoxes $meta_boxes = null;

	private ?TermFields $term_fields = null;

	private ?Admin $admin = null;

	private ?AdminColumns $admin_columns = null;

	private ?RestMeta $rest_meta = null;

	private ?BlockEditor $block_editor = null;

	private ?PostEditForm $post_edit_form = null;

	public function getId(): string {
		return self::MODULE_ID;
	}

	public function getName(): string {
		return __( 'Custom Fields', 'wppowersuite-lite' );
	}

	public function getVersion(): string {
		return self::MODULE_VERSION;
	}

	public function getDescription(): string {
		return __( 'Add custom fields to posts and taxonomy terms, then display them anywhere with blocks, shortcodes, or page builders. For site-wide settings pages, use Options Page Builder.', 'wppowersuite-lite' );
	}

	public function getContext(): string|array {
		return [ 'admin', 'frontend', 'ajax', 'rest' ];
	}

	public function getDependencies(): array {
		return [];
	}

	/**
	 * Declare storage locations for uninstall cleanup.
	 *
	 * @return array{options: array<int, string>}
	 */
	public function getDataLocations(): array {
		return [
			'options' => [
				Storage::OPTION,
			],
		];
	}

	private function isLicenseActive(): bool {
		return function_exists( 'wp_powersuite_is_license_valid' ) && wp_powersuite_is_license_valid();
	}

	/**
	 * Register hooks (runs once per request, only when the module is enabled).
	 */
	protected function registerHooks(): void {
		if ( ! $this->isLicenseActive() ) {
			return;
		}

		$this->restMeta()->register();
		$this->blockEditor()->register();
		$this->postEditForm()->register();
		$this->loadPublicIntegrations();
		add_action( 'elementor/loaded', [ $this, 'loadElementorIntegration' ] );
		if ( defined( 'ELEMENTOR_VERSION' ) || did_action( 'elementor/loaded' ) ) {
			$this->loadElementorIntegration();
		}
		add_action( 'plugins_loaded', [ $this, 'loadBricksIntegration' ], 25 );
		add_action( 'init', [ $this, 'loadBricksIntegration' ], 15 );

		if ( ! is_admin() ) {
			return;
		}

		$meta_boxes  = $this->metaBoxes();
		$term_fields = $this->termFields();

		// Post-type meta boxes.
		add_action( 'add_meta_boxes', [ $meta_boxes, 'register' ], 10, 2 );
		add_action( 'save_post', [ $meta_boxes, 'save' ], 10, 1 );
		add_action( 'admin_notices', [ $meta_boxes, 'showSaveErrors' ] );
		add_action( 'admin_notices', [ $term_fields, 'showSaveErrors' ] );

		// Optional admin list columns.
		add_action( 'admin_init', [ $this->adminColumns(), 'register' ] );

		// Taxonomy term fields (only for taxonomies referenced by enabled groups).
		foreach ( $this->locations()->activeTaxonomies() as $taxonomy ) {
			add_action( $taxonomy . '_add_form_fields', [ $term_fields, 'renderAddFields' ], 10, 1 );
			add_action( $taxonomy . '_edit_form_fields', [ $term_fields, 'renderEditFields' ], 10, 2 );
			add_action( 'created_' . $taxonomy, [ $term_fields, 'save' ], 10, 2 );
			add_action( 'edited_' . $taxonomy, [ $term_fields, 'save' ], 10, 2 );
		}

		// Builder UI.
		$admin = $this->admin();
		add_action( 'admin_menu', [ $this, 'registerMenu' ], 20 );
		add_action( 'admin_enqueue_scripts', [ $this, 'enqueueAssets' ] );

		add_action( 'admin_post_wpps_cf_save', [ $admin, 'handleSave' ] );
		add_action( 'admin_post_wpps_cf_delete', [ $admin, 'handleDelete' ] );
		add_action( 'admin_post_wpps_cf_duplicate', [ $admin, 'handleDuplicate' ] );
		add_action( 'admin_post_wpps_cf_toggle', [ $admin, 'handleToggle' ] );
		add_action( 'admin_post_wpps_cf_export', [ $admin, 'handleExport' ] );
		add_action( 'admin_post_wpps_cf_import', [ $admin, 'handleImport' ] );
	}

	public function loadPublicIntegrations(): void {
		static $loaded = false;
		if ( $loaded ) {
			return;
		}

		$path = __DIR__ . '/integrations/loader.php';
		if ( is_readable( $path ) ) {
			require_once $path;
			if ( function_exists( 'wpps_cf_load_public_integrations' ) ) {
				wpps_cf_load_public_integrations();
			}
		}

		$loaded = true;
	}

	public function loadElementorIntegration(): void {
		static $loaded = false;
		if ( $loaded ) {
			return;
		}

		$path = __DIR__ . '/integrations/elementor.php';
		if ( ! is_readable( $path ) ) {
			return;
		}

		require_once $path;
		$loaded = true;

		if ( did_action( 'init' ) && function_exists( 'wpps_cf_init_elementor' ) ) {
			wpps_cf_init_elementor();
		}
	}

	public function loadBricksIntegration(): void {
		static $loaded = false;
		if ( $loaded || ! defined( 'BRICKS_VERSION' ) ) {
			return;
		}

		$path = __DIR__ . '/integrations/bricks.php';
		if ( ! is_readable( $path ) ) {
			return;
		}

		require_once $path;
		$loaded = true;

		if ( function_exists( 'wpps_cf_init_bricks' ) ) {
			wpps_cf_init_bricks();
		}
	}

	private function restMeta(): RestMeta {
		if ( $this->rest_meta === null ) {
			$this->rest_meta = new RestMeta( $this->storage() );
		}

		return $this->rest_meta;
	}

	private function blockEditor(): BlockEditor {
		if ( $this->block_editor === null ) {
			$this->block_editor = new BlockEditor( $this->locations() );
		}

		return $this->block_editor;
	}

	private function postEditForm(): PostEditForm {
		if ( $this->post_edit_form === null ) {
			$this->post_edit_form = new PostEditForm();
		}

		return $this->post_edit_form;
	}

	private function adminColumns(): AdminColumns {
		if ( $this->admin_columns === null ) {
			$this->admin_columns = new AdminColumns( $this->locations() );
		}

		return $this->admin_columns;
	}

	private function storage(): Storage {
		if ( $this->storage === null ) {
			$this->storage = new Storage();
		}

		return $this->storage;
	}

	private function locations(): Locations {
		if ( $this->locations === null ) {
			$this->locations = new Locations( $this->storage() );
		}

		return $this->locations;
	}

	private function fields(): Fields {
		if ( $this->fields === null ) {
			$this->fields = new Fields();
		}

		return $this->fields;
	}

	private function metaBoxes(): MetaBoxes {
		if ( $this->meta_boxes === null ) {
			$this->meta_boxes = new MetaBoxes( $this->locations(), $this->fields(), $this->storage() );
		}

		return $this->meta_boxes;
	}

	private function termFields(): TermFields {
		if ( $this->term_fields === null ) {
			$this->term_fields = new TermFields( $this->locations(), $this->fields(), $this->storage() );
		}

		return $this->term_fields;
	}

	private function admin(): Admin {
		if ( $this->admin === null ) {
			$validator   = new Validator( $this->storage() );
			$this->admin = new Admin( $this->storage(), $validator );
		}

		return $this->admin;
	}

	/**
	 * Register the builder submenu under WP PowerSuite.
	 */
	public function registerMenu(): void {
		$this->registerPowerSuiteSubmenu(
			__( 'Custom Fields', 'wppowersuite-lite' ),
			__( 'Custom Fields', 'wppowersuite-lite' ),
			Admin::PAGE_SLUG,
			[ $this->admin(), 'renderPage' ]
		);
	}

	/**
	 * Enqueue assets on the builder screen and on edit screens with fields.
	 *
	 * @param string $hook Current admin page hook suffix.
	 */
	public function enqueueAssets( string $hook ): void {
		$base    = defined( 'WP_POWERSUITE_PLUGIN_URL' ) ? WP_POWERSUITE_PLUGIN_URL : '';
		$version = self::MODULE_VERSION;
		if ( $base === '' ) {
			return;
		}

		$current_page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( (string) $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- read-only screen check.
		$is_builder   = $current_page === Admin::PAGE_SLUG;
		$is_editor    = in_array( $hook, [ 'post.php', 'post-new.php', 'edit-tags.php', 'term.php' ], true );

		if ( ! $is_builder && ! $is_editor ) {
			return;
		}

		// Shared field-control behavior (media + color picker) on every relevant screen.
		wp_enqueue_media();
		wp_enqueue_style( 'wp-color-picker' );

		// Filemtime-based versions bust browser caches whenever the files change.
		$css_path       = __DIR__ . '/assets/cf-admin.css';
		$js_path        = __DIR__ . '/assets/cf-admin.js';
		$fields_js_path = __DIR__ . '/assets/cf-fields.js';
		$css_ver        = file_exists( $css_path ) ? (string) filemtime( $css_path ) : $version;
		$js_ver         = file_exists( $js_path ) ? (string) filemtime( $js_path ) : $version;
		$fields_js_ver  = file_exists( $fields_js_path ) ? (string) filemtime( $fields_js_path ) : $version;

		wp_enqueue_style(
			'wpps-cf-admin',
			wp_powersuite_module_asset_url( __FILE__, 'assets/cf-admin.css' ),
			[ 'dashicons' ],
			$css_ver
		);

		wp_enqueue_script(
			'wpps-cf-fields',
			wp_powersuite_module_asset_url( __FILE__, 'assets/cf-fields.js' ),
			[ 'jquery', 'jquery-ui-sortable', 'wp-color-picker' ],
			$fields_js_ver,
			true
		);

		wp_enqueue_script(
			'wpps-cf-conditional',
			wp_powersuite_module_asset_url( __FILE__, 'assets/cf-conditional.js' ),
			[ 'jquery' ],
			$version,
			true
		);

		wp_enqueue_script(
			'wpps-cf-repeater',
			wp_powersuite_module_asset_url( __FILE__, 'assets/cf-repeater.js' ),
			[ 'jquery' ],
			$version,
			true
		);

		if ( $is_editor ) {
			wp_localize_script(
				'wpps-cf-fields',
				'wppsCfFields',
				[
					'searchUrl' => rest_url( 'wp-powersuite/v1/custom-fields/search/' ),
					'createTermUrl' => rest_url( 'wp-powersuite/v1/custom-fields/terms' ),
					'oembedUrl' => rest_url( 'oembed/1.0/embed' ),
					'restNonce' => wp_create_nonce( 'wp_rest' ),
				]
			);
		}

		if ( $is_builder ) {
			wp_enqueue_style( 'dashicons' );
			if ( ! wp_style_is( 'wp-powersuite-admin', 'registered' ) ) {
				wp_register_style( 'wp-powersuite-admin', $base . 'assets/admin/app.css', [ 'wp-admin' ], defined( 'WP_POWERSUITE_VERSION' ) ? WP_POWERSUITE_VERSION : $version );
			}
			wp_enqueue_style( 'wp-powersuite-admin' );
			if ( ! wp_style_is( 'wp-powersuite-module-settings', 'registered' ) ) {
				wp_register_style( 'wp-powersuite-module-settings', $base . 'assets/admin/module-settings.css', [ 'wp-powersuite-admin' ], defined( 'WP_POWERSUITE_VERSION' ) ? WP_POWERSUITE_VERSION : $version );
			}
			wp_enqueue_style( 'wp-powersuite-module-settings' );

			wp_enqueue_editor();
			$module_settings_ui = defined( 'WP_POWERSUITE_SRC_DIR' )
				? WP_POWERSUITE_SRC_DIR . 'Admin' . DIRECTORY_SEPARATOR . 'ModuleSettingsUi.php'
				: '';
			if ( $module_settings_ui !== '' && is_readable( $module_settings_ui ) ) {
				require_once $module_settings_ui;
				wpps_enqueue_accordion_tooltip_script();
			}
			wp_enqueue_script(
				'wpps-cf-admin',
				wp_powersuite_module_asset_url( __FILE__, 'assets/cf-admin.js' ),
				[ 'jquery', 'jquery-ui-sortable', 'wpps-cf-fields' ],
				$js_ver,
				true
			);

			wp_localize_script(
				'wpps-cf-admin',
				'wppsCf',
				[
					'fieldTypes'       => Config::fieldTypes(),
					'choiceTypes'      => Config::choiceTypes(),
					'mediaTypes'       => Config::mediaTypes(),
					'multipleTypes'    => Config::multipleTypes(),
					'displayOnlyTypes' => Config::displayOnlyTypes(),
					'nestedTypes'      => Config::nestedTypes(),
					'relationalTypes'  => Config::relationalTypes(),
					'publicOutputTypes' => FieldOutput::integratableTypes(),
					'supports'         => Config::fieldTypeSupports(),
					'allowedSubTypes'  => NestedFields::allowedSubFieldTypes(),
					'locationParams'   => Config::locationRuleParams(),
					'ruleOperators'    => Config::ruleOperators(),
					'locationRuleOperators' => Config::locationRuleOperators(),
					'numericLocationRuleParams' => Config::numericLocationRuleParams(),
					'fieldWidths'      => Config::fieldWidths(),
					'returnFormats'    => Config::returnFormatOptions(),
					'limits'           => [
						'maxFields'        => Config::MAX_FIELDS,
						'maxSubFields'     => Config::MAX_SUB_FIELDS,
						'maxLayouts'       => Config::MAX_LAYOUTS,
						'maxLocationRules' => Config::MAX_LOCATION_RULES,
						'maxConditionalRules' => Config::MAX_CONDITIONAL_RULES,
					],
					'strings'          => [
						'confirmDelete'  => __( 'Delete this field group? Saved values stay on your content but will no longer appear in the editor.', 'wppowersuite-lite' ),
						'confirmRemove'  => __( 'Remove this field?', 'wppowersuite-lite' ),
						'confirmDup'     => __( 'Duplicate this field?', 'wppowersuite-lite' ),
						'newField'       => __( 'New Field', 'wppowersuite-lite' ),
						'untitledField'  => __( 'Untitled field', 'wppowersuite-lite' ),
						'selectMedia'    => __( 'Select', 'wppowersuite-lite' ),
						'fieldKeyPreview' => __( 'Field key:', 'wppowersuite-lite' ),
						'copy'            => __( 'Copy', 'wppowersuite-lite' ),
						'copied'          => __( 'Copied', 'wppowersuite-lite' ),
						'condSelectField' => __( 'Select a field first', 'wppowersuite-lite' ),
						'condSelectValue' => __( 'Select a value', 'wppowersuite-lite' ),
						'startWithQuickAdd' => __( 'Start with Quick Add', 'wppowersuite-lite' ),
						'noFieldsYet'     => __( 'Choose a field type from the right, or use Add Field for a blank text field.', 'wppowersuite-lite' ),
						'removeCondition' => __( 'Remove condition', 'wppowersuite-lite' ),
						'subFieldLabel'   => __( 'Label', 'wppowersuite-lite' ),
						'subFieldName'    => __( 'Name', 'wppowersuite-lite' ),
						'subFieldType'    => __( 'Type', 'wppowersuite-lite' ),
						'addSubField'     => __( 'Add sub field', 'wppowersuite-lite' ),
						'removeSubField'  => __( 'Remove sub field', 'wppowersuite-lite' ),
						'layoutLabel'     => __( 'Layout label', 'wppowersuite-lite' ),
						'layoutKey'       => __( 'Layout key', 'wppowersuite-lite' ),
						'layoutSubFields' => __( 'Layout sub fields', 'wppowersuite-lite' ),
						'removeLayout'    => __( 'Remove layout', 'wppowersuite-lite' ),
						'fieldLimitReached' => __( 'This field group has reached the maximum number of fields.', 'wppowersuite-lite' ),
						'subFieldLimitReached' => __( 'This field has reached the maximum number of sub fields.', 'wppowersuite-lite' ),
						'layoutLimitReached' => __( 'This flexible content field has reached the maximum number of layouts.', 'wppowersuite-lite' ),
						'locationRuleLimitReached' => __( 'This field group has reached the maximum number of location rules.', 'wppowersuite-lite' ),
						'conditionalRuleLimitReached' => __( 'This field has reached the maximum number of conditional rules.', 'wppowersuite-lite' ),
					],
				]
			);
		}
	}
}
