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

class Calliope_SP_Publish_Helper {
    public static function normalize_payload(array $payload, $network = '') {
        $network = sanitize_key((string) $network);

        $payload['title'] = isset($payload['title']) ? sanitize_text_field((string) $payload['title']) : '';
        $payload['content'] = isset($payload['content']) ? self::normalize_multiline_text($payload['content']) : '';
        $payload['link'] = isset($payload['link']) ? esc_url_raw((string) $payload['link']) : '';
        $payload['alt_text'] = isset($payload['alt_text']) ? sanitize_text_field((string) $payload['alt_text']) : '';
        $payload['hashtags_list'] = self::normalize_hashtags_list($payload['hashtags'] ?? '', $network);
        $payload['hashtags'] = self::format_hashtags($payload['hashtags'] ?? '', $network);

        return $payload;
    }

    public static function normalize_hashtags_list($raw_hashtags, $network = '') {
        $raw_hashtags = wp_strip_all_tags((string) $raw_hashtags);
        $raw_hashtags = preg_replace('/[\r\n\t,;]+/u', ' ', $raw_hashtags);
        $raw_hashtags = preg_replace('/\s+/u', ' ', (string) $raw_hashtags);
        $raw_hashtags = trim((string) $raw_hashtags);

        if ($raw_hashtags === '') {
            return [];
        }

        $parts = preg_split('/\s+/u', $raw_hashtags);
        if (!is_array($parts)) {
            return [];
        }

        $normalized = [];
        $seen = [];

        foreach ($parts as $part) {
            $tag_body = self::sanitize_hashtag_body($part, $network);
            if ($tag_body === '') {
                continue;
            }

            $dedupe_key = function_exists('mb_strtolower')
                ? mb_strtolower($tag_body, 'UTF-8')
                : strtolower($tag_body);

            if (isset($seen[$dedupe_key])) {
                continue;
            }

            $normalized[] = '#' . $tag_body;
            $seen[$dedupe_key] = true;
        }

        return $normalized;
    }

    public static function format_hashtags($raw_hashtags, $network = '') {
        $tags = self::normalize_hashtags_list($raw_hashtags, $network);
        if (empty($tags)) {
            return '';
        }

        switch (sanitize_key((string) $network)) {
            case 'tumblr':
                $tumblr_tags = array_map(static function ($tag) {
                    return ltrim((string) $tag, '#');
                }, $tags);
                return implode(',', array_filter($tumblr_tags));

            default:
                return implode(' ', $tags);
        }
    }

