<?php
/**
 * Rank Forge — Breadcrumbs Engine 
 *
 * Three frontend surfaces, one trail builder:
 * - PHP snippet: the_custom_breadcrumbs — for theme developers
 * - Shortcode: [rankforge_breadcrumbs] — for page builders / editors
 * - Widget block: registered via WP core block (fallback to shortcode)
 *
 * JSON-LD BreadcrumbList is NOT emitted here — that's handled in
 * class-schema.php so it fires even when visual output is disabled
 * (the "Silent Action" requirement from the ticket).
 *
 * Settings live under option `rankforge_breadcrumbs`:
 * enabled bool master switch (default true)
 * separator string visual separator (default »)
 * home_text string label for the first crumb (default Home)
 * show_current bool append current page as last crumb (default true)
 *
 * @package RankForge
 * @since 1.3.0
 */

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

class RANKFORGE_Breadcrumbs {
    private static $instance = null;
    public static function instance() { if (!self::$instance) self::$instance = new self(); return self::$instance; }

    const OPTION = 'rankforge_breadcrumbs';

    private function __construct() {
        add_shortcode('rankforge_breadcrumbs', [$this, 'shortcode']);
        add_action('wp_head', [$this, 'inline_css'], 20);
    }

    public function inline_css() {
        $s = self::settings();
        if (empty($s['enabled'])) return;
        echo "<style id='rankforge-breadcrumbs-css'>.rankforge-breadcrumbs{font-size:0.875rem;line-height:1.5;color:#64748b;margin:0 0 1rem;}.rankforge-breadcrumbs a{color:inherit;text-decoration:none;border-bottom:1px dotted currentColor;}.rankforge-breadcrumbs a:hover{color:#0f172a;}.rankforge-breadcrumbs [aria-current=page]{color:#0f172a;font-weight:500;}.rankforge-bc-sep{margin:0 2px;opacity:0.6;}</style>\n";
    }

    public static function defaults() {
        return [
            'enabled'      => true,
            'separator'    => '»',
            'home_text'    => __('Home', 'rankforge'),
            'show_current' => true,
        ];
    }

    public static function settings() {
        $opt = get_option(self::OPTION, []);
        if (!is_array($opt)) $opt = [];
        return array_merge(self::defaults(), $opt);
    }

    /**
 * Build the ordered list of crumbs: [['name' => ..., 'url' => ...|null], ...]
 * The last item's url may be null when show_current is true (current page).
 */
    public function trail() {
        $s = self::settings();
        $trail = [[
            'name' => $s['home_text'],
            'url'  => home_url('/'),
        ]];

        if (is_front_page() || is_home()) {
            return $trail;
        }

        if (is_category() || is_tag() || is_tax()) {
            $term = get_queried_object();
            if ($term && !empty($term->term_id)) {
                // Walk ancestors for hierarchical taxonomies.
                $ancestors = array_reverse(get_ancestors($term->term_id, $term->taxonomy, 'taxonomy'));
                foreach ($ancestors as $aid) {
                    $a = get_term($aid, $term->taxonomy);
                    if ($a && !is_wp_error($a)) {
                        $trail[] = ['name' => $a->name, 'url' => get_term_link($a)];
                    }
                }
                $trail[] = ['name' => $term->name, 'url' => null];
            }
        } elseif (is_singular()) {
            $post = get_queried_object();
            // WooCommerce products get a shop-aware trail —
            // Home > Shop > [Category ancestors] > Product — instead of the
            // generic "Products" post-type-archive crumb.
            if ($post && $post->post_type === 'product' && function_exists('wc_get_page_id')) {
                $shop_id = wc_get_page_id('shop');
                if ($shop_id && $shop_id > 0 && get_post_status($shop_id) === 'publish') {
                    $trail[] = ['name' => get_the_title($shop_id), 'url' => get_permalink($shop_id)];
                }
                $p_terms = get_the_terms($post->ID, 'product_cat');
                if (!empty($p_terms) && !is_wp_error($p_terms)) {
                    $primary = $p_terms[0];
                    $ancestors = array_reverse(get_ancestors($primary->term_id, 'product_cat', 'taxonomy'));
                    foreach ($ancestors as $aid) {
                        $a = get_term($aid, 'product_cat');
                        if ($a && !is_wp_error($a)) $trail[] = ['name' => $a->name, 'url' => get_term_link($a)];
                    }
                    $trail[] = ['name' => $primary->name, 'url' => get_term_link($primary)];
                }
                $trail[] = ['name' => get_the_title($post), 'url' => null];
            } else {
            // Post-type archive crumb for non-core post types.
            if ($post && !in_array($post->post_type, ['post', 'page'], true)) {
                $pt_obj = get_post_type_object($post->post_type);
                if ($pt_obj && $pt_obj->has_archive) {
                    $trail[] = [
                        'name' => $pt_obj->labels->name,
                        'url'  => get_post_type_archive_link($post->post_type),
                    ];
                }
            }
            // For pages: include ancestors.
            if ($post && $post->post_type === 'page' && $post->post_parent) {
                $ancestors = array_reverse(get_post_ancestors($post));
                foreach ($ancestors as $aid) {
                    $trail[] = ['name' => get_the_title($aid), 'url' => get_permalink($aid)];
                }
            }
            // For posts: include primary category if the post type is 'post'.
            if ($post && $post->post_type === 'post') {
                $cats = get_the_category($post->ID);
                if (!empty($cats)) {
                    $cat = $cats[0];
                    $ancestors = array_reverse(get_ancestors($cat->term_id, 'category'));
                    foreach ($ancestors as $aid) {
                        $a = get_category($aid);
                        if ($a) $trail[] = ['name' => $a->name, 'url' => get_category_link($a->term_id)];
                    }
                    $trail[] = ['name' => $cat->name, 'url' => get_category_link($cat->term_id)];
                }
            }
            if ($post) {
                $trail[] = ['name' => get_the_title($post), 'url' => null];
            }
            } // end non-product singular
        } elseif (is_post_type_archive()) {
            $pt_obj = get_queried_object();
            if ($pt_obj) $trail[] = ['name' => $pt_obj->labels->name, 'url' => null];
        } elseif (is_search()) {
            /* translators: %s: search query string */
            $trail[] = ['name' => sprintf(__('Search: %s', 'rankforge'), get_search_query()), 'url' => null];
        } elseif (is_404()) {
            $trail[] = ['name' => __('Not found', 'rankforge'), 'url' => null];
        } elseif (is_author()) {
            $user = get_queried_object();
            if ($user) $trail[] = ['name' => $user->display_name, 'url' => null];
        } elseif (is_year() || is_month() || is_day()) {
            if (is_year())  $trail[] = ['name' => get_the_date('Y'), 'url' => null];
            if (is_month()) $trail[] = ['name' => get_the_date('F Y'), 'url' => null];
            if (is_day())   $trail[] = ['name' => get_the_date(), 'url' => null];
        }

        if (!$s['show_current']) {
            // Drop the last crumb when it's the current page (url === null).
            $last = end($trail);
            if ($last && $last['url'] === null) array_pop($trail);
        }

        return $trail;
    }

