<?php

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

class WLH_Render
{
    private static $done = false;
    private static $swHeaderDone = false;
    private static $swFooterDone = false;
    private static $snipDone = false;

    public static function register_hooks()
    {
        add_action('template_redirect', array('WLH_Render', 'maybe_start'), 1);
    }

    public static function maybe_start()
    {
        if (function_exists('is_admin') && is_admin()) {
            return;
        }
        if (function_exists('is_feed') && is_feed()) {
            return;
        }
        if (defined('REST_REQUEST') && REST_REQUEST) {
            return;
        }
        if (defined('DOING_AJAX') && DOING_AJAX) {
            return;
        }
        // Start the buffer when THIS page has anything to render: the homepage
        // block (front page + wlh_links, as before) OR any sitewide links at
        // all (0.8.0 — every page, wlh_sw_links) OR the homepage snippet (0.10.1, wlh_snippet — panel-delivered raw HTML for the homepage footer.
        // The front-page gate applies to the homepage set only; the snippet is front-page only.
        $hasHome = false;
        if (function_exists('is_front_page') && is_front_page()) {
            $hasHome = !empty((array)get_option(WLH_OPT_LINKS, array()));
        }
        if (!$hasHome && function_exists('is_front_page') && is_front_page() && (string)get_option(WLH_OPT_SNIPPET, '') !== '') {
            $hasHome = true;
        }
        if (!$hasHome && empty((array)get_option(WLH_OPT_SW_LINKS, array()))) {
            return;
        }
        ob_start(array('WLH_Render', 'filter_buffer'));
    }

    public static function filter_buffer($html)
    {
        if (!is_string($html) || $html === '') {
            return $html;
        }
        // Top of the page: the homepage block (front page only — its behavior
        // is unchanged) and the sitewide header block share one insertion
        // point, homepage first. Independent one-shot flags per block: the
        // buffer can flush in chunks and no block may repeat.
        $top = '';
        if (!self::$done && function_exists('is_front_page') && is_front_page()) {
            $home = self::build_block();
            if ($home !== '') {
                self::$done = true;
                $top .= $home;
            }
        }
        if (!self::$swHeaderDone) {
            $sw = self::build_sw_block('header');
            if ($sw !== '') {
                self::$swHeaderDone = true;
                $top .= $sw;
            }
        }
        if ($top !== '') {
            $pos = stripos($html, '</header>');
            if ($pos !== false) {
                $at = $pos + 9;
                $html = substr($html, 0, $at) . $top . substr($html, $at);
            } elseif (preg_match('/<body[^>]*>/i', $html, $m, PREG_OFFSET_CAPTURE)) {
                $at = $m[0][1] + strlen($m[0][0]);
                $html = substr($html, 0, $at) . $top . substr($html, $at);
            } else {
                $html = $top . $html;
            }
        }
        // Sitewide footer block: right before </body> (appended when the theme
        // outputs none).
        if (!self::$swFooterDone) {
            $sw = self::build_sw_block('footer');
            if ($sw !== '') {
                self::$swFooterDone = true;
                $pos = strripos($html, '</body>');
                if ($pos !== false) {
                    $html = substr($html, 0, $pos) . $sw . substr($html, $pos);
                } else {
                    $html .= $sw;
                }
            }
        }
        // Homepage footer snippet (0.10.1): panel-delivered raw HTML for the homepage footer slot, inserted right before </body> (appended when the theme outputs none).
        if (!self::$snipDone && function_exists('is_front_page') && is_front_page()) {
            $snip = (string)get_option(WLH_OPT_SNIPPET, '');
            if ($snip !== '') {
                self::$snipDone = true;
                $pos = strripos($html, '</body>');
                if ($pos !== false) {
                    $html = substr($html, 0, $pos) . $snip . substr($html, $pos);
                } else {
                    $html .= $snip;
                }
            }
        }
        return $html;
    }

    private static function build_block()
    {
        $links = (array)get_option(WLH_OPT_LINKS, array());
        if (empty($links)) {
            return '';
        }
        return self::build_links_block($links, '');
    }

