<?php

/**
* Plugin Name:      Airdrie Media Posts for Bluesky
* Plugin URI:       https://www.airdriemedia.com/airdriemedia-posts-for-bluesky
* Version:          1.4
* Description:      Displays the most recent Bluesky posts
* Author:           Airdrie Media
* Author URI:       https://www.airdriemedia.com
* License:          GPL v2 or later
* License URI:      https://www.gnu.org/licenses/gpl-2.0.html
*/
if ( !defined( 'ABSPATH' ) ) {
    exit;
}
if ( !function_exists( 'bp_fs' ) ) {
    // Create a helper function for easy SDK access.
    function bp_fs() {
        global $bp_fs;
        if ( !isset( $bp_fs ) ) {
            // Include Freemius SDK.
            require_once dirname( __FILE__ ) . '/vendor/freemius/start.php';
            $bp_fs = fs_dynamic_init( array(
                'id'             => '18083',
                'slug'           => 'bluesky-posts',
                'type'           => 'plugin',
                'public_key'     => 'pk_61a495a7c3fde174fc1fa00ef478b',
                'is_premium'     => false,
                'premium_suffix' => 'Pro',
                'has_addons'     => false,
                'has_paid_plans' => true,
                'trial'          => array(
                    'days'               => 7,
                    'is_require_payment' => false,
                ),
                'menu'           => array(
                    'support' => false,
                ),
                'is_live'        => true,
            ) );
        }
        return $bp_fs;
    }

    // Init Freemius.
    bp_fs();
    // Signal that SDK was initiated.
    do_action( 'bp_fs_loaded' );
}
if ( !defined( 'AMBLUESKY_PLUGIN_VERSION' ) ) {
    define( 'AMBLUESKY_PLUGIN_VERSION', '1.4' );
}
if ( !class_exists( 'amBluesky' ) ) {
    class amBluesky {
        private $username;

        private $app_password;

        private $auth_url = "https://bsky.social/xrpc/com.atproto.server.createSession";

        private $feed_url = "https://bsky.social/xrpc/app.bsky.feed.getAuthorFeed";

        private $access_token;

        private $user_did;

        private $options;

        public function __construct( $file ) {
            add_shortcode( 'bluesky-posts', [$this, 'amBluesky_main'] );
            add_action( 'admin_menu', array($this, 'add_settings_page') );
            add_action( 'admin_init', array($this, 'register_settings') );
            $this->options = get_option( 'ambluesky_options' );
            add_action( 'wp_enqueue_scripts', [$this, 'ambluesky_enqueue_stylesheet'], 10 );
            // Register post-to-Bluesky hook only if premium is active
            if ( $this->is_premium() ) {
                add_action(
                    'publish_post',
                    [$this, 'post_excerpt_to_bluesky'],
                    10,
                    1
                );
            }
        }

        /**
         * Check if premium features are available
         *
         * @return bool
         */
        private function is_premium() {
            return function_exists( 'bp_fs' ) && bp_fs()->can_use_premium_code__premium_only();
        }

        public function getOptions() {
            return $this->options;
        }

        public function getOption( $key ) {
            return ( isset( $this->options[$key] ) ? $this->options[$key] : null );
        }

        public function updateOption( $key, $value ) {
            if ( is_array( $this->options ) ) {
                $this->options[$key] = $value;
                update_option( 'ambluesky_options', $this->options );
            }
        }

        public function ambluesky_enqueue_stylesheet() {
            wp_enqueue_style(
                'ambluesky-plugin-style',
                plugin_dir_url( __FILE__ ) . 'includes/bluesky-posts-style.css',
                false,
                AMBLUESKY_PLUGIN_VERSION
            );
        }

        private function authenticate() {
            $this->username = $this->getOption( "username" );
            $this->app_password = $this->getOption( "apppwd" );
            $auth_data = json_encode( [
                "identifier" => $this->username,
                "password"   => $this->app_password,
            ] );
            $auth_response = $this->api_request( $this->auth_url, $auth_data );
            if ( !$auth_response || !isset( $auth_response['accessJwt'] ) || !isset( $auth_response['did'] ) ) {
                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
                    error_log( "Bluesky auth failed: " . json_encode( $auth_response ) );
                }
                return false;
            }
            $this->access_token = $auth_response['accessJwt'];
            $this->user_did = $auth_response['did'];
            return true;
        }

        public function insertHyperlinks( $text, $facets ) {
            if ( !$facets || !is_array( $facets ) ) {
                return htmlspecialchars( $text, ENT_QUOTES, 'UTF-8' );
            }
            // Sort facets by index position to avoid incorrect replacements
            usort( $facets, function ( $a, $b ) {
                return $b['index']['byteStart'] - $a['index']['byteStart'];
            } );
            // Process facets to insert hyperlinks
            foreach ( $facets as $facet ) {
                if ( isset( $facet['features'] ) ) {
                    foreach ( $facet['features'] as $feature ) {
                        if ( $feature['$type'] === 'app.bsky.richtext.facet#link' && isset( $feature['uri'] ) ) {
                            $url = htmlspecialchars( $feature['uri'], ENT_QUOTES, 'UTF-8' );
                            $start = $facet['index']['byteStart'];
                            $end = $facet['index']['byteEnd'];
                            $text = html_entity_decode( $text );
                            $linkText = mb_substr(
                                $text,
                                $start,
                                $end - $start,
                                'UTF-8'
                            );
                            $linkHtml = '<a href="' . $url . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars( $linkText, ENT_QUOTES, 'UTF-8' ) . '</a>';
                            $text = mb_substr(
                                $text,
                                0,
                                $start,
                                'UTF-8'
                            ) . $linkHtml . mb_substr(
                                $text,
                                $end,
                                null,
                                'UTF-8'
                            );
                        }
                    }
                }
            }
            return $text;
        }

        /**
         * Post excerpt to Bluesky when a WordPress post is published
         * Premium feature only
         *
         * @param int $post_id Post ID
         */
        public function post_excerpt_to_bluesky( $post_id ) {
            // Check if feature is enabled
            if ( $this->getOption( "postExcerpt" ) != 1 ) {
                return;
            }
            // Prevent duplicate posts during autosave
            if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
                return;
            }
            // Check if this is a revision
            if ( wp_is_post_revision( $post_id ) ) {
                return;
            }
            // Get post object
            $post = get_post( $post_id );
            // Only proceed for published posts
            if ( $post->post_status !== 'publish' ) {
                return;
            }
            // Check if already posted to Bluesky (prevent duplicate posts on update)
            $already_posted = get_post_meta( $post_id, '_bluesky_posted', true );
            if ( $already_posted ) {
                return;
            }
            // Authenticate
            if ( !$this->authenticate() ) {
                error_log( 'Bluesky: Authentication failed for post ID ' . $post_id );
                return;
            }
            // Get and clean excerpt
            $excerpt = trim( wp_strip_all_tags( get_the_excerpt( $post_id ) ) );
            $link = get_permalink( $post_id );
            // Calculate max lengths
            $max_total_length = 300;
            $link_len = strlen( $link ) + 3;
            // +3 for ' | '
            $max_excerpt_length = $max_total_length - $link_len;
            // Truncate excerpt if necessary
            if ( mb_strlen( $excerpt ) > $max_excerpt_length ) {
                $excerpt = mb_substr( $excerpt, 0, $max_excerpt_length - 1 ) . '…';
            }
            $final_text = $excerpt . ' | ' . $link;
            // Prepare post data
            $post_data = [
                'repo'       => $this->user_did,
                'collection' => 'app.bsky.feed.post',
                'record'     => [
                    '$type'     => 'app.bsky.feed.post',
                    'text'      => $final_text,
                    'createdAt' => gmdate( 'c' ),
                ],
            ];
            // Send to Bluesky
            $post_response = wp_remote_post( 'https://bsky.social/xrpc/com.atproto.repo.createRecord', [
                'headers' => [
                    'Content-Type'  => 'application/json',
                    'Authorization' => 'Bearer ' . $this->access_token,
                ],
                'body'    => json_encode( $post_data ),
            ] );
            // Handle response
            if ( is_wp_error( $post_response ) ) {
                error_log( 'Bluesky post failed for post ID ' . $post_id . ': ' . $post_response->get_error_message() );
                return;
            }
            $response_body = wp_remote_retrieve_body( $post_response );
            $response_data = json_decode( $response_body, true );
            if ( !empty( $response_data['uri'] ) ) {
                // Mark post as shared to prevent duplicates
                update_post_meta( $post_id, '_bluesky_posted', true );
                update_post_meta( $post_id, '_bluesky_uri', $response_data['uri'] );
                error_log( 'Bluesky post successful for post ID ' . $post_id . ': ' . $response_data['uri'] );
            } else {
                error_log( 'Bluesky post may have failed for post ID ' . $post_id . ': ' . $response_body );
            }
        }

        public function amBluesky_main( $atts = [] ) {
            $limit = 5;
            if ( array_key_exists( "username", $atts ) ) {
                $this->username = $atts['username'];
            }
            if ( array_key_exists( "appwd", $atts ) ) {
                $this->app_password = $atts['appwd'];
            }
            if ( array_key_exists( "apppwd", $atts ) ) {
                $this->app_password = $atts['apppwd'];
            }
            if ( array_key_exists( "limit", $atts ) ) {
                if ( is_numeric( $atts['limit'] ) ) {
                    $limit = $atts['limit'];
                }
            }
            if ( !$this->authenticate() ) {
                return '<p class="ambluesky-error">Unable to connect to Bluesky. Please check your credentials.</p>';
            }
            $feed_response = $this->api_request( "{$this->feed_url}?actor={$this->username}&limit={$limit}", null, $this->access_token );
            if ( !$feed_response || !isset( $feed_response['feed'][0]['post'] ) ) {
                return sprintf( '<p class="ambluesky-error">No posts found for user %s.</p>', esc_html( $this->username ) );
            }
            $authorDisplayName = $feed_response['feed'][0]['post']['author']['displayName'];
            $h = "<!-- AirdrieMedia Posts for Bluesky BEGIN -->";
            $h .= "<div id='amBlueskyDIV'>";
            if ( $this->getOption( "showDisplayName" ) == 1 ) {
                $h .= "<div id='amBlueskyDisplayName'>The latest from " . htmlspecialchars( $authorDisplayName, ENT_QUOTES, 'UTF-8' ) . " on Bluesky </div>";
            }
            $postcount = 0;
            if ( !empty( $feed_response['feed'] ) ) {
                $h .= "<hr>";
            }
            foreach ( $feed_response['feed'] as $feedItem ) {
                $postcount++;
                $post = $feedItem['post'];
                $text = $post['record']['text'];
                $facets = ( isset( $post['record']['facets'] ) ? $post['record']['facets'] : [] );
                $isoTimestamp = $post['record']['createdAt'];
                $likeCount = $post['likeCount'];
                $repostCount = $post['repostCount'];
                $replyCount = $post['replyCount'];
                $quoteCount = $post['quoteCount'];
                $text = nl2br( htmlspecialchars( $text ) );
                if ( $this->getOption( "makeLinks" ) == 1 && !empty( $facets ) ) {
                    $text = $this->insertHyperlinks( $text, $facets );
                }
                $handle = $post['author']['handle'];
                $uriParts = explode( '/', $post['uri'] );
                $postId = end( $uriParts );
                $postLink = "https://bsky.app/profile/{$handle}/post/{$postId}";
                $date = new DateTime($isoTimestamp);
                $date->setTimezone( new DateTimeZone('America/New_York') );
                $timestamp = $date->format( 'l, F j, g:i A' );
                $images = [];
                if ( isset( $post['record']['embed']['$type'] ) && $post['record']['embed']['$type'] === "app.bsky.embed.images" ) {
                    foreach ( $post['record']['embed']['images'] as $image ) {
                        $guid = $image['image']['ref']['$link'];
                        $cdn_url = "https://bsky.social/xrpc/com.atproto.sync.getBlob?did={$this->user_did}&cid={$guid}";
                        $images[] = $cdn_url;
                    }
                }
                $h .= "<div class='amBlueskyPost' id='amBlueskyPost" . htmlspecialchars( $postcount, ENT_QUOTES, 'UTF-8' ) . "'>";
                $h .= "<div class='amBlueskyTimestamp'>" . htmlspecialchars( $timestamp, ENT_QUOTES, 'UTF-8' ) . "</div>";
                $h .= "<div class='amBlueskyContent'>" . $text . "</div>";
                if ( !empty( $images ) ) {
                    $imageCount = 0;
                    $h .= "<div class='amBlueskyImageGroup'>";
                    foreach ( $images as $img ) {
                        $imageCount++;
                        $h .= "<img class='amBlueskyImage' alt='image belonging to Bluesky post' id='amBlueskyImage{$imageCount}' src='" . htmlspecialchars( $img, ENT_QUOTES, 'UTF-8' ) . "'/>";
                    }
                    $h .= "</div>";
                }
                $h .= "<div class='amBlueskyCounts'>";
                if ( $this->getOption( "showLikes" ) == 1 ) {
                    $h .= "<span class='dashicons dashicons-thumbs-up' title='Likes'></span>" . htmlspecialchars( $likeCount, ENT_QUOTES, 'UTF-8' );
                }
                if ( $this->getOption( "showReplies" ) == 1 ) {
                    $h .= " <span class='dashicons dashicons-format-chat' title='Replies'></span>" . htmlspecialchars( $replyCount, ENT_QUOTES, 'UTF-8' );
                }
                if ( $this->getOption( "showReposts" ) == 1 ) {
                    $h .= " <span class='dashicons dashicons-controls-repeat' title='Reposts'></span>" . htmlspecialchars( $repostCount, ENT_QUOTES, 'UTF-8' );
                }
                if ( $this->getOption( "showQuotes" ) == 1 ) {
                    $h .= " <span class='dashicons dashicons-format-quote' title='Quotes'></span> " . htmlspecialchars( $quoteCount, ENT_QUOTES, 'UTF-8' );
                }
                $h .= "</div>";
                $h .= "<div class='amBlueskyLink'>";
                $h .= "<a href='" . htmlspecialchars( $postLink, ENT_QUOTES, 'UTF-8' ) . "' target='_blank'>view in Bluesky</a>";
                $h .= "</div>";
                $h .= "</div>";
                $h .= "<hr>";
            }
            $h .= "</div>";
            $h .= "<!-- AirdrieMedia Posts for Bluesky END -->";
            return $h;
        }

        private function api_request( $url, $data = null, $token = null ) {
            $headers = array(
                'Content-Type' => 'application/json',
            );
            if ( $token ) {
                $headers['Authorization'] = "Bearer {$token}";
            }
            $args = array(
                'headers' => $headers,
            );
            if ( $data ) {
                $args['body'] = $data;
                $args['method'] = 'POST';
                $response = wp_remote_post( $url, $args );
            } else {
                $response = wp_remote_get( $url, $args );
            }
            if ( is_wp_error( $response ) ) {
                return false;
            }
            $body = wp_remote_retrieve_body( $response );
            return json_decode( $body, true );
        }

        public function add_settings_page() {
            add_options_page(
                'Bluesky Posts Options',
                'Bluesky Posts',
                'manage_options',
                'bluesky-posts',
                array($this, 'create_settings_page')
            );
        }

        public function create_settings_page() {
            ?>
            <div class="wrap">
                <h1>Airdrie Media posts for Bluesky: Settings</h1>
                <p>Thank you for using <i>Airdrie Media posts for Bluesky</i>! For more information on this plugin, please see the <a href="https://www.airdriemedia.com/airdriemedia-posts-for-bluesky/">documentation</a>.</p>
                <form method="post" action="options.php">
                <input id="ppAction" type="hidden" name="custom_action" value="">
                    <?php 
            settings_fields( 'ambluesky_options_group' );
            do_settings_sections( 'ambluesky' );
            submit_button( "Update Options" );
            ?>
                </form>
            </div>
            <?php 
        }

        public function register_settings() {
            register_setting( 'ambluesky_options_group', 'ambluesky_options', array(
                'sanitize_callback' => array($this, 'sanitize_options'),
            ) );
            add_settings_section(
                'settings_section',
                'Configuration Settings',
                array($this, 'settings_section_callback'),
                'ambluesky'
            );
            add_settings_field(
                'username',
                'Bluesky User Name',
                array($this, 'username_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'app_password',
                'Bluesky App Password',
                array($this, 'apppwd_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_showDisplayName',
                'Display Options',
                array($this, 'cb_showDisplayName_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_showImages',
                '',
                array($this, 'cb_showImages_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_showLikes',
                '',
                array($this, 'cb_showLikes_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_showReplies',
                '',
                array($this, 'cb_showReplies_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_showReposts',
                '',
                array($this, 'cb_showReposts_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_showQuotes',
                '',
                array($this, 'cb_showQuotes_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_makeLinks',
                '',
                array($this, 'cb_makeLinks_callback'),
                'ambluesky',
                'settings_section'
            );
            add_settings_field(
                'cb_postExcerptName',
                'Posting to Bluesky',
                array($this, 'cb_postExcerpt_callback'),
                'ambluesky',
                'settings_section'
            );
        }

        public function sanitize_options( $input ) {
            if ( isset( $input['username'] ) ) {
                $input['username'] = sanitize_text_field( $input['username'] );
            }
            if ( isset( $input['apppwd'] ) ) {
                $input['apppwd'] = sanitize_text_field( $input['apppwd'] );
            }
            $checkbox_fields = array(
                'showDisplayName',
                'showImages',
                'showLikes',
                'showReplies',
                'showReposts',
                'showQuotes',
                'makeLinks',
                'postExcerpt'
            );
            foreach ( $checkbox_fields as $field ) {
                $input[$field] = ( isset( $input[$field] ) && $input[$field] ? 1 : 0 );
            }
            return $input;
        }

        public function settings_section_callback() {
            ?>
            <p><i>The only required settings are <b>Bluesky User Name</b> and <b>Bluesky App Password</b>. The rest of the controls affect the display of the posts. See <a href="https://www.airdriemedia.com/airdriemedia-posts-for-bluesky/">documentation</a> for details on all of the settings.</i></p>
            <?php 
        }

        public function username_callback() {
            ?>
            <input type="text" name="ambluesky_options[username]" value="<?php 
            echo esc_attr( $this->getOption( 'username' ) );
            ?>" />
            <?php 
        }

        public function apppwd_callback() {
            ?>
            <input type="password" name="ambluesky_options[apppwd]" value="<?php 
            echo esc_attr( $this->getOption( 'apppwd' ) );
            ?>" />
            <?php 
        }

        /**
         * Render a premium checkbox field
         *
         * @param string $key   Option key
         * @param string $label Checkbox label
         */
        private function render_premium_checkbox( $key, $label ) {
            $setVal = $this->getOption( $key );
            $is_premium = $this->is_premium();
            ?>
            <input type="checkbox" id="cb_<?php 
            echo esc_attr( $key );
            ?>" name="ambluesky_options[<?php 
            echo esc_attr( $key );
            ?>]" value="1"
            <?php 
            checked( 1, $setVal );
            ?>
            <?php 
            echo ( !$is_premium ? 'disabled' : '' );
            ?> />
            <label for="cb_<?php 
            echo esc_attr( $key );
            ?>"> <?php 
            echo esc_html( $label );
            ?></label>
            <?php 
            if ( !$is_premium ) {
                ?>
                <p><em style="color: red;">Upgrade to Pro for this feature</em></p>
            <?php 
            }
        }

        public function cb_showDisplayName_callback() {
            $this->render_premium_checkbox( 'showDisplayName', 'Show display name of account' );
        }

        public function cb_showReplies_callback() {
            $this->render_premium_checkbox( 'showReplies', 'Show "reply" count' );
        }

        public function cb_showReposts_callback() {
            $this->render_premium_checkbox( 'showReposts', 'Show "repost" count' );
        }

        public function cb_showQuotes_callback() {
            $this->render_premium_checkbox( 'showQuotes', 'Show "quote" count' );
        }

        public function cb_makeLinks_callback() {
            $this->render_premium_checkbox( 'makeLinks', 'Make links active' );
        }

        public function cb_showLikes_callback() {
            $setVal = $this->getOption( 'showLikes' );
            ?>
            <input type="checkbox" id="cb_showLikes" name="ambluesky_options[showLikes]" value="1"
            <?php 
            checked( 1, $setVal );
            ?> />
            <label for="cb_showLikes"> Show "like" count</label>
            <?php 
        }

        public function cb_showImages_callback() {
            $this->render_premium_checkbox( 'showImages', 'Show images in posts' );
        }

        public function cb_postExcerpt_callback() {
            $setVal = $this->getOption( 'postExcerpt' );
            $is_premium = $this->is_premium();
            ?>
            <input type="checkbox" id="cb_postExcerpt" name="ambluesky_options[postExcerpt]" value="1"
            <?php 
            checked( 1, $setVal );
            ?>
            <?php 
            echo ( !$is_premium ? 'disabled' : '' );
            ?> />
            <label for="cb_postExcerpt"> Upon publishing a WordPress post here, create a Bluesky post using the excerpt content from the WordPress post.</label>
            <?php 
            if ( !$is_premium ) {
                ?>
                <p><em style="color: red;">Upgrade to Pro for this feature</em></p>
            <?php 
            }
        }

    }

    // class declaration
}
// if !class
$amBluesky = new amBluesky(__FILE__);