<?php
/**
 * XML sitemap — registers /seoforge-sitemap.xml as a sitemap index, and
 * /seoforge-sitemap-{type}-{page}.xml as URL sets.
 *
 * Lightweight homemade implementation (the WP-core sitemap is fine but we want
 * full control over taxonomies, exclusions and the sitemap-index look).
 *
 * @package Brainwerk SEO Suite
 */

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

class Seoforge_Sitemap {

    public static function register( Seoforge_Loader $loader ) {
        if ( ! Seoforge_Settings::get( 'sitemap_enabled', true ) ) {
            return;
        }
        if ( Seoforge_Compat::should_stand_down() ) {
            return;
        }
        $loader->add_action( 'init', __CLASS__, 'add_rewrite' );
        $loader->add_filter( 'query_vars', __CLASS__, 'register_qv' );
        $loader->add_filter( 'rewrite_rules_array', __CLASS__, 'prepend_rules', 1 );
        $loader->add_action( 'template_redirect', __CLASS__, 'maybe_render', 1 );
        $loader->add_action( 'parse_request', __CLASS__, 'maybe_intercept', 1 );
    }

    public static function add_rewrite() {
        add_rewrite_rule( '^seoforge-sitemap\.xml$', 'index.php?seoforge_sitemap=index', 'top' );
        add_rewrite_rule( '^seoforge-sitemap-([a-z_-]+)-([0-9]+)\.xml$', 'index.php?seoforge_sitemap=$matches[1]&seoforge_sm_page=$matches[2]', 'top' );
    }

    /**
     * Make sure our rules sit at the very top of the array — some other SEO
     * plugins register catch-all *-sitemap.xml patterns that would otherwise
     * win the match.
     */
    public static function prepend_rules( $rules ) {
        $ours = array(
            '^seoforge-sitemap\.xml$' => 'index.php?seoforge_sitemap=index',
            '^seoforge-sitemap-([a-z_-]+)-([0-9]+)\.xml$' => 'index.php?seoforge_sitemap=$matches[1]&seoforge_sm_page=$matches[2]',
        );
        return $ours + (array) $rules;
    }

    /**
     * Match a URL path against our sitemap routing.
     *
     * @return array{type:string,page:int}|null Returns ['type'=>..., 'page'=>...]
     *     for our paths, null for anything else. Extracted into its own helper
     *     so the routing intent is unit-testable without a WP-Query context.
     */
    public static function match_path( $path ) {
        if ( ! is_string( $path ) ) {
            return null;
        }
        if ( ! preg_match( '#^/seoforge-sitemap(?:-([a-z_-]+)-(\d+))?\.xml$#', $path, $m ) ) {
            return null;
        }
        return array(
            'type' => isset( $m[1] ) && '' !== $m[1] ? $m[1] : 'index',
            'page' => isset( $m[2] ) ? (int) $m[2] : 0,
        );
    }

    /**
     * Last-resort catch: if the URI is our sitemap path but rewrite still
     * resolved it to something else (e.g. a competing -sitemap.xml rule),
     * force the query_var so maybe_render can pick it up.
     */
    public static function maybe_intercept( $wp ) {
        $uri  = isset( $_SERVER['REQUEST_URI'] ) ? wp_unslash( $_SERVER['REQUEST_URI'] ) : '';
        $path = (string) wp_parse_url( $uri, PHP_URL_PATH );
        $match = self::match_path( $path );
        if ( null === $match ) {
            return;
        }
        $wp->query_vars['seoforge_sitemap'] = $match['type'];
        $wp->query_vars['seoforge_sm_page'] = $match['page'];
        // Drop conflicting query vars that competing sitemap rules may have set.
        unset( $wp->query_vars['sitemap'], $wp->query_vars['sitemap_n'] );
        $wp->matched_rule  = '^seoforge-sitemap';
        $wp->matched_query = 'seoforge_sitemap=' . $match['type'];
    }

    public static function register_qv( $vars ) {
        $vars[] = 'seoforge_sitemap';
        $vars[] = 'seoforge_sm_page';
        return $vars;
    }

    public static function maybe_render() {
        $type = get_query_var( 'seoforge_sitemap' );
        if ( ! $type ) {
            return;
        }
        nocache_headers();
        header( 'Content-Type: application/xml; charset=UTF-8' );

        if ( 'index' === $type ) {
            self::render_index();
        } else {
            $page = max( 1, (int) get_query_var( 'seoforge_sm_page' ) );
            self::render_urlset( $type, $page );
        }
        exit;
    }