    public static function classify_result(array $result) {
        $network = sanitize_key((string) ($result['network'] ?? ''));
        $error_code = strtolower((string) ($result['error_code'] ?? ''));
        $technical_message = trim((string) ($result['error'] ?? ''));
        $classification = [
            'network'           => $network,
            'label'             => self::get_network_label($network),
            'ok'                => !empty($result['ok']),
            'log_status'        => !empty($result['ok']) ? 'success' : 'error',
            'summary_status'    => !empty($result['ok']) ? 'success' : 'error',
            'classification'    => '',
            'retryable'         => array_key_exists('retryable', $result) ? (bool) $result['retryable'] : (isset($result['data']) && is_array($result['data']) && array_key_exists('retryable', $result['data']) ? (bool) $result['data']['retryable'] : false),
            'short_message'     => !empty($result['ok']) ? __('sent', 'calliope-social-publisher') : ($technical_message !== '' ? $technical_message : __('Unknown error', 'calliope-social-publisher')),
            'user_message'      => !empty($result['ok']) ? __('Published successfully', 'calliope-social-publisher') : ($technical_message !== '' ? $technical_message : __('Unknown error', 'calliope-social-publisher')),
            'technical_message' => $technical_message,
        ];

        if (!empty($classification['ok']) && (
            (string) ($result['warning_code'] ?? '') === 'pinterest_link_omitted' ||
            !empty($result['link_omitted'])
        )) {
            $classification['log_status'] = 'warning';
            $classification['summary_status'] = 'warning';
            $classification['classification'] = 'PINTEREST_LINK_OMITTED';
            $classification['retryable'] = false;
            $classification['short_message'] = __('published without destination link', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest published the Pin without the destination link because Pinterest blocked that URL as possible spam.', 'calliope-social-publisher');
            return $classification;
        }

        if (!empty($classification['ok']) && (
            !empty($result['warning']) ||
            (string) ($result['ui_status'] ?? '') === 'warning' ||
            (string) ($result['warning_code'] ?? '') !== ''
        )) {
            $warning_code = sanitize_key((string) ($result['warning_code'] ?? 'published_with_warning'));
            $user_message = isset($result['user_message']) && is_scalar($result['user_message'])
                ? sanitize_text_field((string) $result['user_message'])
                : __('Published with a warning.', 'calliope-social-publisher');
            $short_message = isset($result['short_message']) && is_scalar($result['short_message'])
                ? sanitize_text_field((string) $result['short_message'])
                : __('published with omitted images', 'calliope-social-publisher');
            $classification['log_status'] = 'warning';
            $classification['summary_status'] = 'warning';
            $classification['classification'] = strtoupper($warning_code !== '' ? $warning_code : 'published_with_warning');
            $classification['retryable'] = false;
            $classification['short_message'] = $short_message;
            $classification['user_message'] = $user_message;
            return $classification;
        }

        if ($error_code === 'duplicate_skipped' || !empty($result['skipped'])) {
            $classification['ok'] = false;
            $classification['log_status'] = 'warning';
            $classification['summary_status'] = 'warning';
            $classification['classification'] = 'DUPLICATE_SKIPPED';
            $classification['retryable'] = false;
            $classification['short_message'] = __('skipped duplicate', 'calliope-social-publisher');
            $classification['user_message'] = __('This network was skipped because the same publication was already sent or started recently. It was not counted as published.', 'calliope-social-publisher');
            return $classification;
        }

        $status = sanitize_key((string) ($result['status'] ?? ''));
        $stage = sanitize_key((string) ($result['stage'] ?? ''));
        if (!empty($classification['ok'])) {
            return $classification;
        }


        $result_data = isset($result['data']) && is_array($result['data']) ? $result['data'] : [];
        $failure_kind = sanitize_key((string) ($result['failure_kind'] ?? ($result_data['failure_kind'] ?? '')));
        $http_code = (int) ($result['http_code'] ?? ($result_data['http_code'] ?? 0));
        $provider_code = isset($result_data['provider_code']) && is_scalar($result_data['provider_code']) ? (string) $result_data['provider_code'] : '';
        if ($network === 'pinterest' && ($error_code === 'pinterest_publish_access_denied' || in_array($failure_kind, ['access_tier'], true))) {
            $classification['classification'] = 'PINTEREST_PRODUCTION_ACCESS_DENIED';
            $classification['retryable'] = false;
            $classification['short_message'] = __('Production access denied', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest accepted the account connection but denied Pin publishing for the selected environment. Review the application access tier and Pinterest environment settings.', 'calliope-social-publisher');
            return $classification;
        }
        if ($network === 'pinterest' && ($error_code === 'pinterest_scope_missing' || $failure_kind === 'permission')) {
            $classification['classification'] = 'PINTEREST_PERMISSION_REQUIRED';
            $classification['retryable'] = false;
            $classification['short_message'] = __('permission required', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest needs additional publishing permissions.', 'calliope-social-publisher');
            return $classification;
        }
        if ($network === 'pinterest' && ($error_code === 'pinterest_environment_mismatch' || $failure_kind === 'environment')) {
            $classification['classification'] = 'PINTEREST_ENVIRONMENT_MISMATCH';
            $classification['retryable'] = false;
            $classification['short_message'] = __('reconnection required', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest was connected for a different environment.', 'calliope-social-publisher');
            return $classification;
        }
        if ($network === 'pinterest' && (
            in_array($error_code, ['pinterest_auth_invalid', 'pinterest_token_invalid', 'pinterest_token_expired'], true)
            || $failure_kind === 'auth'
            || $http_code === 401
        )) {
            $classification['classification'] = 'PINTEREST_AUTH_INVALID';
            $classification['retryable'] = false;
            $classification['short_message'] = __('authorization invalid', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest authorization is invalid or expired.', 'calliope-social-publisher');
            return $classification;
        }
        if ($network === 'pinterest' && (
            in_array($error_code, ['pinterest_board_required', 'pinterest_board_not_found'], true)
            || $failure_kind === 'target'
            || $http_code === 404
        )) {
            $classification['classification'] = 'PINTEREST_BOARD_INVALID';
            $classification['retryable'] = false;
            $classification['short_message'] = __('board unavailable', 'calliope-social-publisher');
            $classification['user_message'] = __('The selected Pinterest board is unavailable. Select a valid board and try again.', 'calliope-social-publisher');
            return $classification;
        }
        if (
            $network === 'pinterest' && (
                $error_code === 'pinterest_link_blocked' ||
                $provider_code === '9' ||
                self::contains_phrase($technical_message, 'blocked this link because it may lead to spam') ||
                self::contains_phrase($technical_message, 'anti-spam protection')
            )
        ) {
            $classification['classification'] = 'PINTEREST_LINK_BLOCKED';
            $classification['log_status'] = 'warning';
            $classification['summary_status'] = 'warning';
            $classification['retryable'] = false;
            $classification['short_message'] = __('destination link blocked', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest blocked the destination URL as possible spam. The plugin did not treat this as a rate limit. Try a different URL or publish without a destination link.', 'calliope-social-publisher');
            return $classification;
        }
        if ($network === 'pinterest' && ($error_code === 'pinterest_rate_limited' || $http_code === 429)) {
            $classification['classification'] = 'PINTEREST_RATE_LIMITED';
            $classification['retryable'] = true;
            $classification['short_message'] = __('rate limited', 'calliope-social-publisher');
            $classification['user_message'] = __('Pinterest temporarily rate limited this publication. The queued job can retry later.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'instagram_unsupported_aspect_ratio' ||
            ($network === 'instagram' && self::contains_phrase($technical_message, 'aspect ratio is not supported'))
        ) {
            $classification['classification'] = 'INSTAGRAM_UNSUPPORTED_ASPECT_RATIO';
            $classification['retryable'] = true;
            $classification['short_message'] = __('image aspect ratio was rejected', 'calliope-social-publisher');
            $classification['user_message'] = __('Instagram rejected the image aspect ratio even after the plugin tried to prepare a compatible crop. Please try a square, 4:5 portrait, or 1.91:1 landscape image.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'instagram_invalid_media_url' ||
            ($network === 'instagram' && (
                self::contains_phrase($technical_message, 'only photo or video can be accepted as media type') ||
                self::contains_phrase($technical_message, '2207052') ||
                self::contains_phrase($technical_message, 'could not download a valid image')
            ))
        ) {
            $classification['classification'] = 'INSTAGRAM_INVALID_MEDIA_URL';
            $classification['retryable'] = false;
            $classification['short_message'] = __('could not fetch or accept the media URL', 'calliope-social-publisher');
            $classification['user_message'] = __('Instagram rejected the media URL because it could not download a valid image. Please try another image or use Original format.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'threads_invalid_media_url' ||
            ($network === 'threads' && (
                self::contains_phrase($technical_message, '2207052') ||
                self::contains_phrase($technical_message, 'could not download a valid image') ||
                self::contains_phrase($technical_message, 'public media url is not accessible')
            ))
        ) {
            $classification['classification'] = 'THREADS_INVALID_MEDIA_URL';
            $classification['retryable'] = false;
            $classification['short_message'] = __('could not fetch or accept the media URL', 'calliope-social-publisher');
            $classification['user_message'] = __('Threads rejected the media URL because it could not download a valid image. Please try another image or use Original format.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'blogger_invalid_client' ||
            self::contains_phrase($technical_message, 'invalid_client') ||
            self::contains_phrase($technical_message, 'provided client secret is invalid')
        ) {
            $classification['classification'] = 'BLOGGER_INVALID_CLIENT';
            $classification['retryable'] = false;
            $classification['short_message'] = __('invalid OAuth client secret', 'calliope-social-publisher');
            $classification['user_message'] = __('Blogger rejected the saved OAuth client credentials. Update the Client Secret in Google Cloud and reconnect Blogger.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'facebook_identity_confirmation' ||
            self::contains_phrase($technical_message, 'confirm your identity') ||
            self::contains_phrase($technical_message, 'con' . 'firma tu iden' . 'tidad') ||
            self::contains_phrase($technical_message, 'identity confirmation') ||
            self::contains_phrase($technical_message, 'checkpoint') ||
            self::contains_phrase($technical_message, 'security check') ||
            self::contains_phrase($technical_message, 'page publishing authorization') ||
            self::contains_phrase($technical_message, 'account verification') ||
            self::contains_phrase($technical_message, 'verify your identity') ||
            self::contains_phrase($technical_message, 'veri' . 'fica tu iden' . 'tidad') ||
            self::contains_phrase($technical_message, 'facebook mobile app') ||
            self::contains_phrase($technical_message, 'app mo' . 'vil de face' . 'book') ||
            self::contains_phrase($technical_message, 'confirm your account or phone') ||
            self::contains_phrase($technical_message, 'con' . 'firmar tu cue' . 'nta o tu cel' . 'ular') ||
            self::contains_phrase($technical_message, 'con' . 'firma tu cue' . 'nta o tu cel' . 'ular')
        ) {
            $classification['classification'] = 'FACEBOOK_IDENTITY_CONFIRMATION';
            $classification['retryable'] = false;
            $classification['short_message'] = __('identity confirmation required', 'calliope-social-publisher');
            $classification['user_message'] = __('Facebook requires identity confirmation before publishing to this Page. Open the Facebook mobile app with the connected account and follow the instructions to confirm your account or phone. After completing it, disconnect and reconnect Facebook in the plugin, then try again.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'facebook_missing_publish_permission' ||
            self::contains_phrase($technical_message, 'pages_manage_posts') ||
            (self::contains_phrase($technical_message, 'permission') && self::contains_phrase($technical_message, 'publish'))
        ) {
            $classification['classification'] = 'FACEBOOK_MISSING_PUBLISH_PERMISSION';
            $classification['retryable'] = false;
            $classification['short_message'] = __('missing publish permission', 'calliope-social-publisher');
            $classification['user_message'] = __('Facebook is connected, but the Page token does not include permission to publish posts. Please disconnect and reconnect Facebook, making sure the selected Page is allowed and pages_manage_posts is granted.', 'calliope-social-publisher');
            return $classification;
        }

        if ($error_code === 'facebook_media_error') {
            $classification['classification'] = 'FACEBOOK_MEDIA_ERROR';
            $classification['retryable'] = true;
            $classification['short_message'] = __('rejected the image/media', 'calliope-social-publisher');
            $classification['user_message'] = __('Facebook rejected the image/media for this post. The Page is connected, but Meta could not process the image URL or media payload. Please try a different image or publish a text-only test.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'facebook_action_restricted' ||
            $error_code === 'facebook_publish_restricted'
        ) {
            $classification['classification'] = 'FACEBOOK_ACTION_RESTRICTED';
            $classification['log_status'] = 'warning';
            $classification['summary_status'] = 'warning';
            $classification['retryable'] = true;
            $classification['short_message'] = __('publishing restricted by Facebook', 'calliope-social-publisher');
            $classification['user_message'] = __('Facebook rejected this publish request due to a publishing restriction or temporary action block. The Page is connected, but Meta did not allow this specific post to be published. Please try a simpler text-only post, wait a while, or review the app/Page permissions.', 'calliope-social-publisher');
            return $classification;
        }

        if (
            $error_code === 'facebook_missing_page_token' ||
            self::contains_phrase($technical_message, 'no publishable page token')
        ) {
            $classification['classification'] = 'FACEBOOK_MISSING_PAGE_TOKEN';
            $classification['retryable'] = false;
            $classification['short_message'] = __('no publishable Page token found', 'calliope-social-publisher');
            $classification['user_message'] = __('Facebook authorization is saved, but the Page token is missing. Open Connections, select the Page and run diagnostics; Social Publisher obtains the token automatically.', 'calliope-social-publisher');
            return $classification;
        }

        return $classification;
    }

    public static function build_report(array $results) {
        $classified_results = [];
        $lines = [];
        $success_count = 0;
        $failed_count = 0;
        $warning_count = 0;
        $manual_action_count = 0;

        foreach ($results as $key => $result) {
            $classified = self::classify_result(is_array($result) ? $result : []);
            $classified_results[$key] = array_merge(
                is_array($result) ? $result : [],
                [
                    'ok'             => $classified['ok'],
                    'ui_status'      => $classified['summary_status'],
                    'classification' => $classified['classification'],
                    'retryable'      => $classified['retryable'],
                    'user_message'   => $classified['user_message'],
                    'short_message'  => $classified['short_message'],
                ]
            );

            $lines[] = $classified['label'] . ': ' . $classified['short_message'];

            if (!empty($classified['ok'])) {
                $success_count++;
                if ($classified['summary_status'] === 'warning') {
                    $warning_count++;
                }
            } else {
                $failed_count++;
            }
        }

        $all_ok = $failed_count === 0 && $manual_action_count === 0;
        $partial = $success_count > 0 && ($failed_count > 0 || $manual_action_count > 0);

        if ($all_ok && $warning_count > 0) {
            $headline = __('Published with warnings', 'calliope-social-publisher');
            $notice_type = 'warning';
        } elseif ($all_ok) {
            $headline = __('Published successfully', 'calliope-social-publisher');
            $notice_type = 'success';
        } elseif ($partial) {
            $headline = __('Published partially', 'calliope-social-publisher');
            $notice_type = 'warning';
        } else {
            $headline = __('Publishing failed', 'calliope-social-publisher');
            $notice_type = 'error';
        }

        $summary = $headline;
        if (!empty($lines)) {
            $summary .= ' | ' . implode(' | ', $lines);
        }

        return [
            'all_ok'         => $all_ok,
            'partial'        => $partial,
            'notice_type'    => $notice_type,
            'headline'       => $headline,
            'summary'        => $summary,
            'lines'          => $lines,
            'results'        => $classified_results,
            'success_count'  => $success_count,
            'failed_count'   => $failed_count,
            'warning_count'  => $warning_count,
            'manual_action_count' => $manual_action_count,
        ];
    }

    public static function get_network_label($network) {
        $labels = [
            'bsky'      => __('Bluesky', 'calliope-social-publisher'),
            'bluesky'   => __('Bluesky', 'calliope-social-publisher'),
            'mastodon'  => __('Mastodon', 'calliope-social-publisher'),
            'tumblr'    => __('Tumblr', 'calliope-social-publisher'),
            'pinterest' => __('Pinterest', 'calliope-social-publisher'),
            'threads'   => __('Threads', 'calliope-social-publisher'),
            'instagram' => __('Instagram', 'calliope-social-publisher'),
            'linkedin'  => __('LinkedIn', 'calliope-social-publisher'),
            'blogger'   => __('Blogger', 'calliope-social-publisher'),
            'telegram'  => __('Telegram', 'calliope-social-publisher'),
            'facebook'  => __('Facebook', 'calliope-social-publisher'),
            'youtube'   => __('YouTube', 'calliope-social-publisher'),
            'youtube_shorts' => __('YouTube Shorts', 'calliope-social-publisher'),
            'wordpress_remote' => __('Remote WordPress', 'calliope-social-publisher'),
            'custom_webhook' => __('Custom webhook', 'calliope-social-publisher'),
        ];

        $network = sanitize_key((string) $network);
        return $labels[$network] ?? ucfirst($network);
    }

    private static function normalize_multiline_text($value) {
        $value = (string) $value;
        $value = preg_replace("/\r\n?/", "\n", $value);
        $value = preg_replace("/[ \t]+\n/", "\n", (string) $value);
        return trim((string) $value);
    }

    private static function sanitize_hashtag_body($tag, $network = '') {
        $tag = trim((string) $tag);
        $tag = preg_replace('/^[#\s,;]+/u', '', $tag);
        $tag = preg_replace('/[#\s,;]+$/u', '', (string) $tag);
        $tag = preg_replace('/[^\p{L}\p{M}\p{N}_]+/u', '', (string) $tag);

        if ($tag === '') {
            return '';
        }

        if (in_array(sanitize_key((string) $network), ['bluesky'], true) && function_exists('remove_accents')) {
            $ascii_fallback = remove_accents($tag);
            $ascii_fallback = preg_replace('/[^\p{L}\p{M}\p{N}_]+/u', '', (string) $ascii_fallback);
            if ($ascii_fallback !== '') {
                return $tag;
            }
        }

        return $tag;
    }

    private static function contains_phrase($haystack, $needle) {
        return $haystack !== '' && stripos((string) $haystack, (string) $needle) !== false;
    }
}
