<?php
/**
 * Frontend (public) functionality for VMPlayer.
 *
 * Registers frontend CSS and JS. Enqueues them only when the
 * shortcode is actually used on the page (via mark_shortcode_used)
 * so pages without the player pay zero asset cost. Localizes the
 * JS config object with all data the frontend player needs.
 *
 * @package VMPlayer
 */

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

/**
 * Class VMPlayer_Public
 */
class VMPlayer_Public {

    /**
     * Plugin slug.
     *
     * @var string
     */
    private $plugin_slug;

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

    /**
     * Whether the shortcode has been rendered on the current request.
     * Toggled by mark_shortcode_used() from the shortcode class.
     * The public enqueue callback checks this before adding scripts.
     *
     * @var bool
     */
    private static $shortcode_used = false;

    /**
     * Constructor.
     *
     * @param string $plugin_slug Plugin slug.
     * @param string $version     Plugin version.
     */
    public function __construct( $plugin_slug, $version ) {
        $this->plugin_slug = $plugin_slug;
        $this->version     = $version;
    }

    /* ------------------------------------------------------------------
     * Asset registration
     * --------------------------------------------------------------- */

    /**
     * Register CSS and JS handles without enqueuing.
     * Registration happens at wp_enqueue_scripts. Actual enqueuing
     * happens later in the shortcode callback so we do not load
     * assets on pages without the player.
     *
     * @return void
     */
    public function register_assets() {
        // Frontend CSS.
        wp_register_style(
            'vmplayer-frontend',
            VMPLAYER_PLUGIN_URL . 'assets/css/vmplayer-frontend.css',
            array(),
            $this->asset_version( 'assets/css/vmplayer-frontend.css' )
        );

        // Popout CSS. Registered so we can enqueue it on the shortcode
        // pages too, giving the kebab menu the styles it needs.
        wp_register_style(
            'vmplayer-popout',
            VMPLAYER_PLUGIN_URL . 'assets/css/vmplayer-popout.css',
            array( 'vmplayer-frontend' ),
            $this->asset_version( 'assets/css/vmplayer-popout.css' )
        );

        // Visualizer JS. Loaded first because the main JS depends on it.
        wp_register_script(
            'vmplayer-visualizer',
            VMPLAYER_PLUGIN_URL . 'assets/js/vmplayer-visualizer.js',
            array(),
            $this->asset_version( 'assets/js/vmplayer-visualizer.js' ),
            true
        );

        // Kebab menu JS. Shared component used by both main and popout.
        wp_register_script(
            'vmplayer-kebab',
            VMPLAYER_PLUGIN_URL . 'assets/js/vmplayer-kebab.js',
            array(),
            $this->asset_version( 'assets/js/vmplayer-kebab.js' ),
            true
        );

        // Main frontend JS.
        wp_register_script(
            'vmplayer-frontend',
            VMPLAYER_PLUGIN_URL . 'assets/js/vmplayer-frontend.js',
            array( 'vmplayer-visualizer', 'vmplayer-kebab' ),
            $this->asset_version( 'assets/js/vmplayer-frontend.js' ),
            true
        );
    }

    /**
     * Mark the shortcode as having been used on this request.
     * Called from the shortcode render callback.
     *
     * @return void
     */
    public static function mark_shortcode_used() {
        self::$shortcode_used = true;
    }

    /**
     * Whether the shortcode has been used.
     *
     * @return bool
     */
    public static function is_shortcode_used() {
        return self::$shortcode_used;
    }

    /**
     * Enqueue frontend assets if the shortcode has been used.
     * Runs late so the shortcode callback (which may fire mid-content)
     * has already had a chance to set the flag.
     *
     * @return void
     */
    public function enqueue_assets_if_needed() {
        if ( ! self::is_shortcode_used() ) {
            return;
        }

        wp_enqueue_style( 'vmplayer-frontend' );
        // Popout CSS carries the kebab menu styles, which the main
        // player uses too. Cheaper than duplicating that block.
        wp_enqueue_style( 'vmplayer-popout' );
        wp_enqueue_script( 'vmplayer-visualizer' );
        wp_enqueue_script( 'vmplayer-kebab' );
        wp_enqueue_script( 'vmplayer-frontend' );

        // Emit config for the frontend as inline JSON attached to the
        // main script handle. We deliberately avoid wp_localize_script
        // here because that helper runs every string value through
        // htmlspecialchars, which turns "Rock & Roll" into
        // "Rock &amp; Roll" and "What's Up" into "What&apos;s Up".
        // The frontend renders these strings via textContent for XSS
        // safety, and textContent does not decode HTML entities, so
        // the encoded values used to show up as literal text in the
        // player. wp_json_encode preserves the original characters
        // and wp_add_inline_script is the WordPress-idiomatic way to
        // attach dynamic data to an enqueued handle without hand
        // rolling a script tag. Runs here so admin-side settings and
        // station changes are always fresh at the moment the page
        // renders.
        wp_add_inline_script(
            'vmplayer-frontend',
            'window.vmplayerConfig = ' . wp_json_encode( $this->build_localized_config() ) . ';',
            'before'
        );

        // Inline the theme colors as CSS custom properties so admin
        // color changes take effect without rebuilding stylesheets.
        wp_add_inline_style(
            'vmplayer-frontend',
            $this->build_theme_css_vars()
        );
    }