    public function render($extra_class = '') {
        $s = self::settings();
        if (empty($s['enabled'])) return '';

        $trail = $this->trail();
        if (count($trail) < 2 && (is_front_page() || is_home())) return '';

        $sep = '<span class="rankforge-bc-sep" aria-hidden="true">' . esc_html($s['separator']) . '</span>';
        $parts = [];
        $count = count($trail);
        foreach ($trail as $i => $crumb) {
            $is_last = ($i === $count - 1);
            if (!empty($crumb['url']) && !$is_last) {
                $parts[] = '<a href="' . esc_url($crumb['url']) . '">' . esc_html($crumb['name']) . '</a>';
            } else {
                $parts[] = '<span aria-current="page">' . esc_html($crumb['name']) . '</span>';
            }
        }

        $class = trim('rankforge-breadcrumbs ' . $extra_class);
        return '<nav class="' . esc_attr($class) . '" aria-label="' . esc_attr__('Breadcrumb', 'rankforge') . '">'
             . implode(' ' . $sep . ' ', $parts)
             . '</nav>';
    }

    public function shortcode($atts) {
        $atts = shortcode_atts(['class' => ''], $atts, 'rankforge_breadcrumbs');
        return $this->render(sanitize_html_class($atts['class']));
    }
}

/**
 * Template tag — drop into theme files.
 *
 * <?php if (function_exists('rankforge_breadcrumbs')) rankforge_breadcrumbs; ?>
 */
if (!function_exists('rankforge_breadcrumbs')) {
    function rankforge_breadcrumbs($echo = true) {
        $html = RANKFORGE_Breadcrumbs::instance()->render();
        if ($echo) { echo wp_kses_post( $html ); return; }
        return $html;
    }
}

/**
 * Deprecated back-compat alias for the original, unprefixed template tag.
 * Prefer the prefixed rankforge_breadcrumbs. Kept so themes that already
 * paste the legacy tag keep rendering.
 *
 * @deprecated Use rankforge_breadcrumbs instead.
 */
if (!function_exists('the_custom_breadcrumbs')) {
    // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedFunctionFound -- deprecated back-compat alias for rankforge_breadcrumbs; retained for themes using the legacy tag
    function the_custom_breadcrumbs($echo = true) {
        return rankforge_breadcrumbs($echo);
    }
}
