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

/**
 * Service for handling the Single Page Application (SPA) wrapper and template rendering logic.
 */
class NEXDIRECTORY_SPA_Service {

    /**
     * @var NEXDIRECTORY_Frontend_Navigation_Service
     */
    private $frontend_nav;

    /**
     * Constructor.
     *
     * @param NEXDIRECTORY_Frontend_Navigation_Service $frontend_nav Needed to resolve dynamic routes and sticky nav.
     */
    public function __construct($frontend_nav) {
        $this->frontend_nav = $frontend_nav;
    }

    /**
     * Checks if the SPA Hybrid mode is enabled in the settings.
     */
    public function is_spa_hybrid_enabled() {
        $requested = $this->frontend_nav->is_option_enabled('nexdirectory_enable_spa_hybrid', '1');

        // Route-specific assets are not yet hydrated by the partial-page router.
        // Keep navigation on reliable full page loads unless a developer explicitly
        // opts into the experimental runtime after supplying an asset manifest.
        return (bool) apply_filters('nexdirectory_enable_spa_hybrid_runtime', false, $requested);
    }

    /**
     * Returns an array of core template filenames that support SPA rendering.
     */
    public function get_spa_hybrid_main_templates() {
        return [
            'events-archive.php',
            'events-map.php',
            'MapPage-tripleColumn.php',
            'events-calendar.php',
            'account-app.php',
            'submit-event.php',
            'whos-attending.php',
            'notifications.php',
            'whats-new.php',
            'auth.php',
            'user-management.php',
            'moderation-queue.php',
            'claim-listing.php',
            'organizers.php',
            'single-event.php',
        ];
    }

    /**
     * Checks if a specific template file is registered as an SPA-compatible view.
     */
    public function is_spa_hybrid_main_template($template_file) {
        $template_name = sanitize_file_name((string) wp_basename((string) $template_file));
        if ($template_name === '') {
            return false;
        }
        return in_array($template_name, $this->get_spa_hybrid_main_templates(), true);
    }

    /**
     * Generates a whitelist of all platform URLs that should be intercepted by the SPA router.
     */
    public function get_spa_hybrid_route_urls() {
        // Fallback or retrieval of the current active site profile (events, directory, etc.)
        $profile_key = sanitize_key((string) get_option('nexdirectory_site_profile', 'events'));

        $definitions = [
            ['option' => 'nexdirectory_page_events_url', 'route' => ['events', 'listings']],
            ['option' => 'nexdirectory_page_events_map_url', 'route' => ['map', 'events-map']],
            ['option' => 'nexdirectory_page_events_calendar_url', 'route' => ['calendar', 'events-calendar']],
            ['option' => 'nexdirectory_page_submit_event_url', 'route' => ['submit', 'add-new-event']],
            ['option' => 'nexdirectory_page_my_events_url', 'route' => ['my-events', 'workspace', 'my_items']],
            ['option' => 'nexdirectory_page_my_tickets_url', 'route' => ['attending', 'my-tickets', 'saved']],
            ['option' => 'nexdirectory_page_whos_attending_url', 'route' => ['attendees', 'whos-attending']],
            ['option' => 'nexdirectory_page_account_url', 'route' => ['account', 'my-account']],
            ['option' => 'nexdirectory_page_login_signup_url', 'route' => ['auth', 'login-signup']],
            ['option' => 'nexdirectory_page_notifications_url', 'route' => ['notifications', 'inbox']],
            ['option' => 'nexdirectory_claim_listing_page_url', 'route' => ['claim', 'claim-listing']],
        ];
        $definitions = (array) apply_filters('nexdirectory_extension_spa_route_definitions', $definitions, $profile_key);

        $urls = [];
        foreach ($definitions as $definition) {
            $route_slugs = isset($definition['route']) && is_array($definition['route']) ? (array) $definition['route'] : [(string) ($definition['route'] ?? '')];
            $route_slugs = array_values(array_filter(array_map('sanitize_title', $route_slugs)));
            $route_key = sanitize_key((string) ($route_slugs[0] ?? ''));
            $option_key = sanitize_key((string) ($definition['option'] ?? ''));
            
            if ($route_key === '' || $option_key === '' || empty($route_slugs)) {
                continue;
            }

            // Let the navigation service resolve the correct URL dynamically based on fallbacks
            $route_url = $this->frontend_nav->get_platform_page_url(
                $option_key,
                $route_slugs,
                home_url('/' . $route_key . '/')
            );
            
            $route_url = esc_url_raw((string) $route_url);
            if ($route_url !== '') {
                $urls[] = $route_url;
            }
        }

        $extra_urls = [
            $this->frontend_nav->get_platform_page_url('nexdirectory_page_whats_new_url', 'whats-new', home_url('/whats-new/')),
            home_url('/notifications/'),
        ];
        
        foreach ($extra_urls as $extra_url) {
            $extra_url = esc_url_raw((string) $extra_url);
            if ($extra_url !== '') {
                $urls[] = $extra_url;
            }
        }

        foreach (['user-management', 'moderation-queue', 'organizers'] as $fallback_slug) {
            $page = get_page_by_path(sanitize_title((string) $fallback_slug));
            if (!$page instanceof WP_Post || is_wp_error($page)) continue;
            if ((string) get_post_status((int) $page->ID) !== 'publish') continue;
            
            $page_url = esc_url_raw((string) get_permalink((int) $page->ID));
            if ($page_url !== '') {
                $urls[] = $page_url;
            }
        }

        // Normalize paths to ensure trailing slashes and query strings are handled cleanly
        $normalized = [];
        foreach ($urls as $candidate_url) {
            $candidate_url = esc_url_raw((string) $candidate_url);
            if ($candidate_url === '') continue;
            
            $path = (string) wp_parse_url($candidate_url, PHP_URL_PATH);
            $path = '/' . ltrim($path, '/');
            if ($path !== '/') {
                $path = untrailingslashit($path);
            }
            
            $query = (string) wp_parse_url($candidate_url, PHP_URL_QUERY);
            $normalized_url = home_url($path);
            if ($query !== '') {
                $normalized_url .= '?' . ltrim($query, '?');
            }
            $normalized[] = esc_url_raw((string) $normalized_url);
        }

        return array_values(array_unique(array_filter($normalized)));
    }

