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

/**
 * Free per-network social templates and WordPress Smart Tags.
 *
 * Free supports one template per bundled Free destination. Pro can extend the
 * experience with reusable/multiple templates without replacing this storage.
 */
class Calliope_SP_Social_Templates {
    public const ENABLED_OPTION = 'calliope_sp_social_templates_enabled';
    public const TEMPLATES_OPTION = 'calliope_sp_social_templates';

    /** @return array<string,string> */
    public static function network_labels(): array {
        return [
            '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 Page', 'calliope-social-publisher'),
        ];
    }

    /** @return array<string,string> */
    public static function defaults(): array {
        $standard = "{post_title}\n\n{post_excerpt}\n\n{post_link}\n\n{ai_hashtags}";
        return [
            'bluesky'   => $standard,
            'mastodon'  => $standard,
            'tumblr'    => $standard,
            'pinterest' => "{post_excerpt}\n\n{ai_hashtags}",
            'threads'   => $standard,
            'instagram' => "{post_excerpt}\n\n{ai_hashtags}",
            'linkedin'  => $standard,
            'blogger'   => "{post_excerpt}\n\n{post_link}\n\n{ai_hashtags}",
            'telegram'  => $standard,
            'facebook'  => "{post_excerpt}\n\n{post_link}\n\n{ai_hashtags}",
        ];
    }

    /**
     * Smart Tags exposed by Free. Group metadata is used by the settings editor.
     *
     * @return array<string,array{label:string,description:string,group:string}>
     */
    public static function smart_tags(): array {
        $tags = [
            '{post_title}' => [
                'label' => __('Post title', 'calliope-social-publisher'),
                'description' => __('WordPress post title. Falls back to the Publisher title when no source post is selected.', 'calliope-social-publisher'),
                'group' => 'content',
            ],
            '{post_excerpt}' => [
                'label' => __('Post excerpt', 'calliope-social-publisher'),
                'description' => __('WordPress excerpt. If the post has no excerpt, Social Publisher creates a short excerpt from its content.', 'calliope-social-publisher'),
                'group' => 'content',
            ],
            '{post_content}' => [
                'label' => __('Post content', 'calliope-social-publisher'),
                'description' => __('Full plain-text WordPress post content.', 'calliope-social-publisher'),
                'group' => 'content',
            ],
            '{post_link}' => [
                'label' => __('Post link', 'calliope-social-publisher'),
                'description' => __('WordPress post permalink. Falls back to the current Publisher link.', 'calliope-social-publisher'),
                'group' => 'content',
            ],
            '{post_author}' => [
                'label' => __('Post author', 'calliope-social-publisher'),
                'description' => __('Display name of the WordPress post author.', 'calliope-social-publisher'),
                'group' => 'details',
            ],
            '{post_date}' => [
                'label' => __('Post date', 'calliope-social-publisher'),
                'description' => __('Published date formatted with your WordPress date settings.', 'calliope-social-publisher'),
                'group' => 'details',
            ],
            '{post_categories}' => [
                'label' => __('Post categories', 'calliope-social-publisher'),
                'description' => __('Comma-separated WordPress categories assigned to the post.', 'calliope-social-publisher'),
                'group' => 'taxonomy',
            ],
            '{post_tags}' => [
                'label' => __('Post tags', 'calliope-social-publisher'),
                'description' => __('Comma-separated WordPress tags assigned to the post.', 'calliope-social-publisher'),
                'group' => 'taxonomy',
            ],
            '{site_name}' => [
                'label' => __('Site name', 'calliope-social-publisher'),
                'description' => __('The WordPress site title from Settings > General.', 'calliope-social-publisher'),
                'group' => 'site',
            ],
            // Retained from Step 6 so existing templates keep working.
            '{ai_hashtags}' => [
                'label' => __('AI hashtags', 'calliope-social-publisher'),
                'description' => __('Current Publisher hashtags, including hashtags generated with AI.', 'calliope-social-publisher'),
                'group' => 'ai',
            ],
        ];

        /**
         * Let compatible add-ons contribute Smart Tags without moving their
         * provider-specific resolution logic into the Free plugin.
         *
         * @param array<string,array{label:string,description:string,group:string}> $tags
         */
        $filtered = apply_filters('calliope_sp_social_template_smart_tags', $tags);
        if (!is_array($filtered)) {
            return $tags;
        }

        $clean = [];
        foreach ($filtered as $token => $definition) {
            $token = is_scalar($token) ? strtolower(trim((string) $token)) : '';
            if (!preg_match('/^\{[a-z0-9_]+\}$/D', $token) || !is_array($definition)) {
                continue;
            }
            $label = sanitize_text_field((string) ($definition['label'] ?? $token));
            $description = sanitize_text_field((string) ($definition['description'] ?? ''));
            $group = sanitize_key((string) ($definition['group'] ?? 'content'));
            $clean[$token] = [
                'label' => $label !== '' ? $label : $token,
                'description' => $description,
                'group' => $group !== '' ? $group : 'content',
            ];
        }
        return $clean;
    }

    /** @return array<string,string> */
    public static function allowed_tokens(): array {
        $out = [];
        foreach (self::smart_tags() as $token => $definition) {
            $out[$token] = (string) ($definition['description'] ?? '');
        }
        return $out;
    }

    /** @return array<string,string> */
    public static function smart_tag_group_labels(): array {
        $labels = [
            'content'  => __('Content', 'calliope-social-publisher'),
            'details'  => __('Post details', 'calliope-social-publisher'),
            'taxonomy' => __('Categories & tags', 'calliope-social-publisher'),
            'site'     => __('Site', 'calliope-social-publisher'),
            'ai'       => __('AI', 'calliope-social-publisher'),
        ];
        $filtered = apply_filters('calliope_sp_social_template_smart_tag_group_labels', $labels);
        if (!is_array($filtered)) {
            return $labels;
        }
        $clean = [];
        foreach ($filtered as $group => $label) {
            $group = sanitize_key((string) $group);
            $label = sanitize_text_field(is_scalar($label) ? (string) $label : '');
            if ($group !== '' && $label !== '') {
                $clean[$group] = $label;
            }
        }
        return $clean;
    }

    public static function is_enabled(): bool {
        return get_option(self::ENABLED_OPTION, '0') === '1';
    }

    /** @return array<string,string> */
    public static function get_templates(): array {
        $defaults = self::defaults();
        $saved = get_option(self::TEMPLATES_OPTION, null);
        if (!is_array($saved)) {
            return $defaults;
        }

        $out = [];
        foreach ($defaults as $network => $default) {
            if (array_key_exists($network, $saved)) {
                $out[$network] = self::sanitize_template($saved[$network]);
            } else {
                $out[$network] = $default;
            }
        }
        return $out;
    }

    public static function get_template(string $network): string {
        $network = self::normalize_network($network);
        if ($network === '') {
            return '';
        }
        $templates = self::get_templates();
        return isset($templates[$network]) ? (string) $templates[$network] : '';
    }

    /** @return array<string,string> */
    public static function sanitize_templates($raw): array {
        $raw = is_array($raw) ? $raw : [];
        $clean = [];
        foreach (self::defaults() as $network => $default) {
            if (!array_key_exists($network, $raw)) {
                $clean[$network] = $default;
                continue;
            }
            $clean[$network] = self::sanitize_template($raw[$network]);
        }
        return $clean;
    }

    public static function sanitize_template($template): string {
        $template = is_scalar($template) ? (string) $template : '';
        $template = sanitize_textarea_field($template);
        if (function_exists('mb_substr')) {
            $template = mb_substr($template, 0, 4000, 'UTF-8');
        } else {
            $template = substr($template, 0, 4000);
        }
        return trim((string) $template);
    }

    /**
     * Render and apply a saved network template. Idempotent so History Retry
     * republishes the exact snapshot that originally failed even if settings
     * change later.
     *
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $history_context
     * @return array<string,mixed>
     */
    public static function apply_to_payload(array $payload, string $network, array $history_context = []): array {
        if (!empty($payload['social_template_applied'])) {
            return $payload;
        }
        $templates_enabled = (bool) apply_filters(
            'calliope_sp_social_templates_enabled_for_payload',
            self::is_enabled(),
            $payload,
            $network,
            $history_context
        );
        if (!$templates_enabled) {
            return $payload;
        }

        $network = self::normalize_network($network);
        if ($network === '' || !array_key_exists($network, self::defaults())) {
            return $payload;
        }

        $template = (string) apply_filters(
            'calliope_sp_social_template',
            self::get_template($network),
            $network,
            $payload,
            $history_context
        );
        $template = self::sanitize_template($template);
        if ($template === '') {
            return $payload;
        }

        $values = self::resolve_smart_tag_values($payload, $history_context);
        $rendered = strtr($template, $values);
        // Never leak a mistyped Smart Tag into a live social post.
        $rendered = preg_replace('/\{[a-z0-9_]+\}/iu', '', (string) $rendered);
        $rendered = preg_replace("/\r\n?|\n/u", "\n", (string) $rendered);
        $rendered = preg_replace("/\n{3,}/u", "\n\n", (string) $rendered);
        $rendered = trim((string) $rendered);
        if ($rendered === '') {
            return $payload;
        }

        $includes_link = strpos($template, '{post_link}') !== false;
        $payload['social_template_applied'] = 1;
        $payload['social_template_network'] = $network;
        $payload['social_template_text'] = $rendered;
        $payload['social_template_includes_link'] = $includes_link ? 1 : 0;

        // Keep structural fields where the provider uses them outside the social
        // caption itself. Everywhere else, reduce the payload to exactly the
        // rendered template so the original fields are not duplicated.
        if ($network === 'pinterest') {
            $payload['content'] = $rendered;
            $payload['hashtags'] = '';
            $payload['hashtags_list'] = [];
            return $payload;
        }

        if ($network === 'blogger') {
            $payload['content'] = $rendered;
            $payload['hashtags'] = '';
            $payload['hashtags_list'] = [];
            $payload['link'] = '';
            return $payload;
        }

        if ($network === 'facebook') {
            $payload['content'] = $rendered;
            $payload['hashtags'] = '';
            $payload['hashtags_list'] = [];
            if (!$includes_link) {
                $payload['link'] = '';
            }
            return $payload;
        }

        $payload['title'] = '';
        $payload['content'] = $rendered;
        $payload['hashtags'] = '';
        $payload['hashtags_list'] = [];
        $payload['link'] = '';
        return $payload;
    }

    /**
     * Render any safe text through the existing Smart Tag engine without
     * applying a saved Social Template. Add-ons can reuse this for content-
     * specific copy while preserving one Smart Tag implementation.
     *
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $history_context
     */
    public static function render_smart_text(string $text, array $payload, array $history_context = []): string {
        $text = trim(sanitize_textarea_field($text));
        if (function_exists('mb_substr')) {
            $text = mb_substr($text, 0, 8000, 'UTF-8');
        } else {
            $text = substr($text, 0, 8000);
        }
        if ($text === '') {
            return '';
        }
        $rendered = strtr($text, self::resolve_smart_tag_values($payload, $history_context));
        $rendered = preg_replace('/\{[a-z0-9_]+\}/iu', '', (string) $rendered);
        $rendered = preg_replace("/\r\n?|\n/u", "\n", (string) $rendered);
        $rendered = preg_replace("/\n{3,}/u", "\n\n", (string) $rendered);
        return trim((string) $rendered);
    }

    /**
     * Resolve Smart Tags from the selected WordPress source post, with safe
     * Publisher fallbacks when no source post is attached.
     *
     * @param array<string,mixed> $payload
     * @param array<string,mixed> $history_context
     * @return array<string,string>
     */
    private static function resolve_smart_tag_values(array $payload, array $history_context): array {
        $publisher_title = trim((string) ($payload['title'] ?? ''));
        $publisher_content = trim((string) ($payload['content'] ?? ''));
        $publisher_link = trim((string) ($payload['link'] ?? ''));
        $tracked_post_link = trim((string) ($payload['utm_tracking_post_link'] ?? ''));
        $short_post_link = trim((string) ($payload['short_link_post_link'] ?? ''));
        $publisher_hashtags = trim((string) ($payload['hashtags'] ?? ''));

        $post_id = absint($history_context['post_id'] ?? ($payload['post_id'] ?? 0));
        $post = $post_id > 0 ? get_post($post_id) : null;
        if (!($post instanceof WP_Post)) {
            $post = null;
            $post_id = 0;
        }

        $post_title = $publisher_title;
        $post_excerpt = $publisher_content;
        $post_content = $publisher_content;
        $post_link = $short_post_link !== '' ? $short_post_link : ($tracked_post_link !== '' ? $tracked_post_link : $publisher_link);
        $post_author = '';
        $post_date = wp_date((string) get_option('date_format', 'F j, Y'));
        $post_categories = '';
        $post_tags = '';

        if ($post instanceof WP_Post) {
            $post_title = trim(wp_strip_all_tags(get_the_title($post)));
            $post_content = self::post_content_to_plain_text((string) $post->post_content);
            $explicit_excerpt = self::post_content_to_plain_text((string) $post->post_excerpt);
            $post_excerpt = $explicit_excerpt !== ''
                ? $explicit_excerpt
                : wp_trim_words(preg_replace('/\s+/u', ' ', $post_content), 55, '…');
            $permalink = get_permalink($post);
            if ($short_post_link !== '') {
                $post_link = $short_post_link;
            } elseif ($tracked_post_link !== '') {
                $post_link = $tracked_post_link;
            } else {
                $post_link = is_string($permalink) && $permalink !== '' ? $permalink : $publisher_link;
            }
            $post_author = trim((string) get_the_author_meta('display_name', (int) $post->post_author));
            $post_date = (string) get_the_date((string) get_option('date_format', 'F j, Y'), $post);
            $post_categories = self::taxonomy_names($post_id, 'category');
            $post_tags = self::taxonomy_names($post_id, 'post_tag');
            // A content variation is the source copy for this destination. Keep
            // title/link/taxonomy values from WordPress, but let existing Social
            // Templates wrap the prepared variation instead of silently replacing
            // it with the original post excerpt/content.
            if (!empty($payload['content_variation_applied'])) {
                $post_excerpt = $publisher_content;
                $post_content = $publisher_content;
            }
        } else {
            $user_id = absint($history_context['user_id'] ?? get_current_user_id());
            if ($user_id > 0) {
                $post_author = trim((string) get_the_author_meta('display_name', $user_id));
            }
        }

        $values = [
            '{post_title}'      => $post_title,
            '{post_excerpt}'    => $post_excerpt,
            '{post_content}'    => $post_content,
            '{post_link}'       => $post_link,
            '{post_author}'     => $post_author,
            '{post_date}'       => $post_date,
            '{post_categories}' => $post_categories,
            '{post_tags}'       => $post_tags,
            '{site_name}'       => trim((string) get_bloginfo('name')),
            '{ai_hashtags}'     => $publisher_hashtags,
        ];

        /**
         * Provider add-ons can resolve their own tags from the same immutable
         * publication snapshot and selected source post.
         *
         * @param array<string,string> $values
         * @param array<string,mixed>  $payload
         * @param array<string,mixed>  $history_context
         * @param WP_Post|null         $post
         */
        $filtered = apply_filters(
            'calliope_sp_social_template_smart_tag_values',
            $values,
            $payload,
            $history_context,
            $post
        );
        if (!is_array($filtered)) {
            return $values;
        }
        $clean = [];
        foreach ($filtered as $token => $value) {
            $token = is_scalar($token) ? strtolower(trim((string) $token)) : '';
            if (!preg_match('/^\{[a-z0-9_]+\}$/D', $token) || (!is_scalar($value) && $value !== null)) {
                continue;
            }
            $clean[$token] = sanitize_textarea_field((string) $value);
        }
        return $clean;
    }

    private static function post_content_to_plain_text(string $content): string {
        if ($content === '') {
            return '';
        }
        $content = strip_shortcodes($content);
        $content = preg_replace('#<br\s*/?>#i', "\n", $content);
        $content = preg_replace('#</(?:p|div|h[1-6]|li|blockquote|pre|figure|section|article)>#i', "\n", (string) $content);
        $content = html_entity_decode(wp_strip_all_tags((string) $content), ENT_QUOTES | ENT_HTML5, get_bloginfo('charset') ?: 'UTF-8');
        $content = preg_replace("/[\t ]+/u", ' ', (string) $content);
        $content = preg_replace("/\s*\n\s*/u", "\n", (string) $content);
        $content = preg_replace("/\n{3,}/u", "\n\n", (string) $content);
        return trim((string) $content);
    }

    private static function taxonomy_names(int $post_id, string $taxonomy): string {
        if ($post_id <= 0 || !taxonomy_exists($taxonomy)) {
            return '';
        }
        $terms = wp_get_post_terms($post_id, $taxonomy, ['fields' => 'names']);
        if (is_wp_error($terms) || !is_array($terms)) {
            return '';
        }
        $terms = array_values(array_filter(array_map(static function ($term): string {
            return sanitize_text_field((string) $term);
        }, $terms)));
        return implode(', ', $terms);
    }

    private static function normalize_network(string $network): string {
        $network = sanitize_key($network);
        if ($network === 'bsky') {
            return 'bluesky';
        }
        return $network;
    }
}
