<?php
if (!defined('ABSPATH')) exit;

class Calliope_SP_Bluesky {

    public function test_connection() {
        $handle = get_option('calliope_sp_bsky_handle');
        $password = get_option('calliope_sp_bsky_password');

        if (!$handle || !$password) {
            return new WP_Error('cfg', 'Missing Bluesky handle or password.');
        }

        $session = $this->bsky_req('com.atproto.server.createSession', 'POST', [
            'identifier' => $handle,
            'password' => $password,
        ]);

        if (is_wp_error($session)) {
            return $session;
        }

        if (empty($session['accessJwt']) || empty($session['did'])) {
            return new WP_Error('bsky', 'Invalid response from Bluesky.');
        }

        return true;
    }

    public function publish($data) {
        $handle = get_option('calliope_sp_bsky_handle');
        $password = get_option('calliope_sp_bsky_password');

        if (!$handle || !$password) {
            return new WP_Error('cfg', 'Missing Bluesky configuration.');
        }

        $session = $this->bsky_req('com.atproto.server.createSession', 'POST', [
            'identifier' => $handle,
            'password'   => $password,
        ]);
        if (is_wp_error($session)) return $session;

        $jwt = $session['accessJwt'] ?? '';
        $did = $session['did'] ?? '';
        if (!$jwt || !$did) {
            return new WP_Error('bsky', 'Could not sign in to Bluesky.');
        }

        $text = $this->format_text_clean_tags($data, 290);
        $facets = $this->gen_facets($text);

        $record = [
            '$type' => 'app.bsky.feed.post',
            'text' => $text,
            'createdAt' => gmdate('c'),
        ];
        if (!empty($facets)) {
            $record['facets'] = $facets;
        }

        // Optional image
        $upload = null;
        if (!empty($data['image_b64'])) {
            $upload = $this->upload_blob($jwt, $data['image_b64']);
        }
        if (is_array($upload) && !empty($upload['blob'])) {
            $image = [
                'alt' => !empty($data['alt_text']) ? (string)$data['alt_text'] : 'Image',
                'image' => $upload['blob'],
            ];

            if (!empty($upload['aspectRatio']) && is_array($upload['aspectRatio'])) {
                $image['aspectRatio'] = $upload['aspectRatio'];
            }

            $record['embed'] = [
                '$type' => 'app.bsky.embed.images',
                'images' => [$image],
            ];
        }

        $res = wp_remote_post('https://bsky.social/xrpc/com.atproto.repo.createRecord', [
            'headers' => [
                'Authorization' => 'Bearer ' . $jwt,
                'Content-Type' => 'application/json',
            ],
            'body' => wp_json_encode([
                'repo' => $did,
                'collection' => 'app.bsky.feed.post',
                'record' => $record,
            ]),
            'timeout' => 45,
        ]);

        if (is_wp_error($res)) return $res;

        $code = wp_remote_retrieve_response_code($res);
        $body = json_decode(wp_remote_retrieve_body($res), true);

        if ($code < 200 || $code >= 300) {
            return new WP_Error('bsky', 'Error (' . $code . '): ' . ($body['message'] ?? 'Could not publish.'));
        }

        return true;
    }

    private function upload_blob($jwt, $b64) {
        $parts = explode('base64,', (string)$b64, 2);
        $bin = base64_decode($parts[1] ?? $parts[0]);
        if ($bin === false) return null;

        $aspect_ratio = null;
        if (function_exists('getimagesizefromstring')) {
            $image_info = @getimagesizefromstring($bin);
            if (is_array($image_info) && !empty($image_info[0]) && !empty($image_info[1])) {
                $aspect_ratio = [
                    'width' => (int) $image_info[0],
                    'height' => (int) $image_info[1],
                ];
            }
        }

        $res = wp_remote_post('https://bsky.social/xrpc/com.atproto.repo.uploadBlob', [
            'headers' => [
                'Authorization' => 'Bearer ' . $jwt,
                'Content-Type' => 'image/jpeg',
            ],
            'body' => $bin,
            'timeout' => 45,
        ]);

        if (is_wp_error($res)) return null;
        if (wp_remote_retrieve_response_code($res) !== 200) return null;

        $body = json_decode(wp_remote_retrieve_body($res), true);
        if (empty($body['blob'])) {
            return null;
        }

        return [
            'blob' => $body['blob'],
            'aspectRatio' => $aspect_ratio,
        ];
    }

