<?php
declare(strict_types=1);

namespace KbMarkdownImporter\Frontend;

use KbMarkdownImporter\Access\AccessController;
use KbMarkdownImporter\Plugin;
use KbMarkdownImporter\Repository\PageRepository;

final class Router
{
    public function boot(): void
    {
        add_action('init', [$this, 'addRewriteRules']);
        add_filter('request', [$this, 'routeRequest']);
        add_filter('query_vars', [$this, 'addQueryVars']);
        add_action('template_redirect', [$this, 'dispatch']);
    }

    public function addRewriteRules(): void
    {
        $base = trim((string) Plugin::settings()['docs_base_slug'], '/') ?: 'docs';
        add_rewrite_rule('^' . preg_quote($base, '#') . '/?$', 'index.php?kb_markdown_route=index', 'top');
        add_rewrite_rule('^' . preg_quote($base, '#') . '/([^/]+)/?$', 'index.php?kb_markdown_route=product&kb_product_slug=$matches[1]', 'top');
        add_rewrite_rule('^' . preg_quote($base, '#') . '/([^/]+)/([^/]+)/?$', 'index.php?kb_markdown_route=version&kb_product_slug=$matches[1]&kb_version_slug=$matches[2]', 'top');
        add_rewrite_rule('^' . preg_quote($base, '#') . '/([^/]+)/([^/]+)/(.+?)/?$', 'index.php?kb_markdown_route=page&kb_product_slug=$matches[1]&kb_version_slug=$matches[2]&kb_page_slug=$matches[3]', 'top');
    }

    public function addQueryVars(array $vars): array
    {
        return array_merge($vars, ['kb_markdown_route', 'kb_product_slug', 'kb_version_slug', 'kb_page_slug']);
    }

    public function routeRequest(array $queryVars): array
    {
        $route = $this->routeFromRequestUri();

        if (! $route) {
            return $queryVars;
        }

        $queryVars = [
            'kb_markdown_route' => $route['route'],
        ];

        if (! empty($route['product'])) {
            $queryVars['kb_product_slug'] = $route['product'];
        }

        if (! empty($route['version'])) {
            $queryVars['kb_version_slug'] = $route['version'];
        }

        if (! empty($route['page'])) {
            $queryVars['kb_page_slug'] = $route['page'];
        }

        return $queryVars;
    }

    public function dispatch(): void
    {
        $route = get_query_var('kb_markdown_route');
        $requestRoute = $this->routeFromRequestUri();

        if (! $route && $requestRoute) {
            $route = $requestRoute['route'];
        }

        if (! $route) {
            $queryRoute = $this->routeFromQuery();
            if (! $queryRoute) {
                return;
            }

            $requestRoute = $queryRoute;
            $route = $queryRoute['route'];
        }

        (new AccessController())->enforce();

        get_header();

        match ($route) {
            'index' => $this->renderIndex(),
            'product' => $this->renderProduct((string) ($requestRoute['product'] ?? get_query_var('kb_product_slug'))),
            'version' => $this->renderVersion((string) ($requestRoute['product'] ?? get_query_var('kb_product_slug')), (string) ($requestRoute['version'] ?? get_query_var('kb_version_slug'))),
            'page' => $this->renderPage((string) ($requestRoute['product'] ?? get_query_var('kb_product_slug')), (string) ($requestRoute['version'] ?? get_query_var('kb_version_slug')), trim((string) ($requestRoute['page'] ?? get_query_var('kb_page_slug')), '/')),
            default => $this->render404(),
        };

        get_footer();
        exit;
    }

    private function routeFromRequestUri(): array
    {
        $base = trim((string) Plugin::settings()['docs_base_slug'], '/') ?: 'docs';
        $path = (string) wp_parse_url((string) ($_SERVER['REQUEST_URI'] ?? ''), PHP_URL_PATH);
        $path = trim(rawurldecode($path), '/');

        if ($path === $base) {
            return ['route' => 'index'];
        }

        if (! str_starts_with($path . '/', $base . '/')) {
            return [];
        }

        $parts = array_values(array_filter(explode('/', substr($path, strlen($base))), static fn (string $part): bool => '' !== $part));

        if (1 === count($parts)) {
            return ['route' => 'product', 'product' => sanitize_title($parts[0])];
        }

        if (2 === count($parts)) {
            return ['route' => 'version', 'product' => sanitize_title($parts[0]), 'version' => sanitize_title($parts[1])];
        }

        return [
            'route' => 'page',
            'product' => sanitize_title($parts[0] ?? ''),
            'version' => sanitize_title($parts[1] ?? ''),
            'page' => sanitize_title(implode('/', array_slice($parts, 2))),
        ];
    }

