<?php

namespace Silvanus;

use Silvanus\includes\blocks\TimberAcfWpBlocks;
use Silvanus\includes\blocks\BlockModules;
use Silvanus\includes\helpers\Image;
use Silvanus\includes\helpers\TimberFunctions;
use Timber\Timber;
use Asset_Loader;
use WP_Block_Type_Registry;

if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
}

if (!class_exists('Silvanus')) {
    class Silvanus
    {
        public static function init(): void
        {
            // Only require WordPress hooks and Timber at bootstrap time.
            // ACF functions (like acf_register_block_type) may not exist yet here.
            if (is_callable('add_action') && class_exists('\Timber\Timber')) {
                Timber::init();
                $silvanus = new static();
                $silvanus->addContexts();
                $silvanus->addThemeSupport();
                $silvanus->addActions();
            } elseif (is_callable('add_action')) {
                add_action(
                    'admin_notices',
                    function () {
                        echo '<div class="error"><p>Silvanus requires Timber.';
                        echo ' Check if the plugins or libraries are installed and activated.</p></div>';
                    },
                );
            }

            // check if the Asset Loader plugin is available
            if (!is_plugin_active('asset-loader/asset-loader.php')) {
                add_action(
                    'admin_notices',
                    function () {
                        echo '<div class="error"><p>Silvanus requires the Asset Loader plugin.';
                        echo ' Check if the plugin is installed and activated.</p></div>';
                    },
                );
            }
        }

        /**
         * ACF field groups declared by CPT definition files.
         *
         * @var array<int, array>
         */
        private array $cptAcfFieldGroups = [];

        private function addActions(): void
        {
            add_action('wp_enqueue_scripts', $this->enqueueScripts(...));

            // Added so the built JS/CSS loads in the Gutenberg editor too
            add_action('enqueue_block_editor_assets', $this->enqueueEditorAssets(...));

            add_action('init', $this->wordpressInit(...), 1);
            add_action('acf/init', $this->addOptions(...));
            // Register CPT-provided ACF field groups (after ACF is ready)
            add_action('acf/init', $this->registerCptAcfFieldGroups(...), 20);

            add_action('after_setup_theme', $this->brandLogoSetup(...));
            // now deal with block registrations
            add_action('acf/init', $this->registerTwigBlocks(...), 20);

            // Populate ACF select choices (Top Menu Routing -> Menu to show)
            add_filter('acf/load_field/key=field_options_general_settings_top_menu_routing_routes_top_menu_route_menu', $this->acfPopulateTopMenuRoutingMenuChoices(...), 20);

            // Populate Footer Settings -> Columns -> Menu (term_id)
            add_filter(
                'acf/load_field/key=field_options_footer_settings_columns_footer_settings_column_menu_term_id',
                $this->acfPopulateNavMenuTermIdChoices(...),
                20,
            );

            add_filter('body_class', $this->addSlugToBodyClass(...));
            add_filter('timber/twig/functions', $this->silvanusTimbrFunctions(...));
            // Register per-block hooks/filters/validators from modules
            BlockModules::registerAll();

            // Prepare logo slider images once (keeps Twig clean)
            add_filter('timber/acf-gutenberg-blocks-data', $this->prepareImagesRepeatersInBlocks(...), 20);

            /**
             * Only allow ACF blocks (hide/disable core Gutenberg blocks).
             */
            add_filter('allowed_block_types_all', $this->hideCoreGutenbergBlocks(...), 10, 2);
        }

        private function addSlugToBodyClass($classes)
        {
            global $post;
            if (isset($post)) {
                $classes[] = $post->post_type . '-' . $post->post_name;
            }
            return $classes;
        }

        private function silvanusTimbrFunctions($functions)
        {
            $functions['getImageObject'] = [
                'callable' => [TimberFunctions::class, 'normalize_theme_image']
            ];

            $functions['languageAttributes'] = [
                'callable' => [TimberFunctions::class, 'language_attributes'],
                'options' => ['is_safe' => ['html']]
            ];

            return $functions;
        }

        /**
         * Populate the "Menu to show" select with WP Nav Menus.
         *
         * @param array $field ACF field array.
         * @return array
         */
        private function acfPopulateTopMenuRoutingMenuChoices(array $field): array
        {
            $field['choices'] = [];

            // Ensure the nav menu taxonomy exists (it should, but this avoids silent failure).
            if (!taxonomy_exists('nav_menu')) {
                return $field;
            }

            // Most reliable: query the nav_menu terms directly.
            $terms = get_terms([
                'taxonomy' => 'nav_menu',
                'hide_empty' => false,
            ]);

            if (is_array($terms)) {
                foreach ($terms as $term) {
                    if (isset($term->term_id, $term->name)) {
                        $field['choices'][(string) $term->term_id] = (string) $term->name;
                    }
                }
            }

            // Fallback, just in case.
            if ($field['choices'] === []) {
                $menus = wp_get_nav_menus();
                if (is_array($menus)) {
                    foreach ($menus as $menu) {
                        if (isset($menu->term_id, $menu->name)) {
                            $field['choices'][(string) $menu->term_id] = (string) $menu->name;
                        }
                    }
                }
            }

            return $field;
        }

        private function enqueueEditorAssets(): void
        {
            $this->registerAsset('main.js', [], 'main-editor.js');
            $this->registerAsset('main.css', [], 'main-editor.css');

            wp_enqueue_script('main-editor.js');
            wp_enqueue_style('main-editor.css');
        }

        private function addThemeSupport(): void
        {
            // add menu support
            add_theme_support('menus');
            add_theme_support('post-thumbnails');
            // let WordPress render <title> in wp_head() so every page gets a unique title
            add_theme_support('title-tag');
        }

        private function enqueueScripts(): void
        {
            $this->registerAsset('main.js');
            $this->registerAsset('main.css');

            wp_enqueue_script('main.js');
            wp_enqueue_style('main.css');

            // Optional child bundle: only wire it up when a child manifest actually declares it.
            if ($this->childManifestHas('child.js')) {
                $this->registerAsset('child.js', [], 'child.js');
                wp_enqueue_script('child.js');
            }

            // Footer settings for front-end JS enhancements (no markup changes required).
            $footerSettings = get_fields('options-footer-settings');
            $itemsPerColumn = 5;

            if (is_array($footerSettings) && isset($footerSettings['menu_items_per_column']) && is_numeric($footerSettings['menu_items_per_column'])) {
                $itemsPerColumn = (int) $footerSettings['menu_items_per_column'];
            }

            if ($itemsPerColumn < 1) {
                $itemsPerColumn = 1;
            }

            wp_add_inline_script(
                'main.js',
                'window.SilvanusFooter = window.SilvanusFooter || {}; window.SilvanusFooter.menuItemsPerColumn = ' . (int) $itemsPerColumn . ';',
                'before',
            );
        }

        /**
         * Add base Context values.
         */
        private function addContexts(): void
        {
            add_filter('timber/context', $this->addToContext(...));
        }

        /**
         * Fire for all init hooks.
         */
        private function wordpressInit(): void
        {
            register_nav_menu('primary', __('Main Menu'));
            register_nav_menu('second_menu', __('Secondary Menu'));
            register_nav_menu('contact_menu', __('Contact Menu'));
            register_nav_menu('policy_menu', __('Policy Menu'));
            register_nav_menu('footer_menu', __('Footer Menu'));

            $this->registerContentTypes();
        }

        private function addOptions(): void
        {
            acf_add_options_page([
                'post_id'     => 'options-site-settings',
                'page_title'    => 'Site Settings',
                'menu_title'    => 'Site Settings',
                'menu_slug'     => 'general-settings',
                'capability'    => 'edit_posts',
                'redirect'      => false,
            ]);
            acf_add_options_sub_page([
                'post_id'     => 'options-theme-settings',
                'page_title'    => 'Theme Settings',
                'menu_title'    => 'Theme Settings',
                'parent_slug'   => 'general-settings',
            ]);
            acf_add_options_sub_page([
                'post_id'     => 'options-footer-settings',
                'page_title'    => 'Footer Settings',
                'menu_title'    => 'Footer Settings',
                'parent_slug'   => 'general-settings',
            ]);
        }
        private function brandLogoSetup(): void
        {
            $defaults = [
                'height' => 100,
                'width' => 400,
                'flex-height' => true,
                'flex-width' => true,
                'header-text' => ['site-title', 'site-description'],
                'unlink-homepage-logo' => true,
            ];
            add_theme_support('custom-logo', $defaults);
        }

        /**
         * @throws \Exception
         */
        private function registerTwigBlocks(): void
        {
            TimberAcfWpBlocks::timber_block_init();
        }

        /**
         * Global Timber context.
         *
         * @param array $context Global context variables.
         */
        private function addToContext(array $context): array
        {
            // Add primary menu
            $context['menu'] = Timber::get_menu('primary');
            $context['second_menu'] = Timber::get_menu('second_menu');
            $context['policy_menu'] = Timber::get_menu('policy_menu');
            $context['contact_menu'] = $this->resolveTopMenu();
            $context['logo'] = $this->resolveHeaderLogo();
            $context['theme_settings'] = get_fields('options-theme-settings');
            $context['site_settings'] = get_fields('options-site-settings');
            $context['footer_settings'] = get_fields('options-footer-settings');

            $context['menus_by_id'] = [];
            // Provide all WP menus as Timber menu objects keyed by term_id,

            $menus = wp_get_nav_menus();
            if (is_array($menus)) {
                foreach ($menus as $m) {
                    if (!isset($m->term_id)) {
                        continue;
                    }

                    $termId = (int) $m->term_id;
                    if ($termId <= 0) {
                        continue;
                    }

                    $context['menus_by_id'][(string) $termId] = Timber::get_menu($termId);
                }
            }

            return $context;
        }

        private function registerContentTypes(): void
        {
            // Load CPT definitions from child first, then parent (child overrides by filename).
            $dirs = [
                get_stylesheet_directory() . '/content/types',
                get_template_directory() . '/content/types',
            ];

            $filesByName = [];

            foreach ($dirs as $dir) {
                if (!is_dir($dir) || !is_readable($dir)) {
                    continue;
                }

                $realDir = realpath($dir);
                if (!$realDir) {
                    continue;
                }

                $files = glob($dir . '/*.cpt.php');
                if (!$files) {
                    continue;
                }

                foreach ($files as $file) {
                    $real = realpath($file);
                    // Keep the containment check against the directory this file was scanned from.
                    if (!$real || str_starts_with($real, $realDir) === false) {
                        continue;
                    }

                    $base = basename($real); // e.g. projects.cpt.php
                    // First one wins (child is listed first), so parent gets ignored if child provides same filename.
                    if (!isset($filesByName[$base])) {
                        $filesByName[$base] = $real;
                    }
                }
            }

            if (empty($filesByName)) {
                return;
            }

            $allowedArgKeys = [
                'labels',
                'public',
                'publicly_queryable',
                'exclude_from_search',
                'show_ui',
                'show_in_menu',
                'show_in_admin_bar',
                'show_in_nav_menus',
                'show_in_rest',
                'menu_position',
                'menu_icon',
                'supports',
                'hierarchical',
                'has_archive',
                'rewrite',
                'query_var',
                'can_export',
                'delete_with_user',
                'description',
            ];

            foreach ($filesByName as $real) {
                $def = require $real;
                if (!is_array($def)) {
                    continue;
                }

                $postType = $def['post_type'] ?? '';
                $args = $def['args'] ?? [];

                if (!is_string($postType) || $postType === '') {
                    continue;
                }

                if (!preg_match('/^[a-z0-9_]{1,20}$/', $postType)) {
                    continue;
                }

                if (!is_array($args)) {
                    continue;
                }

                $safeArgs = array_intersect_key($args, array_flip($allowedArgKeys));

                if (isset($safeArgs['labels']) && !is_array($safeArgs['labels'])) {
                    unset($safeArgs['labels']);
                }

                if (isset($safeArgs['supports']) && !is_array($safeArgs['supports'])) {
                    unset($safeArgs['supports']);
                }

                register_post_type($postType, $safeArgs);

                // --- NEW: collect ACF field groups declared by the CPT file ---
                $acfGroups = $def['acf_field_groups'] ?? [];
                if (is_array($acfGroups) && !empty($acfGroups)) {
                    foreach ($acfGroups as $group) {
                        if (is_array($group)) {
                            $this->cptAcfFieldGroups[] = $group;
                        }
                    }
                }
            }
        }

        /**
         * Register any CPT-provided ACF field groups.
         */
        private function registerCptAcfFieldGroups(): void
        {
            if (empty($this->cptAcfFieldGroups)) {
                return;
            }

            if (!function_exists('acf_add_local_field_group')) {
                return;
            }

            foreach ($this->cptAcfFieldGroups as $group) {
                if (is_array($group)) {
                    acf_add_local_field_group($group);
                }
            }
        }

        private function resolveTopMenu()
        {
            $settings = get_fields('options-theme-settings');
            if (!is_array($settings)) {
                return Timber::get_menu('contact_menu');
            }

            $routing = $settings['top_menu_routing'] ?? [];
            if (!is_array($routing)) {
                return Timber::get_menu('contact_menu');
            }

            $routes = $routing['routes'] ?? [];
            $hidden = $routing['hidden_paths'] ?? [];

            $path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
            $path = '/' . ltrim($path, '/');
            if ($path !== '/' && str_ends_with($path, '/')) {
                $path = rtrim($path, '/');
            }

            // Exact hide overrides
            if (is_array($hidden)) {
                foreach ($hidden as $row) {
                    $hidePath = isset($row['path']) && is_string($row['path']) ? trim($row['path']) : '';
                    if ($hidePath === '') {
                        continue;
                    }
                    $hidePath = '/' . ltrim($hidePath, '/');
                    if ($hidePath !== '/' && str_ends_with($hidePath, '/')) {
                        $hidePath = rtrim($hidePath, '/');
                    }

                    if ($path === $hidePath) {
                        return null;
                    }
                }
            }

            $best = null;
            $bestLen = -1;

            if (is_array($routes)) {
                foreach ($routes as $row) {
                    if (!is_array($row)) {
                        continue;
                    }

                    $rulePath = isset($row['path']) && is_string($row['path']) ? trim($row['path']) : '';
                    if ($rulePath === '') {
                        continue;
                    }

                    $rulePath = '/' . ltrim($rulePath, '/');
                    if ($rulePath !== '/' && str_ends_with($rulePath, '/')) {
                        $rulePath = rtrim($rulePath, '/');
                    }

                    $matchType = isset($row['match_type']) && is_string($row['match_type']) ? $row['match_type'] : 'prefix';

                    $matched = false;
                    if ($matchType === 'exact') {
                        $matched = ($path === $rulePath);
                    } else {
                        // prefix match: /development matches /development and /development/anything
                        $matched = ($path === $rulePath) || (str_starts_with($path, $rulePath . '/'));
                    }

                    if ($matched) {
                        $len = strlen($rulePath);
                        if ($len > $bestLen) {
                            $best = $row;
                            $bestLen = $len;
                        }
                    }
                }
            }

            if (!empty($best['hide_menu'])) {
                return null;
            }

            $menuId = $best['menu'] ?? null;
            if (empty($menuId)) {
                return null; // explicit "no menu"
            }

            return Timber::get_menu((int) $menuId);
        }

        private function resolveHeaderLogo(): ?int
        {
            $defaultId = (int) get_theme_mod('custom_logo');
            $defaultLogo = $defaultId > 0 ? $defaultId : null;

            $settings = get_fields('options-theme-settings');
            if (!is_array($settings)) {
                return $defaultLogo;
            }

            $routing = $settings['header_logo_routing'] ?? [];
            if (!is_array($routing)) {
                return $defaultLogo;
            }

            $routes = $routing['routes'] ?? [];
            $hidden = $routing['hidden_paths'] ?? [];

            $path = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
            $path = '/' . ltrim($path, '/');
            if ($path !== '/' && str_ends_with($path, '/')) {
                $path = rtrim($path, '/');
            }

            // Exact hide overrides
            if (is_array($hidden)) {
                foreach ($hidden as $row) {
                    $hidePath = isset($row['path']) && is_string($row['path']) ? trim($row['path']) : '';
                    if ($hidePath === '') {
                        continue;
                    }

                    $hidePath = '/' . ltrim($hidePath, '/');
                    if ($hidePath !== '/' && str_ends_with($hidePath, '/')) {
                        $hidePath = rtrim($hidePath, '/');
                    }

                    if ($path === $hidePath) {
                        return null;
                    }
                }
            }

            $best = null;
            $bestLen = -1;

            if (is_array($routes)) {
                foreach ($routes as $row) {
                    if (!is_array($row)) {
                        continue;
                    }

                    $rulePath = isset($row['path']) && is_string($row['path']) ? trim($row['path']) : '';
                    if ($rulePath === '') {
                        continue;
                    }

                    $rulePath = '/' . ltrim($rulePath, '/');
                    if ($rulePath !== '/' && str_ends_with($rulePath, '/')) {
                        $rulePath = rtrim($rulePath, '/');
                    }

                    $matchType = isset($row['match_type']) && is_string($row['match_type']) ? $row['match_type'] : 'prefix';

                    $matched = false;
                    if ($matchType === 'exact') {
                        $matched = ($path === $rulePath);
                    } else {
                        $matched = ($path === $rulePath) || (str_starts_with($path, $rulePath . '/'));
                    }

                    if ($matched) {
                        $len = strlen($rulePath);
                        if ($len > $bestLen) {
                            $best = $row;
                            $bestLen = $len;
                        }
                    }
                }
            }

            if (!$best) {
                return $defaultLogo;
            }

            if (!empty($best['hide_logo'])) {
                return null;
            }

            $overrideId = isset($best['logo']) ? (int) $best['logo'] : 0;
            if ($overrideId > 0) {
                return $overrideId;
            }

            // If route matched but no override image was selected, keep default logo
            return $defaultLogo;
        }

        /**
         * Does the active stylesheet's build manifest declare the given entry?
         */
        private function childManifestHas(string $manifestHandle): bool
        {
            $childManifest = get_stylesheet_directory() . '/dist/manifest.json';

            if (!is_readable($childManifest)) {
                return false;
            }

            $json = file_get_contents($childManifest);
            $data = is_string($json) ? json_decode($json, true) : null;

            return is_array($data) && array_key_exists($manifestHandle, $data);
        }

        private function registerAsset(string $manifestHandle, array $dependencies = [], ?string $wpHandle = null): void
        {
            $manifestPath = $this->childManifestHas($manifestHandle)
                ? get_stylesheet_directory() . '/dist/manifest.json'
                : get_template_directory() . '/dist/manifest.json';

            Asset_Loader\enqueue_asset(
                $manifestPath,
                $manifestHandle,
                [
                    'handle' => $wpHandle ?? $manifestHandle,
                    'dependencies' => $dependencies,
                ],
            );
        }

        /**
         * Enriches any repeater named `images` anywhere in block fields, recursively.
         *
         * Expected repeater row shapes (common patterns):
         * - [ 'image' => <attachment_id>, ... ]
         * - [ 'id'    => <attachment_id>, ... ]
         *
         * Adds:
         * - row['image']      => responsive array from Image::getImageById() (only for non-SVG)
         * - row['image_mime'] => mime type
         * - row['image_url']  => attachment URL
         * - row['image_alt']  => alt text
         */
        private function prepareImagesRepeatersInBlocks(array $context): array
        {
            if (empty($context['fields']) || !is_array($context['fields'])) {
                return $context;
            }

            $context['fields'] = $this->enrichImagesRepeaters($context['fields']);

            return $context;
        }

        private function enrichImagesRepeaters(array $data): array
        {
            foreach ($data as $key => $value) {
                // Repeater named "images"
                if ($key === 'images' && is_array($value)) {
                    $data[$key] = $this->enrichImagesRepeaterRows($value);
                    continue;
                }

                // Recurse through nested groups/repeaters/flexible content arrays
                if (is_array($value)) {
                    $data[$key] = $this->enrichImagesRepeaters($value);
                }
            }

            return $data;
        }

        private function enrichImagesRepeaterRows(array $rows): array
        {
            foreach ($rows as $i => $row) {
                if (!is_array($row)) {
                    continue;
                }

                // Try common keys that might store the attachment ID
                $attachmentId = $row['image'] ?? $row['id'] ?? null;

                if (!is_numeric($attachmentId) || (int) $attachmentId <= 0) {
                    // Still recurse in case the row contains nested `images` repeaters
                    $rows[$i] = $this->enrichImagesRepeaters($row);
                    continue;
                }

                $attachmentId = (int) $attachmentId;

                $mime = get_post_mime_type($attachmentId) ?: '';
                $alt = get_post_meta($attachmentId, '_wp_attachment_image_alt', true) ?: '';
                $url = wp_get_attachment_url($attachmentId) ?: '';

                $row['image_mime'] = $mime;
                $row['image_alt'] = $alt;
                $row['image_url'] = $url;

                // For raster images, provide responsive sources via helper.
                // For SVG, consumers can use image_url + image_alt directly.
                if ($mime !== 'image/svg+xml') {
                    $row['image'] = $attachmentId;
                }

                // Recurse inside row too (nested content)
                $rows[$i] = $this->enrichImagesRepeaters($row);
            }

            return $rows;
        }

        private function hideCoreGutenbergBlocks($allowed_block_types, $block_editor_context)
        {
            // Allow only ACF blocks (all of them).
            $allowed = [];

            $acf_blocks = WP_Block_Type_Registry::get_instance()->get_all_registered();
            foreach ($acf_blocks as $name => $block_type) {
                // ACF blocks are registered as "acf/..."
                if (str_starts_with($name, 'acf/')) {
                    $allowed[] = $name;
                }
            }

            return $allowed;
        }

        /**
         * Populate a select field with WP nav menus (value = term_id).
         */
        private function acfPopulateNavMenuTermIdChoices(array $field): array
        {
            $field['choices'] = [];

            if (!taxonomy_exists('nav_menu')) {
                return $field;
            }

            $menus = wp_get_nav_menus();
            if (is_array($menus)) {
                foreach ($menus as $menu) {
                    if (!isset($menu->term_id, $menu->name)) {
                        continue;
                    }
                    $field['choices'][(string) $menu->term_id] = (string) $menu->name;
                }
            }

            return $field;
        }
    }
}