    private function bsky_req($endpoint, $method, $body) {
        $res = wp_remote_request('https://bsky.social/xrpc/' . $endpoint, [
            'method'  => $method,
            'headers' => ['Content-Type' => 'application/json'],
            'body'    => wp_json_encode($body),
            'timeout' => 30,
        ]);

        if (is_wp_error($res)) return $res;

        $code = wp_remote_retrieve_response_code($res);
        $json = json_decode(wp_remote_retrieve_body($res), true);

        if ($code === 200 && is_array($json)) {
            return $json;
        }

        return new WP_Error('bsky', $json['message'] ?? ('Error HTTP ' . $code));
    }

    /**
     * Format title, content, hashtags, and the link while respecting the limit.
     */
    private function format_text_clean_tags($data, $limit) {
        $title = trim((string) ($data['title'] ?? ''));
        $content = trim((string) ($data['content'] ?? ''));
        $link = trim((string) ($data['link'] ?? ''));
        $tags = !empty($data['hashtags']) ? preg_split('/\s+/', trim((string) $data['hashtags'])) : [];

        $tags_clean = [];
        foreach ($tags as $tag) {
            $tag = trim((string) $tag);
            if ($tag === '') {
                continue;
            }
            $tags_clean[] = $tag;
        }
        $hashtags_line = implode(' ', $tags_clean);

        $fixed_blocks = [];
        if ($title !== '') {
            $fixed_blocks[] = $title;
        }
        if ($hashtags_line !== '') {
            $fixed_blocks[] = $hashtags_line;
        }
        if ($link !== '') {
            $fixed_blocks[] = $link;
        }

        $reserved_length = 0;
        if (!empty($fixed_blocks)) {
            $reserved_length = array_sum(array_map('mb_strlen', $fixed_blocks));
            $reserved_length += count($fixed_blocks);
        }

        $content_limit = max(0, $limit - $reserved_length);
        if ($content !== '' && mb_strlen($content) > $content_limit) {
            $content = $content_limit > 3 ? mb_substr($content, 0, $content_limit - 3) . '...' : '';
        }

        $blocks = [];
        foreach ([$title, $content, $hashtags_line, $link] as $block) {
            if ($block !== '') {
                $blocks[] = $block;
            }
        }

        $text = implode("\n", $blocks);
        if (mb_strlen($text) <= $limit) {
            return $text;
        }

        if ($hashtags_line !== '') {
            $tags_final = [];
            foreach ($tags_clean as $tag) {
                $candidate = implode(' ', array_merge($tags_final, [$tag]));
                $candidate_blocks = [];
                foreach ([$title, $content, $candidate, $link] as $block) {
                    if ($block !== '') {
                        $candidate_blocks[] = $block;
                    }
                }
                $candidate_text = implode("\n", $candidate_blocks);
                if (mb_strlen($candidate_text) <= $limit) {
                    $tags_final[] = $tag;
                } else {
                    break;
                }
            }
            $hashtags_line = implode(' ', $tags_final);
        }

        $blocks = [];
        foreach ([$title, $content, $hashtags_line, $link] as $block) {
            if ($block !== '') {
                $blocks[] = $block;
            }
        }

        return implode("\n", $blocks);
    }

    private function gen_facets($text) {
        $facets = [];

        // Links
        preg_match_all('/(https?:\/\/[^\s]+)/', $text, $m, PREG_OFFSET_CAPTURE);
        foreach ($m[0] as $x) {
            $facets[] = [
                'index' => [
                    'byteStart' => $x[1],
                    'byteEnd' => $x[1] + strlen($x[0]),
                ],
                'features' => [[
                    '$type' => 'app.bsky.richtext.facet#link',
                    'uri' => $x[0],
                ]],
            ];
        }

        // Tags
        preg_match_all('/#([\p{L}\p{M}\p{N}_]+)/u', $text, $m, PREG_OFFSET_CAPTURE);
        foreach ($m[0] as $k => $x) {
            $tag = $m[1][$k][0] ?? '';
            if ($tag === '') continue;

            $facet_tag = $this->normalize_facet_tag($tag);
            if ($facet_tag === '') {
                continue;
            }

            $facets[] = [
                'index' => [
                    'byteStart' => $x[1],
                    'byteEnd' => $x[1] + strlen($x[0]),
                ],
                'features' => [[
                    '$type' => 'app.bsky.richtext.facet#tag',
                    'tag' => $facet_tag,
                ]],
            ];
        }

        return $facets;
    }

    private function normalize_facet_tag($tag) {
        $tag = (string) $tag;

        if (function_exists('remove_accents')) {
            $tag = remove_accents($tag);
        }

        $tag = preg_replace('/[^A-Za-z0-9_]+/', '', $tag);

        return trim((string) $tag);
    }
}