    /**
     * The sitewide (0.8.0) block for one region: $where = 'header'|'footer'.
     * A link renders in the region when its position is that region or 'both'
     * (missing/garbage position = 'both'). Same wrapper/anchor builder as the
     * homepage block, but the hide pool is seeded with the '|sw'-salted site
     * so a page's sw styles never mirror the homepage block 1:1.
     */
    private static function build_sw_block($where)
    {
        $links = (array)get_option(WLH_OPT_SW_LINKS, array());
        if (empty($links)) {
            return '';
        }
        $want = array();
        foreach ($links as $l) {
            if (!is_array($l) || !isset($l['url'], $l['anchor'])) {
                continue;
            }
            $pos = isset($l['position']) ? (string)$l['position'] : 'both';
            if ($pos !== 'header' && $pos !== 'footer' && $pos !== 'both') {
                $pos = 'both';
            }
            if ($pos !== 'both' && $pos !== $where) {
                continue;
            }
            $want[] = $l;
        }
        if (empty($want)) {
            return '';
        }
        return self::build_links_block($want, '|sw');
    }

    private static function build_links_block($links, $salt)
    {
        $site = function_exists('home_url') ? (string)home_url() : '';
        $parts = array();
        foreach ($links as $l) {
            if (!is_array($l) || !isset($l['url'], $l['anchor'])) {
                continue;
            }
            $url = esc_url((string)$l['url']);
            $anchor = esc_html((string)$l['anchor']);
            if ($url === '' || $anchor === '') {
                continue;
            }
            $rel = '';
            if (isset($l['rel']) && (string)$l['rel'] !== '') {
                $rel = ' rel="' . esc_attr((string)$l['rel']) . '"';
            }
            $parts[] = self::hidden_anchor($url, $anchor, $rel, $site . $salt);
        }
        if (empty($parts)) {
            return '';
        }
        // Plausible builder/theme wrapper classes — wide pool so the wrapper is
        // not a fingerprint either.
        $pool = array(
            'elementor-widget-container', 'wp-block-group__inner-container', 'wpb_wrapper',
            'et_pb_text_inner', 'vc_column-inner', 'fusion-text', 'entry-content-inner',
            'widget-inner', 'tdb-block-inner', 'kt-inside-inner-col', 'wp-container-inner',
            'so-widget-content', 'x-column-inner', 'g-content', 'sow-widget-block',
            'fl-builder-content-inner', 'av_textblock_section', 'uncode_text_column',
            'qodef-e-inner', 'bdevs-element-content', 'ct-text-block', 'oxy-rich-text',
            'bricks-element-inner', 'kadence-blocks-column-inner', 'generateblocks-container',
            'uagb-container-inner', 'premium-container-inner', 'blocksy-content-inner',
        );
        $seed = $site !== '' ? crc32($site . $salt) : mt_rand();
        if ($seed % 6 === 0) {
            return '<div>' . implode('', $parts) . '</div>';
        }
        return '<div class="' . $pool[$seed % count($pool)] . '">' . implode('', $parts) . '</div>';
    }

    /**
     * One link, hidden with a technique chosen DETERMINISTICALLY per-link
     * (crc32(url.site)) so a single site mixes techniques and no one regex
     * matches them all. Property order / spacing / offset vary with the same
     * seed to defeat exact-string fingerprints. The anchor stays a plain,
     * valid <a href> — crawlers and the panel's href-based probe are unaffected.
     *
     * Public since 0.7.0: WLH_Static hides the blocks it writes into managed
     * static sites with the exact same pool (site = the static's host), so a
     * static donor's markup is indistinguishable from a WP donor's.
     */
    public static function hidden_anchor($url, $anchor, $rel, $site)
    {
        $seed = crc32($url . $site);
        $n = 3000 + ($seed % 12000);
        switch ($seed % 5) {
            case 0: // off-screen fixed (the original technique)
                $props = array('position: fixed', 'left: ' . $n . 'px', 'top: 0');
                break;
            case 1: // off-screen absolute + huge negative indent
                $props = array('position: absolute', 'text-indent: -' . $n . 'px');
                break;
            case 2: // clipped away
                $props = array('position: absolute', 'clip-path: inset(50%)', 'width: 1px', 'height: 1px', 'overflow: hidden');
                break;
            case 3: // zero-size box
                $props = array('display: inline-block', 'width: 0', 'height: 0', 'overflow: hidden');
                break;
            default: // invisible text, aria-hidden
                $props = array('font-size: 0', 'line-height: 0', 'color: transparent');
                break;
        }
        if ($seed % 2 === 1) {
            $props = array_reverse($props);
        }
        $sep = ($seed % 3 === 0) ? '; ' : ';';
        $aria = ($seed % 5 === 4) ? ' aria-hidden="true"' : '';
        return '<a href="' . $url . '"' . $rel . $aria . ' style="' . implode($sep, $props) . ';">' . $anchor . '</a>';
    }
}
