<?php
/**
 * Plugin Name:       Commerce Link Builder
 * Plugin URI:        https://example.com/commerce-link-builder
 * Description:       Amazon / 楽天市場 / Yahoo!ショッピングの商品検索・商品リンク生成・記事埋め込みを提供する WordPress プラグイン。
 * Version:           0.2.3
 * Requires at least: 6.6
 * Requires PHP:      8.1
 * Author:            Commerce Link Builder Contributors
 * Author URI:        https://example.com/
 * License:           GPL-2.0-or-later
 * License URI:       https://www.gnu.org/licenses/gpl-2.0.html
 * Text Domain:       commerce-link-builder
 * Domain Path:       /languages
 *
 * @package CommerceLinkBuilder
 */

declare(strict_types=1);

namespace CommerceLinkBuilder;

use CommerceLinkBuilder\Application\Cache\CacheServiceProvider;
use CommerceLinkBuilder\Application\Render\RenderServiceProvider;
use CommerceLinkBuilder\Bootstrap\AdminCapabilityActivator;
use CommerceLinkBuilder\Bootstrap\Plugin;
use CommerceLinkBuilder\Bootstrap\SyncCronServiceProvider;
use CommerceLinkBuilder\Infrastructure\Persistence\PersistenceServiceProvider;
use CommerceLinkBuilder\Presentation\Admin\AdminServiceProvider;
use CommerceLinkBuilder\Presentation\Block\BlockServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\AuditServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\DiagnosticsServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\EditorServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\LogsServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\ProductsServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\ProviderServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\RestServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\SettingsServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\SyncJobsServiceProvider;
use CommerceLinkBuilder\Presentation\Rest\SyncServiceProvider;

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

/**
 * Plugin constants.
 *
 * CLB_ prefix is used across the codebase to avoid collisions with other
 * plugins or themes, as required by the plugin design specification
 * (§22 共存・破壊防止戦略).
 */
if ( ! defined( 'CLB_VERSION' ) ) {
	define( 'CLB_VERSION', '0.2.3' );
}

if ( ! defined( 'CLB_PLUGIN_FILE' ) ) {
	define( 'CLB_PLUGIN_FILE', __FILE__ );
}

