<?php
/**
 * DBA_Link_Sanitizer — Shared internal link validator for all bots.
 *
 * Prevents AI-generated hallucinated damalion.com links from being published.
 *
 * Two layers of defence:
 *   1. PROMPT: get_prompt_block() returns an approved-links list for the AI prompt
 *   2. POST-GEN: sanitize() strips/replaces any damalion.com link not on the whitelist
 *
 * The whitelist is built DYNAMICALLY from the WordPress database:
 *   - All published pages (post_type = 'page', post_status = 'publish')
 *   - All published posts (optional, for internal cross-linking)
 *   - Plus a hardcoded fallback of key service pages
 *
 * WPML-aware: detects current language and adjusts URLs accordingly.
 *
 * Usage in any generator:
 *   // In prompt construction:
 *   $prompt .= DBA_Link_Sanitizer::get_prompt_block( 'en' );
 *
 *   // After AI returns HTML:
 *   $content = DBA_Link_Sanitizer::sanitize( $content, 'en' );
 *
 * @package DamalionBlogAI
 * @since   2.21.0
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class DBA_Link_Sanitizer {

    /** Cache key for discovered pages */
    const CACHE_KEY = 'dba_link_sanitizer_pages';
    const CACHE_TTL = 3600; // 1 hour

    /** Domain to protect — only links to this domain are validated */
    const PROTECTED_DOMAIN = 'damalion.com';

    /**
     * Hardcoded fallback pages — always whitelisted even if DB query fails.
     * Structure: slug_key => [ 'en' => URL, 'fr' => URL ]
     *
     * Filterable: apply_filters( 'dba_link_sanitizer_fallback_pages', ... )
     *
     * @var array
     */
    private static $fallback_pages = array(
        'start_business'      => array(
            'en' => 'https://www.damalion.com/how-to-start-a-business-in-luxembourg/',
            'fr' => 'https://www.damalion.com/how-to-start-a-business-in-luxembourg/?lang=fr',
        ),
        'register_company'    => array(
            'en' => 'https://www.damalion.com/how-to-register-your-company-in-luxembourg/',
            'fr' => 'https://www.damalion.com/how-to-register-your-company-in-luxembourg/?lang=fr',
        ),
        'securitization'      => array(
            'en' => 'https://www.damalion.com/launch-your-securitization-vehicle-in-luxembourg/',
            'fr' => 'https://www.damalion.com/launch-your-securitization-vehicle-in-luxembourg/?lang=fr',
        ),
        'soparfi'             => array(
            'en' => 'https://www.damalion.com/create-your-holding-company-in-luxembourg-the-soparfi/',
            'fr' => 'https://www.damalion.com/create-your-holding-company-in-luxembourg-the-soparfi/?lang=fr',
        ),
        'fund_structuring'    => array(
            'en' => 'https://www.damalion.com/fund-structuring-services/',
            'fr' => 'https://www.damalion.com/fund-structuring-services/?lang=fr',
        ),
        'crypto_digital'      => array(
            'en' => 'https://www.damalion.com/crypto-digital-assets/',
            'fr' => 'https://www.damalion.com/crypto-digital-assets/?lang=fr',
        ),
        'esg_funds'           => array(
            'en' => 'https://www.damalion.com/esg-funds/',
            'fr' => 'https://www.damalion.com/esg-funds/?lang=fr',
        ),
        'real_estate_funds'   => array(
            'en' => 'https://www.damalion.com/real-estate-funds/',
            'fr' => 'https://www.damalion.com/real-estate-funds/?lang=fr',
        ),
        'hedge_funds'         => array(
            'en' => 'https://www.damalion.com/hedge-funds/',
            'fr' => 'https://www.damalion.com/hedge-funds/?lang=fr',
        ),
        'infrastructure_funds'=> array(
            'en' => 'https://www.damalion.com/infrastructure-funds/',
            'fr' => 'https://www.damalion.com/infrastructure-funds/?lang=fr',
        ),
        'pe_vc_funds'         => array(
            'en' => 'https://www.damalion.com/pe-vc-funds/',
            'fr' => 'https://www.damalion.com/pe-vc-funds/?lang=fr',
        ),
        'sarl_s_guide'        => array(
            'en' => 'https://www.damalion.com/comprehensive-guide-to-establishing-your-sarl-s-in-luxembourg/',
            'fr' => 'https://www.damalion.com/comprehensive-guide-to-establishing-your-sarl-s-in-luxembourg/?lang=fr',
        ),
        'sarl_s_register'     => array(
            'en' => 'https://www.damalion.com/how-entrepreneurs-register-their-luxembourg-simplified-limited-liability-company-sarl-s/',
            'fr' => 'https://www.damalion.com/how-entrepreneurs-register-their-luxembourg-simplified-limited-liability-company-sarl-s/?lang=fr',
        ),
        'sarl_s_use'          => array(
            'en' => 'https://www.damalion.com/use-luxembourg-simplified-limited-liability-company-simplified-llcs-sarl-s/',
            'fr' => 'https://www.damalion.com/use-luxembourg-simplified-limited-liability-company-simplified-llcs-sarl-s/?lang=fr',
        ),
        'contact'             => array(
            'en' => 'https://www.damalion.com/lets-connect/',
            'fr' => 'https://www.damalion.com/lets-connect/?lang=fr',
        ),
        'funds'               => array(
            'en' => 'https://www.damalion.com/funds/',
            'fr' => 'https://www.damalion.com/funds/?lang=fr',
        ),
    );


    /* ══════════════════════════════════════════════════════════════════
     *  LAYER 1: PROMPT BLOCK — tells AI which URLs are allowed
     * ══════════════════════════════════════════════════════════════════ */

    /**
     * Build a prompt block listing every approved damalion.com URL.
     *
     * Inject this into any bot's user_prompt so the AI never invents links.
     *
     * @param  string $lang  Language code ('en', 'fr', 'de', 'pt', 'es').
     * @param  int    $max   Max links to show in prompt (avoid token bloat).
     * @return string        Prompt instruction block.
     */
    public static function get_prompt_block( $lang = 'en', $max = 30 ) {
        $urls = self::get_whitelist( $lang );

        if ( empty( $urls ) ) {
            return "\n\nINTERNAL LINKS: Do NOT include any links to damalion.com in the article body."
                 . " The CTA link at the end is the only damalion.com link allowed.\n";
        }

        $block  = "\n\nAPPROVED INTERNAL LINKS — You may ONLY use damalion.com URLs from this list."
                . " NEVER invent, guess, or fabricate any damalion.com URL."
                . " If no link from this list fits naturally, do not add an internal link.\n";

        $count = 0;
        foreach ( $urls as $url => $title ) {
            if ( $count >= $max ) break;
            $block .= "  • {$title} → {$url}\n";
            $count++;
        }

        $block .= "Use 1–3 internal links maximum per article. Place them naturally in context."
                 . " External authority links (legilux.public.lu, lbr.lu, cssf.lu, government sites) are allowed freely.\n";

        return $block;
    }


    /* ══════════════════════════════════════════════════════════════════
     *  LAYER 2: POST-GENERATION SANITIZER — strips fake links
     * ══════════════════════════════════════════════════════════════════ */

    /**
     * Sanitize HTML content: strip or replace any damalion.com link
     * not in the approved whitelist.
     *
     * @param  string $html  HTML content from AI.
     * @param  string $lang  Language code.
     * @return string        Cleaned HTML.
     */
    public static function sanitize( $html, $lang = 'en' ) {
        if ( empty( $html ) ) return $html;

        $whitelist_urls = self::get_whitelist_normalised( $lang );

        if ( empty( $whitelist_urls ) ) {
            // If we can't build a whitelist, strip ALL damalion.com links as safety net
            return self::strip_all_internal_links( $html );
        }

        // Match all <a> tags linking to damalion.com
        $pattern = '/<a\s[^>]*href=["\']([^"\']*' . preg_quote( self::PROTECTED_DOMAIN, '/' ) . '[^"\']*)["\'][^>]*>(.*?)<\/a>/si';

        $cleaned = preg_replace_callback( $pattern, function( $match ) use ( $whitelist_urls ) {
            $full_tag    = $match[0];
            $href        = $match[1];
            $anchor_text = $match[2];
            $norm        = self::normalise_url( $href );

            // ── URL is in whitelist → keep it (with normalised href) ──
            if ( isset( $whitelist_urls[ $norm ] ) ) {
                return $full_tag; // Keep as-is
            }

            // ── Try fuzzy match by slug overlap ──
            $best = self::find_closest_url( $href, $whitelist_urls );
            if ( $best ) {
                error_log( 'DBA Link Sanitizer: Replaced fake "' . $href . '" → "' . $best . '"' );
                return preg_replace(
                    '/href=["\'][^"\']*["\']/',
                    'href="' . esc_url( $best ) . '"',
                    $full_tag
                );
            }

            // ── No match → strip the <a> tag, keep anchor text ──
            error_log( 'DBA Link Sanitizer: Stripped fake link "' . $href . '" (anchor: "' . wp_strip_all_tags( $anchor_text ) . '")' );
            return $anchor_text;

        }, $html );

        return $cleaned;
    }


    /* ══════════════════════════════════════════════════════════════════
     *  WHITELIST BUILDER — dynamic from WP DB + hardcoded fallback
     * ══════════════════════════════════════════════════════════════════ */

    /**
     * Get all approved URLs for a language.
     *
     * Returns: [ 'https://www.damalion.com/page/' => 'Page Title', ... ]
     *
     * @param  string $lang Language code.
     * @return array        URL → Title map.
     */
    public static function get_whitelist( $lang = 'en' ) {
        $cache_key = self::CACHE_KEY . '_' . $lang;
        $cached    = get_transient( $cache_key );

        if ( is_array( $cached ) ) {
            return $cached;
        }

        $urls = array();

        // ── 1. Query all published pages from WP DB ──
        $db_urls = self::discover_from_db( $lang );
        foreach ( $db_urls as $url => $title ) {
            $urls[ $url ] = $title;
        }

        // ── 2. Add hardcoded fallback pages ──
        $fallback = apply_filters( 'dba_link_sanitizer_fallback_pages', self::$fallback_pages );
        $lang_key = self::lang_key( $lang );

        foreach ( $fallback as $key => $entry ) {
            $url = $entry[ $lang_key ] ?? $entry['en'] ?? '';
            if ( $url && ! isset( $urls[ $url ] ) ) {
                $title = ucwords( str_replace( '_', ' ', $key ) );
                $urls[ $url ] = $title;
            }
        }

        // ── 3. Cache for 1 hour ──
        set_transient( $cache_key, $urls, self::CACHE_TTL );

        return $urls;
    }

    /**
     * Get normalised whitelist for comparison.
     *
     * Returns: [ 'www.damalion.com/page-slug' => 'https://www.damalion.com/page-slug/', ... ]
     *
     * @param  string $lang Language code.
     * @return array        Normalised URL → original URL map.
     */
    private static function get_whitelist_normalised( $lang = 'en' ) {
        $urls   = self::get_whitelist( $lang );
        $normed = array();

        foreach ( $urls as $url => $title ) {
            $normed[ self::normalise_url( $url ) ] = $url;
        }

        // Also whitelist the EN version of all fallback pages (for bilingual safety)
        if ( $lang !== 'en' ) {
            $fallback = apply_filters( 'dba_link_sanitizer_fallback_pages', self::$fallback_pages );
            foreach ( $fallback as $entry ) {
                if ( ! empty( $entry['en'] ) ) {
                    $normed[ self::normalise_url( $entry['en'] ) ] = $entry['en'];
                }
            }
        }

        return $normed;
    }

    /**
     * Discover published pages/posts from WordPress database.
     *
     * Uses WPML-aware permalink resolution when available.
     *
     * @param  string $lang Language code.
     * @return array        URL → Title map.
     */
    private static function discover_from_db( $lang = 'en' ) {
        global $sitepress;
        $urls = array();

        // ── WPML: Temporarily switch language context for correct permalinks ──
        $switched   = false;
        $prev_lang  = null;

        if ( $sitepress && method_exists( $sitepress, 'switch_lang' ) ) {
            $prev_lang = $sitepress->get_current_language();
            if ( $prev_lang !== $lang ) {
                $sitepress->switch_lang( $lang, true );
                $switched = true;
            }
        }

        try {
            // Query all published pages
            $pages = get_posts( array(
                'post_type'      => 'page',
                'post_status'    => 'publish',
                'posts_per_page' => 500,
                'no_found_rows'  => true,
                'suppress_filters' => false, // Allow WPML to filter
            ) );

            foreach ( $pages as $page ) {
                $permalink = get_permalink( $page->ID );
                if ( $permalink && strpos( $permalink, self::PROTECTED_DOMAIN ) !== false ) {
                    $urls[ $permalink ] = $page->post_title;
                }
            }

            // Also add published posts (for internal cross-linking)
            // Limit to recent 200 posts to avoid bloat
            $posts = get_posts( array(
                'post_type'      => 'post',
                'post_status'    => 'publish',
                'posts_per_page' => 200,
                'orderby'        => 'date',
                'order'          => 'DESC',
                'no_found_rows'  => true,
                'suppress_filters' => false,
            ) );

            foreach ( $posts as $post ) {
                $permalink = get_permalink( $post->ID );
                if ( $permalink && strpos( $permalink, self::PROTECTED_DOMAIN ) !== false ) {
                    $urls[ $permalink ] = $post->post_title;
                }
            }

        } catch ( \Throwable $e ) {
            error_log( 'DBA Link Sanitizer: DB discovery failed: ' . $e->getMessage() );
        }

        // Restore WPML language
        if ( $switched && $sitepress ) {
            $sitepress->switch_lang( $prev_lang, true );
        }

        return $urls;
    }


    /* ══════════════════════════════════════════════════════════════════
     *  URL MATCHING HELPERS
     * ══════════════════════════════════════════════════════════════════ */

    /**
     * Normalise a URL for comparison: lowercase, strip scheme + trailing slash + query params.
     */
    private static function normalise_url( $url ) {
        $url = strtolower( trim( $url ) );
        $url = preg_replace( '#^https?://#', '', $url );
        $url = preg_replace( '/\?.*$/', '', $url );
        $url = rtrim( $url, '/' );
        return $url;
    }

    /**
     * Try to find the closest approved URL by slug keyword overlap.
     *
     * @param  string $href      The hallucinated URL.
     * @param  array  $whitelist Normalised URL => original URL map.
     * @return string|false      Best matching approved URL or false.
     */
    private static function find_closest_url( $href, $whitelist ) {
        $parsed = wp_parse_url( $href );
        $path   = $parsed['path'] ?? '';
        $slugs  = array_filter( explode( '/', strtolower( $path ) ) );

        if ( empty( $slugs ) ) {
            return false;
        }

        $best_score = 0;
        $best_url   = false;

        foreach ( $whitelist as $norm => $original ) {
            $approved_parts = array_filter( explode( '/', $norm ) );
            // Remove domain from parts
            if ( ! empty( $approved_parts ) && strpos( reset( $approved_parts ), self::PROTECTED_DOMAIN ) !== false ) {
                array_shift( $approved_parts );
            }

            $overlap = count( array_intersect( $slugs, $approved_parts ) );
            if ( $overlap > $best_score ) {
                $best_score = $overlap;
                $best_url   = $original;
            }
        }

        // Require at least 1 slug match
        return ( $best_score >= 1 ) ? $best_url : false;
    }

    /**
     * Emergency fallback: strip ALL damalion.com links (keeps anchor text).
     */
    private static function strip_all_internal_links( $html ) {
        $pattern = '/<a\s[^>]*href=["\'][^"\']*' . preg_quote( self::PROTECTED_DOMAIN, '/' ) . '[^"\']*["\'][^>]*>(.*?)<\/a>/si';
        return preg_replace( $pattern, '$1', $html );
    }

    /**
     * Map language code to fallback array key.
     */
    private static function lang_key( $lang ) {
        $lang = strtolower( substr( $lang, 0, 2 ) );
        // Only FR has dedicated fallback URLs; all others use EN
        return ( $lang === 'fr' ) ? 'fr' : 'en';
    }

    /**
     * Flush the whitelist cache (call after publishing new pages).
     */
    public static function flush_cache() {
        // Delete all language variants
        foreach ( array( 'en', 'fr', 'de', 'pt', 'es' ) as $lang ) {
            delete_transient( self::CACHE_KEY . '_' . $lang );
        }
    }
}