    private function routeFromQuery(): array
    {
        $route = sanitize_key(wp_unslash((string) ($_GET['kb_markdown_route'] ?? '')));

        if (! $route) {
            return [];
        }

        return [
            'route' => $route,
            'product' => sanitize_title(wp_unslash((string) ($_GET['kb_product_slug'] ?? ''))),
            'version' => sanitize_title(wp_unslash((string) ($_GET['kb_version_slug'] ?? ''))),
            'page' => sanitize_title(wp_unslash((string) ($_GET['kb_page_slug'] ?? ''))),
        ];
    }

    public static function shortcodeDocsIndex(): string
    {
        return (new self())->captureRoute('index');
    }

    public static function shortcodeDocsApp(array $atts = []): string
    {
        if (! (new AccessController())->canView()) {
            return '';
        }

        $atts = shortcode_atts([
            'product' => '',
            'version' => '',
            'page' => '',
        ], $atts, 'kb_docs');

        $router = new self();
        $baseUrl = get_permalink() ?: home_url(add_query_arg([], (string) ($_SERVER['REQUEST_URI'] ?? '/')));

        UrlBuilder::beginEmbed($baseUrl);

        try {
            $route = sanitize_key(wp_unslash((string) ($_GET['kb_docs_route'] ?? '')));
            $product = sanitize_title(wp_unslash((string) ($_GET['kb_docs_product'] ?? $atts['product'])));
            $version = sanitize_title(wp_unslash((string) ($_GET['kb_docs_version'] ?? $atts['version'])));
            $page = sanitize_title(wp_unslash((string) ($_GET['kb_docs_page'] ?? $atts['page'])));

            if (! $route) {
                $route = $product ? ($version ? ($page ? 'page' : 'version') : 'product') : 'index';
            }

            return $router->captureRoute($route, $product, $version, $page);
        } finally {
            UrlBuilder::endEmbed();
        }
    }

    public static function shortcodeProductIndex(array $atts): string
    {
        $atts = shortcode_atts(['product' => ''], $atts, 'kb_product_index');
        $router = new self();

        return $router->captureRoute('product', sanitize_title((string) $atts['product']));
    }

    private function renderIndex(): void
    {
        echo $this->captureRoute('index');
    }

    private function captureIndex(): string
    {
        $settings = Plugin::settings();

        return (new TemplateLoader())->capture('documentation-index', [
            'products' => self::productsWithVersions(),
            'settings' => $settings,
            'updates' => ProductUpdatesFeed::items($settings),
            'base_slug' => trim((string) $settings['docs_base_slug'], '/'),
            'url_builder' => UrlBuilder::class,
        ]);
    }

    private function renderProduct(string $productSlug): void
    {
        echo $this->captureRoute('product', $productSlug);
    }

    private function captureProduct(string $productSlug): string
    {
        $product = get_term_by('slug', $productSlug, 'kb_product');
        if (! $product) {
            return $this->capture404();
        }

        $versions = $this->versionsForProduct($productSlug);

        return (new TemplateLoader())->capture('product', [
            'product' => $product,
            'versions' => $versions,
            'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
            'url_builder' => UrlBuilder::class,
        ]);
    }

    private function renderVersion(string $productSlug, string $versionSlug): void
    {
        echo $this->captureRoute('version', $productSlug, $versionSlug);
    }

    private function captureVersion(string $productSlug, string $versionSlug): string
    {
        $product = get_term_by('slug', $productSlug, 'kb_product');
        $version = get_term_by('slug', $versionSlug, 'kb_version');

        if (! $product || ! $version) {
            return $this->capture404();
        }

        $landing = $this->landingPageForVersion($productSlug, $versionSlug);
        if ($landing) {
            return $this->captureDocPage($landing, $productSlug, $versionSlug);
        }

        return (new TemplateLoader())->capture('version', [
            'product' => $product,
            'version' => $version,
            'versions' => $this->versionsForProduct($productSlug),
            'pages' => $this->pagesForVersion($productSlug, $versionSlug),
            'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
            'url_builder' => UrlBuilder::class,
        ]);
    }

    private function renderPage(string $productSlug, string $versionSlug, string $pageSlug): void
    {
        echo $this->captureRoute('page', $productSlug, $versionSlug, $pageSlug);
    }

    private function capturePage(string $productSlug, string $versionSlug, string $pageSlug): string
    {
        $post = (new PageRepository())->findFrontendPage($productSlug, $versionSlug, $pageSlug);

        if (! $post && '' === $pageSlug) {
            $post = (new PageRepository())->findFrontendPage($productSlug, $versionSlug, 'index');
        }

        if (! $post) {
            return $this->capture404();
        }

        return $this->captureDocPage($post, $productSlug, $versionSlug);
    }

    private function renderDocPage(\WP_Post $post, string $productSlug, string $versionSlug): void
    {
        echo $this->captureDocPage($post, $productSlug, $versionSlug);
    }