    /**
     * Includes a frontend template and optionally wraps it in SPA metadata.
     *
     * @param string $template_file
     * @param array<string, mixed> $context
     * @param object|null $template_host Object to bind as $this inside template scope.
     * @return string
     */
    public function render_frontend_template($template_file, array $context = [], $template_host = null) {
        $template_file = trim((string) $template_file);
        if ($template_file === '') {
            return '';
        }
        
        // Resolve template from plugin root. This service lives in includes/utilities/,
        // so plugin_dir_path(dirname(__FILE__)) would incorrectly point to includes/.
        $path = plugin_dir_path(NEXDIRECTORY_PLUGIN_FILE) . 'templates/frontend/' . ltrim($template_file, '/');
        if (!is_readable($path)) {
            return '';
        }

        $template_name = sanitize_file_name((string) wp_basename((string) $template_file));
        
        $uniform_gutter_exclusions = [
            'widget-events-grid.php',
            'widget-events-bento.php',
            'widget-events-ajax-search.php',
        ];
        $apply_uniform_gutter = !in_array($template_name, $uniform_gutter_exclusions, true);

        ob_start();

        // Safely render sticky nav if it hasn't been output yet in this request lifecycle
        if ($this->frontend_nav->should_show_frontend_sticky_nav_for_current_user() && did_action('nexdirectory_frontend_sticky_nav_rendered') === 0) {
            if (method_exists($this->frontend_nav, 'render_frontend_sticky_nav')) {
	                $sticky_markup = $this->frontend_nav->render_frontend_sticky_nav();
	                if ($sticky_markup !== '') {
	                    // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Markup is sanitized by esc_nexdirectory_plugin_markup() against the plugin-owned allow-list.
	                    echo esc_nexdirectory_plugin_markup((string) $sticky_markup);
	                    do_action('nexdirectory_frontend_sticky_nav_rendered');
	                }
            }
        }
        
        // Safely render auth links if they haven't been output yet
	        if ($this->frontend_nav->is_option_enabled('nexdirectory_show_frontend_auth_links', '1') && did_action('nexdirectory_frontend_auth_links_rendered') === 0) {
	            if (method_exists($this->frontend_nav, 'render_frontend_auth_links')) {
	                $auth_links_markup = (string) $this->frontend_nav->render_frontend_auth_links();
	                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Markup is sanitized by esc_nexdirectory_plugin_markup() against the plugin-owned allow-list.
	                echo esc_nexdirectory_plugin_markup($auth_links_markup);
	                do_action('nexdirectory_frontend_auth_links_rendered');
	            }
	        }

	        $template_markup = (string) $this->include_frontend_template($path, $context, $template_host);
	        // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Markup is sanitized by esc_nexdirectory_plugin_markup() against the plugin-owned allow-list.
	        echo esc_nexdirectory_plugin_markup($template_markup);
	        if (did_action('nexdirectory_frontend_footer_rendered') === 0 && method_exists($this->frontend_nav, 'render_frontend_platform_footer')) {
	            $footer_markup = $this->frontend_nav->render_frontend_platform_footer();
	            if ($footer_markup !== '') {
	                // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Markup is sanitized by esc_nexdirectory_plugin_markup() against the plugin-owned allow-list.
	                echo esc_nexdirectory_plugin_markup((string) $footer_markup);
	                do_action('nexdirectory_frontend_footer_rendered');
	            }
	        }
        $markup = (string) ob_get_clean();
        
        if ($markup === '') {
            return '';
        }

        // Keep utility styles inside plugin-rendered markup so common classes such
        // as .container, .grid, and .hidden cannot restyle the active theme.
        $markup = '<div class="nxd-ui" data-nexdirectory-tailwind-scope="1">' . $markup . '</div>';

        // Apply platform-wide uniform padding if appropriate
        if ($apply_uniform_gutter) {
            static $uniform_gutter_style_printed = false;
            $uniform_gutter_style = '';
            if (!$uniform_gutter_style_printed) {
                if (method_exists($this->frontend_nav, 'get_page_side_padding_value')) {
                    $page_side_padding = (string) $this->frontend_nav->get_page_side_padding_value('10%');
                } else {
                    $page_side_padding = '10%';
                }
                if (method_exists($this->frontend_nav, 'get_page_side_padding_mobile_value')) {
                    $page_side_padding_mobile = (string) $this->frontend_nav->get_page_side_padding_mobile_value($page_side_padding);
                } else {
                    $page_side_padding_mobile = $page_side_padding;
                }

                $uniform_gutter_css = ':root{--nexdirectory-page-side-padding:' . esc_attr($page_side_padding) . ';--nexdirectory-page-side-padding-mobile:' . esc_attr($page_side_padding_mobile) . ';}'
                    . '@media (max-width: 767px){:root{--nexdirectory-page-side-padding:var(--nexdirectory-page-side-padding-mobile);}}'
                    . '[data-nexdirectory-uniform-page="1"]{--nexdirectory-uniform-inner-gutter:clamp(18px,2vw,32px);width:100%;max-width:100%;box-sizing:border-box;padding-left:var(--nexdirectory-page-side-padding)!important;padding-right:var(--nexdirectory-page-side-padding)!important;}'
                    . '[data-nexdirectory-uniform-page="1"]>:is(section,div,main,article,header,footer,nav){max-width:100%;}'
                    . '[data-nexdirectory-uniform-page="1"] :is(.nexdirectory-layout,.nexdirectory-submit-wrap,.nexdirectory-auth-grid,.nexdirectory-my-events,.nexdirectory-theme-shell,.nexdirectory-page-banner,.nexdirectory-event-page,.nexdirectory-event-layout-modern,.nexdirectory-archive-layout,.nexdirectory-map-layout){max-width:100%!important;margin-left:0!important;margin-right:0!important;padding-left:0!important;padding-right:0!important;}'
                    . '[data-nexdirectory-uniform-page="1"] :is(.max-w-7xl,.max-w-6xl,.max-w-screen-2xl).mx-auto{max-width:100%!important;margin-left:0!important;margin-right:0!important;}'
                    . '[data-nexdirectory-uniform-page="1"] .nexdirectory-account-shell,[data-nexdirectory-uniform-page="1"] .nexdirectory-account-panel,[data-nexdirectory-uniform-page="1"] main.max-w-6xl.mx-auto{padding-left:var(--nexdirectory-uniform-inner-gutter)!important;padding-right:var(--nexdirectory-uniform-inner-gutter)!important;}';
                wp_register_style('nexdirectory-uniform-page-gutter', false, [], defined('NEXDIRECTORY_DB_VERSION') ? NEXDIRECTORY_DB_VERSION : null);
                wp_enqueue_style('nexdirectory-uniform-page-gutter');
                wp_add_inline_style('nexdirectory-uniform-page-gutter', wp_strip_all_tags($uniform_gutter_css));
                $uniform_gutter_style_printed = true;
            }
            $markup = $uniform_gutter_style . '<div class="nexdirectory-uniform-page-gutter" data-nexdirectory-uniform-page="1">' . $markup . '</div>';
        }

        // If SPA is not enabled, or this isn't a core template, just return the raw markup
        if (!$this->is_spa_hybrid_enabled() || !$this->is_spa_hybrid_main_template($template_file)) {
            return $markup;
        }

        // Wrap the final HTML in the SPA identification attributes
        return '<div class="nexdirectory-spa-view" data-nexdirectory-spa-main="1" data-nexdirectory-template="' . esc_attr($template_name) . '">' . $markup . '</div>';
    }

    /**
     * Includes a template with optional bound object scope so legacy templates
     * can keep calling $this->... methods from NEXDIRECTORY_Plugin.
     *
     * @param string $path
     * @param array<string, mixed> $context
     * @param object|null $template_host
     * @return string
     */
    private function include_frontend_template($path, array $context, $template_host = null) {
        $renderer = function ($template_path, $template_context) {
            extract($template_context, EXTR_SKIP);
            ob_start();
            include $template_path;
            return (string) ob_get_clean();
        };

        if (is_object($template_host)) {
            $scope = get_class($template_host);
            $bound_renderer = \Closure::bind($renderer, $template_host, $scope);
            if ($bound_renderer instanceof \Closure) {
                return (string) $bound_renderer($path, $context);
            }
        }

        return (string) $renderer($path, $context);
    }
}