    private static function render_index() {
        $chunks = self::build_index();
        echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        echo '<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";
        foreach ( $chunks as $chunk ) {
            echo "<sitemap>\n";
            echo '<loc>' . esc_url( $chunk['url'] ) . "</loc>\n";
            if ( ! empty( $chunk['lastmod'] ) ) {
                echo '<lastmod>' . esc_html( $chunk['lastmod'] ) . "</lastmod>\n";
            }
            echo "</sitemap>\n";
        }
        echo '</sitemapindex>';
    }

    private static function build_index() {
        $max  = max( 50, (int) Seoforge_Settings::get( 'sitemap_max_per_chunk', 1000 ) );
        $list = array();

        foreach ( (array) Seoforge_Settings::get( 'sitemap_include_post_types', array( 'post', 'page' ) ) as $pt ) {
            $count = self::count_posts( $pt );
            if ( ! $count ) {
                continue;
            }
            $pages = (int) ceil( $count / $max );
            for ( $p = 1; $p <= $pages; $p++ ) {
                $list[] = array(
                    'url'     => home_url( "/seoforge-sitemap-{$pt}-{$p}.xml" ),
                    'lastmod' => self::latest_post_date( $pt ),
                );
            }
        }
        foreach ( (array) Seoforge_Settings::get( 'sitemap_include_taxonomies', array( 'category', 'post_tag' ) ) as $tx ) {
            $count = self::count_terms( $tx );
            if ( ! $count ) {
                continue;
            }
            $pages = (int) ceil( $count / $max );
            for ( $p = 1; $p <= $pages; $p++ ) {
                $list[] = array(
                    'url'     => home_url( "/seoforge-sitemap-tax-{$tx}-{$p}.xml" ),
                    'lastmod' => '',
                );
            }
        }
        return $list;
    }

    private static function render_urlset( $type, $page ) {
        $max    = max( 50, (int) Seoforge_Settings::get( 'sitemap_max_per_chunk', 1000 ) );
        $offset = ( $page - 1 ) * $max;

        echo '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
        echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . "\n";

        if ( 0 === strpos( $type, 'tax-' ) ) {
            $tx = substr( $type, 4 );
            $terms = get_terms( array(
                'taxonomy'   => $tx,
                'hide_empty' => true,
                'number'     => $max,
                'offset'     => $offset,
            ) );
            if ( ! is_wp_error( $terms ) ) {
                foreach ( $terms as $term ) {
                    $url = get_term_link( $term );
                    if ( is_wp_error( $url ) ) {
                        continue;
                    }
                    echo "<url>\n";
                    echo '<loc>' . esc_url( $url ) . "</loc>\n";
                    echo "<changefreq>weekly</changefreq>\n";
                    echo "</url>\n";
                }
            }
        } else {
            $q = new WP_Query( array(
                'post_type'      => $type,
                'post_status'    => 'publish',
                'posts_per_page' => $max,
                'offset'         => $offset,
                'orderby'        => 'modified',
                'order'          => 'DESC',
                'no_found_rows'  => true,
            ) );
            while ( $q->have_posts() ) {
                $q->the_post();
                $post_id = get_the_ID();
                $meta    = Seoforge_Meta::get_post_meta( $post_id );
                if ( ! empty( $meta['robots_noindex'] ) ) {
                    continue;
                }
                echo "<url>\n";
                echo '<loc>' . esc_url( get_permalink( $post_id ) ) . "</loc>\n";
                echo '<lastmod>' . esc_html( get_the_modified_date( 'c', $post_id ) ) . "</lastmod>\n";
                echo "<changefreq>weekly</changefreq>\n";
                echo "</url>\n";
            }
            wp_reset_postdata();
        }
        echo '</urlset>';
    }

    private static function count_posts( $post_type ) {
        $counts = wp_count_posts( $post_type );
        return $counts ? (int) $counts->publish : 0;
    }

    private static function latest_post_date( $post_type ) {
        global $wpdb;
        $date = $wpdb->get_var( $wpdb->prepare(
            "SELECT MAX(post_modified_gmt) FROM {$wpdb->posts} WHERE post_type = %s AND post_status = 'publish'",
            $post_type
        ) );
        return $date ? gmdate( 'c', strtotime( $date ) ) : '';
    }

    private static function count_terms( $taxonomy ) {
        $count = wp_count_terms( array( 'taxonomy' => $taxonomy, 'hide_empty' => true ) );
        return is_wp_error( $count ) ? 0 : (int) $count;
    }

    public static function flush_rewrite_on_activate() {
        self::add_rewrite();
        flush_rewrite_rules();
    }
}
