<?php
/**
 * Plugin Name: DataStream API Sync
 * Description: Pull any JSON API into WordPress on a schedule and display it with a shortcode, block, or template tag. No code required.
 * Version: 2.1.2
 * Author: Utah Hans
 * Requires at least: 6.0
 * Requires PHP: 7.4
 * License: GPLv3
 * License URI: https://www.gnu.org/licenses/gpl-3.0.html
 * Text Domain: datastream-api-sync
 * Domain Path: /languages
 *
 * @package DataStream
 *
 * @fs_premium_only /includes/class-datastream-oauth.php, /includes/class-datastream-post-mapper.php
 */

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

define( 'DATASTREAM_VERSION', '2.1.2' );
define( 'DATASTREAM_PLUGIN_FILE', __FILE__ );
define( 'DATASTREAM_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
define( 'DATASTREAM_TABLE', 'ds_ingestion_table' );
define( 'DATASTREAM_CRON_HOOK', 'ds_ingestion_event' );

require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-config.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-logger.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-feature-gate.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-parser.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-streams.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-chronicles.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-schema.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-sync-lock.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-health.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-dao.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-engine.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-provider.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-renderer.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-detector.php';
// Premium-only: the free build strips these (see @fs_premium_only in the
// header above), so load them conditionally. Every call site pairs this with
// a class_exists check -- a missing file must degrade, never fatal.
if ( file_exists( DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-oauth.php' ) ) {
	require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-oauth.php';
}

if ( file_exists( DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-post-mapper.php' ) ) {
	require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-post-mapper.php';
}
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-cli.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-shortcodes.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-block.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/datastream-template-tags.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-api.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-admin.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-license.php';
require_once DATASTREAM_PLUGIN_DIR . 'includes/class-datastream-uninstaller.php';

/**
 * Initialize the DataStream orchestrator.
 */
function datastream_run_engine() {
	DataStream_Engine::get_instance();
	new DataStream_API();
	DataStream_Shortcodes::register();
	datastream_register_uninstall_cleanup();

	if ( is_admin() ) {
		new DataStream_Admin();
		DataStream_Health::maybe_repair_cron();
	}
}

/**
 * Registers the uninstall cleanup down whichever path is actually available.
 *
 * There used to be an uninstall.php here. Freemius refuses to accept a build
 * containing one, and it is right to: a main-folder uninstall.php takes
 * precedence over every uninstall hook, so the SDK's own cleanup would
 * silently never run. Its documented replacement is the `after_uninstall`
 * action.
 *
 * Only registering `after_uninstall` would be a trap, though. That action
 * exists solely when the SDK booted, so a build with no credentials -- or one
 * where a customer deleted vendor/freemius -- would clean up nothing, while
 * the readme promises uninstalling removes what the plugin created. So:
 * Freemius' action when Freemius is running, WordPress' own
 * register_uninstall_hook otherwise. Exactly one of the two is ever
 * registered, because both routes end at the same idempotent method.
 */
function datastream_register_uninstall_cleanup() {
	$cleanup  = array( 'DataStream_Uninstaller', 'run' );
	$freemius = DataStream_License::get_instance();

	if ( is_object( $freemius ) && method_exists( $freemius, 'add_action' ) ) {
		$freemius->add_action( 'after_uninstall', $cleanup );
		return;
	}

	register_uninstall_hook( DATASTREAM_PLUGIN_FILE, $cleanup );
}

// Priority 4: the SDK must be booted before anything asks the gate whether
// this site is licensed, and it can only load inside a real WordPress request
// (it calls get_theme_root() at include time), so plugins_loaded is the
// earliest safe point. A no-op when no store is configured.
add_action( 'plugins_loaded', array( 'DataStream_License', 'initialize' ), 4 );
add_action( 'plugins_loaded', 'datastream_run_engine' );
add_action( 'plugins_loaded', 'datastream_maybe_upgrade_schema', 5 );
add_action( 'init', array( 'DataStream_Renderer', 'register_assets' ) );
add_filter( 'swiftqueue_hook_priority_map', 'datastream_swiftqueue_priority' );

/**
 * When SwiftQueue is active, declare the sync as low-priority background
 * work so its priority lanes let checkout and other urgent tasks go first.
 * The filter simply never fires when SwiftQueue is not installed.
 *
 * @param array<string, string> $map Hook => track map.
 * @return array<string, string>
 */
function datastream_swiftqueue_priority( $map ) {
	if ( is_array( $map ) ) {
		$map[ DATASTREAM_CRON_HOOK ] = 'low';
	}

	return $map;
}

DataStream_Block::register();

register_activation_hook( __FILE__, 'datastream_activate' );
register_deactivation_hook( __FILE__, 'datastream_deactivate' );

/**
 * Plugin activation: create schema, defaults, and schedule cron.
 */
function datastream_activate() {
	DataStream_Schema::create_schema();
	DataStream_Config::register_defaults();
	DataStream_Streams::migrate_legacy();
	DataStream_Engine::schedule_sync();
	update_option( 'datastream_db_version', DATASTREAM_VERSION, false );

	// First-run: send the activating admin to the Connect Wizard (single,
	// interactive activations only — never bulk or network activations).
	if ( ! is_network_admin() && ! isset( $_GET['activate-multi'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
		set_transient( 'datastream_activation_redirect', get_current_user_id(), 60 );
	}
}

/**
 * Complete the first-run redirect set during activation.
 */
function datastream_maybe_first_run_redirect() {
	$user_id = (int) get_transient( 'datastream_activation_redirect' );

	if ( $user_id < 1 || get_current_user_id() !== $user_id || wp_doing_ajax() || ! current_user_can( 'manage_options' ) ) {
		return;
	}

	delete_transient( 'datastream_activation_redirect' );
	wp_safe_redirect( admin_url( 'admin.php?page=datastream-settings&tab=streams' ) );
	exit;
}

add_action( 'admin_init', 'datastream_maybe_first_run_redirect' );

/**
 * Plugin deactivation: clear scheduled cron events.
 */
function datastream_deactivate() {
	DataStream_Engine::unschedule_sync();
	DataStream_Sync_Lock::force_release();
}

/**
 * Upgrade schema when the plugin version changes.
 */
function datastream_maybe_upgrade_schema() {
	$installed = get_option( 'datastream_db_version', '0' );

	if ( version_compare( $installed, DATASTREAM_VERSION, '<' ) ) {
		DataStream_Schema::create_schema();
		DataStream_Config::register_defaults();
		DataStream_Streams::migrate_legacy();
		update_option( 'datastream_db_version', DATASTREAM_VERSION, false );
	}
}
