<?php
/**
 * The core plugin class.
 *
 * Singleton coordinator. Holds the hook loader, sets the locale,
 * and will register admin, public, and API hooks as those components
 * come online in later build steps.
 *
 * @package VMPlayer
 */

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

/**
 * Class VMPlayer
 *
 * Main plugin coordinator. Singleton.
 */
final class VMPlayer {

    /**
     * Singleton instance.
     *
     * @var VMPlayer|null
     */
    private static $instance = null;

    /**
     * Hook loader.
     *
     * @var VMPlayer_Loader
     */
    protected $loader;

    /**
     * Plugin slug, used as text domain.
     *
     * @var string
     */
    protected $plugin_slug;

    /**
     * Plugin version.
     *
     * @var string
     */
    protected $version;

    /**
     * Get the singleton instance.
     *
     * @return VMPlayer
     */
    public static function get_instance() {
        if ( null === self::$instance ) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    /**
     * Constructor. Private to enforce singleton.
     */
    private function __construct() {
        $this->version     = defined( 'VMPLAYER_VERSION' ) ? VMPLAYER_VERSION : '1.0.0';
        $this->plugin_slug = defined( 'VMPLAYER_SLUG' ) ? VMPLAYER_SLUG : 'vmplayer';
        $this->loader      = new VMPlayer_Loader();

        $this->load_dependencies();
        $this->set_locale();
        $this->define_admin_hooks();
        $this->define_api_hooks();
        $this->define_public_hooks();
    }

    /**
     * Require all class files the plugin depends on at runtime.
     * Loader and main class are already required by the bootstrap.
     *
     * @return void
     */
    private function load_dependencies() {
        require_once VMPLAYER_PLUGIN_DIR . 'includes/class-vmplayer-settings.php';

        // API layer: metadata orchestrator, protocol handlers, artwork
        // sources, and public AJAX router. Loaded in both admin and
        // frontend context because the admin test button and shortcode
        // both need them.
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-metadata.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-shoutcast.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-icecast.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-hls.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-itunes.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-deezer.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-musicbrainz.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/api/class-vmplayer-api.php';

        // Frontend layer: public asset handler and shortcode. Loaded
        // in both contexts so admins previewing pages still get the
        // frontend rendering.
        require_once VMPLAYER_PLUGIN_DIR . 'includes/frontend/class-vmplayer-public.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/frontend/class-vmplayer-shortcode.php';
        require_once VMPLAYER_PLUGIN_DIR . 'includes/frontend/class-vmplayer-popout.php';

        if ( is_admin() ) {
            require_once VMPLAYER_PLUGIN_DIR . 'includes/admin/class-vmplayer-admin.php';
        }
    }

    /**
     * Register admin-only hooks. No-op outside admin context.
     *
     * @return void
     */
    private function define_admin_hooks() {
        if ( ! is_admin() ) {
            return;
        }

        $admin = new VMPlayer_Admin( $this->plugin_slug, $this->version );

        // Menu and asset enqueuing.
        $this->loader->add_action( 'admin_menu', $admin, 'add_menu_page' );
        $this->loader->add_action( 'admin_enqueue_scripts', $admin, 'enqueue_admin_assets' );

        // AJAX endpoints. All require manage_options capability.
        $this->loader->add_action( 'wp_ajax_vmplayer_save_settings', $admin, 'ajax_save_settings' );
        $this->loader->add_action( 'wp_ajax_vmplayer_save_station', $admin, 'ajax_save_station' );
        $this->loader->add_action( 'wp_ajax_vmplayer_delete_station', $admin, 'ajax_delete_station' );
        $this->loader->add_action( 'wp_ajax_vmplayer_reorder_stations', $admin, 'ajax_reorder_stations' );
        $this->loader->add_action( 'wp_ajax_vmplayer_test_stream', $admin, 'ajax_test_stream' );
    }

    /**
     * Register public AJAX endpoints. Both wp_ajax_ (logged in) and
     * wp_ajax_nopriv_ (visitors) variants get registered so listeners
     * can use the player whether or not they have a WordPress account.
     *
     * @return void
     */
    private function define_api_hooks() {
        $api = new VMPlayer_API( $this->version );

        // Metadata: current track for a station.
        $this->loader->add_action( 'wp_ajax_vmplayer_get_metadata', $api, 'ajax_get_metadata' );
        $this->loader->add_action( 'wp_ajax_nopriv_vmplayer_get_metadata', $api, 'ajax_get_metadata' );

        // Artwork: standalone MusicBrainz lookup.
        $this->loader->add_action( 'wp_ajax_vmplayer_get_artwork', $api, 'ajax_get_artwork' );
        $this->loader->add_action( 'wp_ajax_nopriv_vmplayer_get_artwork', $api, 'ajax_get_artwork' );
    }

    /**
     * Register frontend (public-facing) hooks. Runs regardless of context
     * so the shortcode and its assets are available on any page.
     *
     * @return void
     */
    private function define_public_hooks() {
        $public    = new VMPlayer_Public( $this->plugin_slug, $this->version );
        $shortcode = new VMPlayer_Shortcode();
        $popout    = new VMPlayer_Popout( $this->plugin_slug, $this->version );

        // Register assets during wp_enqueue_scripts. Actual enqueuing
        // is deferred until the shortcode fires so pages without the
        // player pay zero asset cost.
        $this->loader->add_action( 'wp_enqueue_scripts', $public, 'register_assets' );

        // Enqueue late so the shortcode has had a chance to run and
        // mark itself as used.
        $this->loader->add_action( 'wp_footer', $public, 'enqueue_assets_if_needed', 5 );

        // Register the [vmplayer] shortcode.
        $this->loader->add_action( 'init', $shortcode, 'register' );

        // Popout window route. The query var makes vmplayer_popout=1
        // recognizable, and template_redirect intercepts requests to
        // render the standalone popout page.
        $this->loader->add_filter( 'query_vars', $popout, 'register_query_var' );
        $this->loader->add_action( 'template_redirect', $popout, 'maybe_render_popout' );
    }

    /**
     * Prevent cloning.
     *
     * @return void
     */
    private function __clone() {}

    /**
     * Prevent unserialization.
     *
     * @return void
     * @throws Exception When unserialization is attempted.
     */
    public function __wakeup() {
        throw new Exception( 'Cannot unserialize VMPlayer singleton.' );
    }

    /**
     * Register locale loading hook.
     *
     * @return void
     */
    private function set_locale() {
        $this->loader->add_action( 'init', $this, 'load_plugin_textdomain' );
    }

    /**
     * Load plugin text domain for translations.
     *
     * @return void
     */
    public function load_plugin_textdomain() {
        load_plugin_textdomain(
            'vmplayer',
            false,
            dirname( VMPLAYER_PLUGIN_BASENAME ) . '/languages/'
        );
    }

    /**
     * Run the loader to register all queued hooks with WordPress.
     *
     * @return void
     */
    public function run() {
        $this->loader->run();
    }

    /**
     * Get the plugin slug.
     *
     * @return string
     */
    public function get_plugin_slug() {
        return $this->plugin_slug;
    }

    /**
     * Get the plugin version.
     *
     * @return string
     */
    public function get_version() {
        return $this->version;
    }

    /**
     * Get the loader instance.
     *
     * @return VMPlayer_Loader
     */
    public function get_loader() {
        return $this->loader;
    }
}