    /* ------------------------------------------------------------------
     * Config building
     * --------------------------------------------------------------- */

    /**
     * Assemble the JS config object exposed as window.vmplayerConfig.
     *
     * @return array
     */
    private function build_localized_config() {
        $settings = VMPlayer_Settings::get_settings();
        $stations = VMPlayer_Settings::get_stations();

        return array(
            'ajaxUrl'             => admin_url( 'admin-ajax.php' ),
            'nonce'               => VMPlayer_API::create_nonce(),
            'stations'            => self::sanitize_stations_for_frontend( $stations ),
            'defaultStationIndex' => isset( $settings['default_station'] ) ? (int) $settings['default_station'] : 0,
            'defaultLogoUrl'      => self::get_default_logo_url(),
            'refreshInterval'     => isset( $settings['metadata_refresh_interval'] )
                ? (int) $settings['metadata_refresh_interval']
                : 15,
            'enableVisualizer'    => ! empty( $settings['enable_visualizer'] ),
            'enablePopout'        => ! empty( $settings['enable_popout'] ),
            'autoResume'          => ! empty( $settings['auto_resume'] ),
            'popoutUrl'           => $this->get_popout_url(),
            'supportedFormats'    => array( 'Shoutcast', 'Icecast', 'HLS', 'MP3', 'AAC', 'OGG', 'Opus' ),
            'strings'             => self::get_frontend_strings(),
        );
    }

    /**
     * Return the URL for the packaged default station logo. Used as
     * a fallback whenever a station is saved without a custom logo so
     * the frontend never shows a broken image or an empty spot in
     * the artwork slot.
     *
     * @return string Absolute URL to the default logo PNG.
     */
    public static function get_default_logo_url() {
        return VMPLAYER_PLUGIN_URL . 'assets/images/default-station-logo.png';
    }

    /**
     * Strip anything the frontend does not need from station data.
     * The stream_url and mount URLs must stay because the browser
     * needs to hit them directly. Everything the user configured
     * for display is kept. Missing logos are replaced with the
     * packaged default so downstream template and JS code can rely
     * on logo_url always being a non-empty string.
     *
     * @param array $stations Full stations array from Settings.
     * @return array Stations sanitized for the frontend.
     */
    public static function sanitize_stations_for_frontend( $stations ) {
        $default_logo = self::get_default_logo_url();
        $result       = array();
        foreach ( $stations as $station ) {
            if ( ! is_array( $station ) ) {
                continue;
            }
            $logo_url = isset( $station['logo_url'] ) ? (string) $station['logo_url'] : '';
            if ( '' === trim( $logo_url ) ) {
                $logo_url = $default_logo;
            }
            $result[] = array(
                'id'                  => isset( $station['id'] ) ? (string) $station['id'] : '',
                'name'                => isset( $station['name'] ) ? (string) $station['name'] : '',
                'tagline'             => isset( $station['tagline'] ) ? (string) $station['tagline'] : '',
                'description'         => isset( $station['description'] ) ? (string) $station['description'] : '',
                'logo_url'            => $logo_url,
                'stream_url'          => isset( $station['stream_url'] ) ? (string) $station['stream_url'] : '',
                'stream_type'         => isset( $station['stream_type'] ) ? (string) $station['stream_type'] : 'direct',
                'genre'               => isset( $station['genre'] ) ? (string) $station['genre'] : '',
                'mounts'              => isset( $station['mounts'] ) && is_array( $station['mounts'] ) ? $station['mounts'] : array(),
                'social'              => self::build_social_urls_from_handles(
                    isset( $station['social'] ) && is_array( $station['social'] ) ? $station['social'] : array()
                ),
                // Include the per-station display toggles so the JS
                // knows which features to activate when switching to
                // this station. Without these fields the JS keeps the
                // initial page-load values, which means switching from
                // a station with listener count on to a station with
                // it off would leave the chip stuck visible. Routed
                // through sanitize_bool as a belt-and-braces measure
                // against stale string values from any code path.
                'detect_live'         => VMPlayer_Settings::sanitize_bool(
                    isset( $station['detect_live'] ) ? $station['detect_live'] : false
                ),
                'show_listener_count' => VMPlayer_Settings::sanitize_bool(
                    isset( $station['show_listener_count'] ) ? $station['show_listener_count'] : false
                ),
            );
        }
        return $result;
    }