if ( ! defined( 'CLB_PLUGIN_DIR' ) ) {
	define( 'CLB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
}

if ( ! defined( 'CLB_PLUGIN_URL' ) ) {
	define( 'CLB_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
}

if ( ! defined( 'CLB_PLUGIN_BASENAME' ) ) {
	define( 'CLB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
}

if ( ! defined( 'CLB_MIN_PHP_VERSION' ) ) {
	define( 'CLB_MIN_PHP_VERSION', '8.1' );
}

if ( ! defined( 'CLB_MIN_WP_VERSION' ) ) {
	define( 'CLB_MIN_WP_VERSION', '6.6' );
}

/*
 * Composer autoloader.
 *
 * Required for PSR-4 classes under the CommerceLinkBuilder\ namespace,
 * including the Bootstrap\Plugin class wired up below. If the vendor
 * directory is missing we fail fast with an admin notice rather than
 * letting the Plugin class fatal on an undefined symbol.
 */
$clb_autoload = CLB_PLUGIN_DIR . 'vendor/autoload.php';
if ( is_readable( $clb_autoload ) ) {
	require_once $clb_autoload;
} else {
	add_action(
		'admin_notices',
		static function (): void {
			if ( ! current_user_can( 'activate_plugins' ) ) {
				return;
			}
			echo '<div class="notice notice-error"><p>';
			// phpcs:disable Generic.Files.LineLength.TooLong -- Single literal required by WordPress.WP.I18n.NonSingularStringLiteralText.
			echo esc_html__(
				'Commerce Link Builder: Composer dependencies are not installed. Run `composer install` in the plugin directory.',
				'commerce-link-builder'
			);
			// phpcs:enable Generic.Files.LineLength.TooLong
			echo '</p></div>';
		}
	);
	return;
}

/**
 * Plugin bootstrap singleton accessor.
 *
 * Returns the single Plugin instance owned by this plugin file. Creating
 * the instance lazily keeps the entry file cheap to load in contexts
 * that only need the plugin constants (for example uninstall.php or
 * PHPStan's bootstrap).
 *
 * The instance is cached in a function-static variable rather than a
 * global so that test code can observe and assert on a single canonical
 * object without having to reach into $GLOBALS.
 */
function clb_plugin(): Plugin {
	static $instance = null;

	if ( $instance === null ) {
		$instance = new Plugin( CLB_PLUGIN_FILE );
	}

	return $instance;
}

/*
 * Activation / deactivation hooks.
 *
 * Delegated to instance methods on the Plugin class so that activation
 * logic can evolve without touching this entry file. The callbacks
 * resolve the singleton at call time rather than capturing it in the
 * closure, so activation works correctly even when WordPress loads this
 * file before the autoloader has registered further classes.
 */
register_activation_hook(
	__FILE__,
	static function (): void {
		clb_plugin()->onActivate();
	}
);

/*
 * Load the plugin's translation catalogue (.mo) on init.
 *
 * KI-018 ja seed lives at `languages/commerce-link-builder-ja.mo`;
 * WordPress picks the right file based on the active locale (e.g.
 * ja_JP -> `commerce-link-builder-ja.mo`). The text domain matches
 * the plugin header `Text Domain: commerce-link-builder` so every
 * `__()` / `esc_html__()` / `esc_attr__()` call in the plugin
 * resolves through the loaded catalogue.
 */
add_action(
	'init',
	static function (): void {
		if ( ! function_exists( 'load_plugin_textdomain' ) ) {
			return;
		}
		load_plugin_textdomain(
			'commerce-link-builder',
			false,
			dirname( CLB_PLUGIN_BASENAME ) . '/languages'
		);
	}
);

register_deactivation_hook(
	__FILE__,
	static function (): void {
		// Phase 2 KI-003 / KI-004 / KI-006 follow-up: clear the
		// SyncCronServiceProvider's scheduled cron events directly here
		// because plugins_loaded (and therefore
		// SyncCronServiceProvider::boot()) does not fire during the
		// deactivation request, so the provider instance returned by
		// clb_plugin()->registry() may not be hydrated. Hook names are
		// duplicated as literals to avoid loading the provider class
		// on a request that is about to deactivate the plugin; they
		// must stay in sync with
		// SyncCronServiceProvider::HOOK_DISPATCH_JOBS /
		// HOOK_PERIODIC_SYNC / HOOK_AUDIT_RETENTION_PURGE.
		if ( function_exists( 'wp_clear_scheduled_hook' ) ) {
			wp_clear_scheduled_hook( 'clb_cron_dispatch_jobs' );
			wp_clear_scheduled_hook( 'clb_cron_periodic_sync' );
			wp_clear_scheduled_hook( 'clb_cron_audit_retention_purge' );
		}

		clb_plugin()->onDeactivate();
	}
);

/*
 * Kick off the bootstrap pipeline.
 *
 * Plugin::boot() schedules the real work (environment check + service
 * provider registration) on `plugins_loaded`, and fires the `clb_loaded`
 * action once every provider has booted. Integrators should hook
 * `clb_loaded` rather than `plugins_loaded` directly so they only run
 * when the environment check has passed.
 *
 * The PersistenceServiceProvider binds the Phase 2 persistence
 * collaborators (option store, schema version repository, table name
 * resolver, schema installer, migration runner) and subscribes the
 * migration runner to the `clb_activate` action fired by
 * Plugin::onActivate(). The global $wpdb is forwarded so the resolver
 * and charset/collation clause pick up the host site's values; on the
 * activation request WordPress has already populated $wpdb by the
 * time the plugin main file loads.
 */
global $wpdb;
clb_plugin()
	->withProviders(
		array(
			new PersistenceServiceProvider( isset( $wpdb ) && is_object( $wpdb ) ? $wpdb : null ),
			new CacheServiceProvider(
				null,
				null,
				null,
				null,
				null,
				null,
				isset( $wpdb ) && is_object( $wpdb ) ? $wpdb : null
			),
			new RenderServiceProvider( CLB_PLUGIN_DIR ),
			new RestServiceProvider(),
			new ProviderServiceProvider(),
			new ProductsServiceProvider(),
			new EditorServiceProvider(),
			new SyncServiceProvider(),
			new SyncJobsServiceProvider(),
			new LogsServiceProvider(),
			new AuditServiceProvider(),
			new DiagnosticsServiceProvider(),
			new SettingsServiceProvider(),
			new AdminServiceProvider(),
			new BlockServiceProvider( CLB_PLUGIN_DIR ),
			new SyncCronServiceProvider(),
			new AdminCapabilityActivator(),
		)
	)
	->boot();