    private function captureDocPage(\WP_Post $post, string $productSlug, string $versionSlug): string
    {
        $product = get_term_by('slug', $productSlug, 'kb_product');
        $version = get_term_by('slug', $versionSlug, 'kb_version');
        $navTree = json_decode((string) get_post_meta($post->ID, '_kb_nav_tree', true), true);

        return (new TemplateLoader())->capture('page', [
            'post' => $post,
            'product' => $product,
            'version' => $version,
            'versions' => $this->versionsForProduct($productSlug),
            'nav_tree' => is_array($navTree) ? $navTree : [],
            'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
            'product_slug' => $productSlug,
            'version_slug' => $versionSlug,
            'url_builder' => UrlBuilder::class,
        ]);
    }

    private function captureRoute(string $route, string $productSlug = '', string $versionSlug = '', string $pageSlug = ''): string
    {
        $content = match ($route) {
            'index' => $this->captureIndex(),
            'product' => $productSlug ? $this->captureProduct($productSlug) : $this->captureIndex(),
            'version' => ($productSlug && $versionSlug) ? $this->captureVersion($productSlug, $versionSlug) : $this->captureIndex(),
            'page' => ($productSlug && $versionSlug) ? $this->capturePage($productSlug, $versionSlug, $pageSlug) : $this->captureIndex(),
            default => $this->captureIndex(),
        };

        return $this->captureShell($content, $productSlug, $versionSlug, $pageSlug);
    }

    private function captureShell(string $content, string $productSlug = '', string $versionSlug = '', string $pageSlug = ''): string
    {
        $activePages = [];

        if ($productSlug && $versionSlug) {
            $activePages = $this->pagesForVersion($productSlug, $versionSlug);
        }

        return (new TemplateLoader())->capture('docs-app', [
            'content' => $content,
            'products' => self::productsWithVersions(),
            'active_product_slug' => $productSlug,
            'active_version_slug' => $versionSlug,
            'active_page_slug' => $pageSlug,
            'active_pages' => $activePages,
            'base_slug' => trim((string) Plugin::settings()['docs_base_slug'], '/'),
            'url_builder' => UrlBuilder::class,
        ]);
    }

    private function render404(): void
    {
        status_header(404);
        (new TemplateLoader())->render('search', [
            'title' => __('Dokumentationsseite nicht gefunden.', 'kb-markdown-importer'),
            'results' => [],
            'query' => '',
        ]);
    }

    private function capture404(): string
    {
        status_header(404);
        return (new TemplateLoader())->capture('search', [
            'title' => __('Dokumentationsseite nicht gefunden.', 'kb-markdown-importer'),
            'results' => [],
            'query' => '',
        ]);
    }

    public static function productsWithVersions(): array
    {
        $products = get_terms(['taxonomy' => 'kb_product', 'hide_empty' => false]);
        $items = [];

        if (is_wp_error($products)) {
            return [];
        }

        foreach ($products as $product) {
            $versions = (new self())->versionsForProduct($product->slug);

            if (! $versions) {
                continue;
            }

            $items[] = [
                'term' => $product,
                'versions' => $versions,
            ];
        }

        return $items;
    }

    private function versionsForProduct(string $productSlug): array
    {
        $query = new \WP_Query([
            'post_type' => 'kb_doc_page',
            'post_status' => 'publish',
            'posts_per_page' => -1,
            'fields' => 'ids',
            'tax_query' => [
                ['taxonomy' => 'kb_product', 'field' => 'slug', 'terms' => $productSlug],
            ],
        ]);
        $versions = [];

        foreach ($query->posts as $postId) {
            foreach (wp_get_object_terms((int) $postId, 'kb_version') as $term) {
                $versions[$term->slug] = $term;
            }
        }

        uasort($versions, static fn ($a, $b): int => strnatcasecmp($b->name, $a->name));

        return array_values($versions);
    }

    private function pagesForVersion(string $productSlug, string $versionSlug): array
    {
        $query = new \WP_Query([
            'post_type' => 'kb_doc_page',
            'post_status' => 'publish',
            'posts_per_page' => -1,
            'meta_key' => '_kb_nav_order',
            'orderby' => ['meta_value_num' => 'ASC', 'title' => 'ASC'],
            'tax_query' => [
                'relation' => 'AND',
                ['taxonomy' => 'kb_product', 'field' => 'slug', 'terms' => $productSlug],
                ['taxonomy' => 'kb_version', 'field' => 'slug', 'terms' => $versionSlug],
            ],
        ]);

        return $query->posts;
    }

    private function landingPageForVersion(string $productSlug, string $versionSlug): ?\WP_Post
    {
        $repository = new PageRepository();
        $landing = $repository->findFrontendPage($productSlug, $versionSlug, '');

        if ($landing) {
            return $landing;
        }

        $pages = $this->pagesForVersion($productSlug, $versionSlug);

        return $pages[0] ?? null;
    }
}