    /**
     * Expand a station's stored social handles into full profile URLs
     * ready for the frontend to link to. Handles are stored bare in
     * the database so the URL scheme can adapt if a platform changes
     * its links. Legacy keys ('bluesky', 'twitter') are folded into
     * the new ones so old data still renders.
     *
     * @param array $social Handles keyed by platform slug.
     * @return array URLs keyed by platform slug.
     */
    private static function build_social_urls_from_handles( $social ) {
        // Legacy key fold. Old stored data used these key names.
        if ( isset( $social['bluesky'] ) && ! isset( $social['bsky'] ) ) {
            $social['bsky'] = $social['bluesky'];
        }
        if ( isset( $social['twitter'] ) && ! isset( $social['x'] ) ) {
            $social['x'] = $social['twitter'];
        }
        unset( $social['bluesky'], $social['twitter'] );

        $out = array();
        foreach ( VMPlayer_Settings::get_supported_social_platforms() as $platform ) {
            if ( empty( $social[ $platform ] ) ) {
                continue;
            }
            $url = VMPlayer_Settings::build_social_url( $platform, $social[ $platform ] );
            if ( '' !== $url ) {
                $out[ $platform ] = $url;
            }
        }
        return $out;
    }

    /**
     * Translatable strings the frontend player displays.
     *
     * @return array
     */
    private static function get_frontend_strings() {
        return array(
            'onAir'         => esc_html__( 'ON AIR', 'vmplayer' ),
            'nowPlaying'    => esc_html__( 'NOW PLAYING', 'vmplayer' ),
            'liveStream'    => esc_html__( 'Live Stream', 'vmplayer' ),
            'play'          => esc_html__( 'Play', 'vmplayer' ),
            'pause'         => esc_html__( 'Pause', 'vmplayer' ),
            'mute'          => esc_html__( 'Mute', 'vmplayer' ),
            'unmute'        => esc_html__( 'Unmute', 'vmplayer' ),
            'previous'      => esc_html__( 'Previous station', 'vmplayer' ),
            'next'          => esc_html__( 'Next station', 'vmplayer' ),
            'popOut'        => esc_html__( 'POP OUT', 'vmplayer' ),
            'streamingVia'  => esc_html__( 'STREAMING VIA', 'vmplayer' ),
            'followUs'      => esc_html__( 'FOLLOW US', 'vmplayer' ),
            'supports'      => esc_html__( 'Supports:', 'vmplayer' ),
            'volumeDown'    => esc_html__( 'Volume down', 'vmplayer' ),
            'volumeUp'      => esc_html__( 'Volume up', 'vmplayer' ),
            'menu'          => esc_html__( 'More options', 'vmplayer' ),
            'connecting'    => esc_html__( 'Connecting...', 'vmplayer' ),
            'streamError'   => esc_html__( 'Could not connect to stream.', 'vmplayer' ),
            /* translators: 1: current station number, 2: total station count */
            'stationOf'     => esc_html__( 'Station %1$d of %2$d', 'vmplayer' ),
        );
    }

    /**
     * Build the popout URL. Step 5 stubs this to the current page URL
     * with a query parameter. Step 6 will register a rewrite rule to
     * render the popout template at this URL.
     *
     * @return string
     */
    private function get_popout_url() {
        return add_query_arg(
            array( 'vmplayer_popout' => '1' ),
            home_url( '/' )
        );
    }

    /* ------------------------------------------------------------------
     * Theme CSS custom properties
     * --------------------------------------------------------------- */

    /**
     * Build a CSS block that declares theme colors as custom properties
     * on the player container. Injected via wp_add_inline_style so
     * admin color changes flow through without file edits.
     *
     * @return string
     */
    private function build_theme_css_vars() {
        $settings = VMPlayer_Settings::get_settings();
        $primary   = isset( $settings['theme_color_primary'] ) ? $settings['theme_color_primary'] : '#a855f7';
        $secondary = isset( $settings['theme_color_secondary'] ) ? $settings['theme_color_secondary'] : '#3b82f6';

        // Sanitize colors defensively even though Settings already validated.
        $primary   = VMPlayer_Settings::sanitize_hex_color( $primary, '#a855f7' );
        $secondary = VMPlayer_Settings::sanitize_hex_color( $secondary, '#3b82f6' );

        return sprintf(
            '.vmplayer { --vmp-primary: %s; --vmp-secondary: %s; }',
            esc_html( $primary ),
            esc_html( $secondary )
        );
    }

    /* ------------------------------------------------------------------
     * Utility
     * --------------------------------------------------------------- */

    /**
     * Build a filemtime-based version string for an asset. Same pattern
     * as the admin class so cache busts automatically on file changes.
     *
     * @param string $relative_path Path relative to plugin root.
     * @return string
     */
    private function asset_version( $relative_path ) {
        $full_path = VMPLAYER_PLUGIN_DIR . $relative_path;
        if ( file_exists( $full_path ) ) {
            return $this->version . '.' . filemtime( $full_path );
        }
        return $this->version;
    }
}
