<?php

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

if (is_admin()) {
    return;
}

if (defined('REST_REQUEST') && REST_REQUEST) {
    $route = $GLOBALS['wp']->query_vars['rest_route'] ?? '';

    if (strpos($route, '/wp/v2/uicore-tb') !== false) {
        return;
    }
}

if (!function_exists('uicore_filters_terms_expand_descendants_only')) {

    // Returns ONLY descendants (children/grandchildren/...) of root IDs (roots excluded).
    function uicore_filters_terms_expand_descendants_only($taxonomy, $rootIds): array
    {
        $seen  = [];
        $queue = [];

        foreach ((array) $rootIds as $id) {
            $id = (int) $id;
            if ($id > 0) {
                $queue[] = $id;
            }
        }

        while (!empty($queue)) {
            $parentId = (int) array_shift($queue);

            $children = get_terms([
                'taxonomy'   => $taxonomy,
                'parent'     => $parentId,
                'number'     => 0,
                'fields'     => 'ids',
                'hide_empty' => false,
            ]);

            if (is_wp_error($children) || empty($children)) {
                continue;
            }

            foreach ($children as $cid) {
                $cid = (int) $cid;
                if ($cid > 0 && !isset($seen[$cid])) {
                    $seen[$cid] = true;
                    $queue[]    = $cid;
                }
            }
        }

        return array_map('intval', array_keys($seen));
    }
}

if (!function_exists('uicore_filters_terms_build_terms_args')) {

    function uicore_filters_terms_build_terms_args($entityFilter): array
    {
        $entityFilter = is_array($entityFilter) ? $entityFilter : [];

        $type = $entityFilter['type'] ?? '';
        $tax  = $entityFilter['field'] ?? '';

        if ($type !== 'postTerms' || $tax === '') {
            return [
                'args' => [],
                'taxonomy' => '',
                'includeAll' => true,
                'isDescendantsMode' => false,
                'allowChilds' => false,
            ];
        }

        $order   = $entityFilter['order'] ?? 'asc';
        $orderBy = $entityFilter['orderBy'] ?? 'name';
        $limit = (int) ($entityFilter['limit'] ?? 100);
        if ($limit <= 0) {
            $limit = 100;
        }

        $filters = $entityFilter['filters'] ?? [];
        $filters = is_array($filters) ? $filters : [];
        $hideEmptyEnabled = (bool) ($entityFilter['hideEmpty'] ?? true);

        $has = static function ($inc) use ($filters): bool {
            foreach ($filters as $f) {
                if (is_array($f) && (($f['include'] ?? '') === $inc)) {
                    return true;
                }
            }
            return false;
        };

        $find = static function ($inc) use ($filters): array {
            foreach ($filters as $f) {
                if (is_array($f) && (($f['include'] ?? '') === $inc)) {
                    return $f;
                }
            }
            return [];
        };

        $ids = static function ($items): array {
            $out = [];
            foreach ((array) $items as $v) {
                $v = (int) $v;
                if ($v > 0) {
                    $out[] = $v;
                }
            }
            return array_values(array_unique($out));
        };

        $in       = $find('IN');
        $notIn    = $find('NOT_IN');
        $childsOf = $find('IN_CHILDS_OF');

        $includeIds = $ids($in['items'] ?? []);
        $excludeIds = $ids($notIn['items'] ?? []);

        // Scope modifiers
        $allowChilds = $has('IN_CHILDS'); // removes parent=0 restriction even without IN items
        $isDescendantsMode = false;

        // IN_CHILDS_OF: items are parents => only descendants (parents excluded)
        $roots = $ids($childsOf['items'] ?? []);
        if (!empty($roots)) {
            $allowChilds = true;
            $isDescendantsMode = true;

            $desc = uicore_filters_terms_expand_descendants_only($tax, $roots);

            // if descendants empty => prevent showing everything
            $includeIds = !empty($desc)
                ? array_merge($includeIds, $desc)
                : (!empty($includeIds) ? $includeIds : [0]);
        }

        // IN_CHILDS + IN: expand included terms to include descendants (same as JS)
        if ($allowChilds && !empty($includeIds) && !$isDescendantsMode) {
            $desc = uicore_filters_terms_expand_descendants_only($tax, $includeIds);

            // descendants_only excludes the roots, so add both roots + descendants
            if (!empty($desc)) {
                $includeIds = array_merge($includeIds, $desc);
            }
        }

        $includeIds = array_values(array_unique(array_map('intval', $includeIds)));
        $excludeIds = array_values(array_unique(array_map('intval', $excludeIds)));

        $args = [
            'taxonomy'   => $tax,
            'order'      => $order ?: 'asc',
            'orderby'    => $orderBy ?: 'name',
            'number'     => $limit,
            'hide_empty' => $hideEmptyEnabled || $has('NOT_IN_EMPTY'),
        ];

        // Default: top-level only unless IN or IN_CHILDS or IN_CHILDS_OF is present
        $hasExplicitInclude = !empty($includeIds);
        if (!$allowChilds && !$hasExplicitInclude) {
            $args['parent'] = 0;
        }

        // IN / NOT_IN are exact IDs only (no auto children), ordering should match include list
        if (!empty($includeIds)) {
            $args['include'] = $includeIds;

            // When include is used, orderby include
            $args['orderby'] = 'include';

            // parent conflicts with include (WP ignores parent or can constrain unexpectedly)
            unset($args['parent']);
        }

        if (!empty($excludeIds)) {
            $args['exclude'] = $excludeIds;
        }

        // NOT_IN_FIRST_LEVEL: exclude all parent=0 terms
        if ($has('NOT_IN_FIRST_LEVEL')) {
            $top = get_terms([
                'taxonomy'   => $tax,
                'parent'     => 0,
                'number'     => 0,
                'fields'     => 'ids',
                'hide_empty' => $args['hide_empty'],
            ]);

            if (!is_wp_error($top) && !empty($top)) {
                $top = array_values(array_unique(array_map('intval', (array) $top)));
                $args['exclude'] = !empty($args['exclude'])
                    ? array_values(array_unique(array_merge((array) $args['exclude'], $top)))
                    : $top;
            }
        }

        return [
            'args' => $args,
            'taxonomy' => $tax,
            'isDescendantsMode' => $isDescendantsMode,
            'allowChilds' => $allowChilds,
        ];
    }
}

if (!function_exists('uicore_filters_terms_get_item_template')) {

    function uicore_filters_terms_get_item_template($block): ?array
    {
        $parsed   = is_array($block->parsed_block) ? $block->parsed_block : [];
        $children = $parsed['innerBlocks'] ?? [];

        foreach ($children as $child) {
            if (is_array($child) && ($child['blockName'] ?? '') === 'uicore/filters-terms-item') {
                return $child;
            }
        }

        return null;
    }
}
if (!function_exists('uicore_filters_terms_inject_term_text')) {
    function uicore_filters_terms_inject_term_text($html, $name, $count, $isAll = false, $withCount = true): string
    {
        // Always inject the name if the span exists
        $html = preg_replace(
            '/(<span\b[^>]*\bwp-block-uicore-filters-term-name\b[^>]*>)[\s\S]*?(<\/span>)/i',
            '$1' . esc_html($name) . '$2',
            $html
        );

        // If template doesn't even have the count span, do nothing
        if (stripos($html, 'wp-block-uicore-filters-term-count') === false) {
            return $html;
        }

        // "All" or withCount disabled => remove the whole count span
        if ($isAll || !$withCount) {
            return preg_replace(
                '/\s*<span\b[^>]*\bwp-block-uicore-filters-term-count\b[^>]*>[\s\S]*?<\/span>\s*/i',
                '',
                $html
            );
        }

        // Normal term => replace count content
        return preg_replace(
            '/(<span\b[^>]*\bwp-block-uicore-filters-term-count\b[^>]*>)[\s\S]*?(<\/span>)/i',
            '$1 ' . esc_html((string) $count) . '$2',
            $html
        );
    }
}

if (!function_exists('uicore_filters_terms_render_item')) {

    function uicore_filters_terms_render_item($template, $href, $isActive, $name, $count, $isAll = false, $withCount = true): string
    {
        $html = render_block($template);
        if ($html === '') {
            return '';
        }

        $html = uicore_filters_terms_inject_term_text($html, $name, $count, $isAll, $withCount);

        if (class_exists('WP_HTML_Tag_Processor')) {
            $tp = new WP_HTML_Tag_Processor($html);

            if ($tp->next_tag('a')) {
                $tp->set_attribute('href', esc_url($href));

                $cls = trim((string) ($tp->get_attribute('class') ?? ''));
                $cls = trim(preg_replace('/\buicore-is-active\b/', '', $cls));

                if ($isActive) {
                    $cls = trim($cls . ' uicore-is-active');
                }

                if ($cls !== '') {
                    $tp->set_attribute('class', $cls);
                }

                $html = $tp->get_updated_html();
            }

            // Mirror active state to item sub-elements inside the template.
            $tp = new WP_HTML_Tag_Processor($html);
            while ($tp->next_tag()) {
                $cls = trim((string) ($tp->get_attribute('class') ?? ''));
                if (
                    $cls === '' ||
                    !preg_match('/\binnerblocks\b|\bwp-block-uicore-[^\s"]+/i', $cls)
                ) {
                    continue;
                }

                $cls = trim(preg_replace('/\buicore-is-active\b/', '', $cls));
                if ($isActive) {
                    $cls = trim($cls . ' uicore-is-active');
                }

                if ($cls !== '') {
                    $tp->set_attribute('class', $cls);
                }
            }

            $html = $tp->get_updated_html();
        }

        return $html;
    }
}

if (!function_exists('uicore_filters_terms_get_contextual_counts')) {

    /**
     * Recalculate term counts for a taxonomy against active URL taxonomy filters.
     * Uses AND between taxonomies and OR within a taxonomy.
     *
     * @param string $taxonomy
     * @param array  $activeTaxMap      ['taxonomy_slug' => [term_ids]]
     * @param array  $excludeTaxonomies Taxonomies to exclude from tax_query.
     * @return array<int,int> term_id => count
     */
    function uicore_filters_terms_get_contextual_counts(string $taxonomy, array $activeTaxMap, array $excludeTaxonomies = []): array
    {
        $taxQuery = ['relation' => 'AND'];
        $hasTaxClauses = false;

        foreach ($activeTaxMap as $taxSlug => $termIds) {
            $taxSlug = sanitize_key((string) $taxSlug);
            if ($taxSlug === '' || in_array($taxSlug, $excludeTaxonomies, true)) {
                continue;
            }

            $termIds = array_values(array_unique(array_filter(array_map('absint', (array) $termIds))));
            if (empty($termIds)) {
                continue;
            }

            $taxQuery[] = [
                'taxonomy' => $taxSlug,
                'field'    => 'term_id',
                'terms'    => $termIds,
                'operator' => 'IN',
            ];
            $hasTaxClauses = true;
        }

        $taxObject = get_taxonomy($taxonomy);
        $postTypes = is_object($taxObject) && !empty($taxObject->object_type)
            ? array_values(array_filter((array) $taxObject->object_type, 'post_type_exists'))
            : [];

        $postArgs = [
            'post_type'              => !empty($postTypes) ? $postTypes : 'any',
            'post_status'            => 'publish',
            'fields'                 => 'ids',
            'posts_per_page'         => -1,
            'no_found_rows'          => true,
            'cache_results'          => false,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'suppress_filters'       => false,
        ];

        if ($hasTaxClauses) {
            $postArgs['tax_query'] = $taxQuery;
        }

        $postIds = get_posts($postArgs);
        if (empty($postIds)) {
            return [];
        }

        $termObjects = wp_get_object_terms($postIds, $taxonomy, [
            'fields'     => 'all_with_object_id',
            'hide_empty' => false,
        ]);

        if (is_wp_error($termObjects) || empty($termObjects)) {
            return [];
        }

        $perTermObjects = [];
        foreach ($termObjects as $termObject) {
            $termId = (int) ($termObject->term_id ?? 0);
            $objectId = (int) ($termObject->object_id ?? 0);

            if ($termId <= 0 || $objectId <= 0) {
                continue;
            }

            if (!isset($perTermObjects[$termId])) {
                $perTermObjects[$termId] = [];
            }
            $perTermObjects[$termId][$objectId] = true;
        }

        $counts = [];
        foreach ($perTermObjects as $termId => $objectMap) {
            $counts[(int) $termId] = count($objectMap);
        }

        return $counts;
    }
}

/** ---------------- main ---------------- */

$displayType = $attributes['displayType'] ?? '';
$blockId     = $attributes['blockId'] ?? '';
$multiSelect = !empty($attributes['multiSelect']);
$placeholderText = isset($attributes['placeholder']) && is_string($attributes['placeholder'])
    ? trim($attributes['placeholder'])
    : '';
$placeholderText = ($placeholderText !== '') ? $placeholderText : __('Select item', 'uicore-blocks');

$entityFilter = $block->context['entityFilter'] ?? [];
$built        = uicore_filters_terms_build_terms_args($entityFilter);
$hideEmpty    = (bool) ($entityFilter['hideEmpty'] ?? true);

$args              = $built['args'];
$taxonomy          = $built['taxonomy'];
$isSingleSelect = ($displayType === 'select' && !$multiSelect);
$canIncludeAll = ($displayType !== 'select' || $isSingleSelect);
$includeAll = $isSingleSelect || ($canIncludeAll && (
    array_key_exists('includeAll', $attributes)
    ? (bool) $attributes['includeAll']
    : (!isset($entityFilter['includeAll']) || $entityFilter['includeAll'] !== false)
));
$includeAllText = isset($attributes['includeAllText']) && is_string($attributes['includeAllText'])
    ? trim($attributes['includeAllText'])
    : '';
$includeAllText = ($includeAllText !== '') ? $includeAllText : __('All', 'uicore-blocks');
$isDescendantsMode = !empty($built['isDescendantsMode']);

if (empty($args) || $taxonomy === '') {
    echo '';
    return;
}

$terms = get_terms($args);
if (is_wp_error($terms)) {
    echo '';
    return;
}

if ($hideEmpty && empty($terms)) {
    echo '';
    return;
}

// Scope links to query loop only when queryId exists
$queryId = isset($block->context['queryId']) ? (int) $block->context['queryId'] : 0;

$current_qid   = isset($_GET['ui_qid']) ? (int) $_GET['ui_qid'] : 0;
$is_for_this_instance = ($current_qid === 0 || $queryId === 0 || $current_qid === $queryId);
$parse_term_ids = static function ($value): array {
    return array_values(array_unique(array_filter(array_map(
        'absint',
        array_map('trim', explode(',', (string) $value))
    ))));
};

$active_tax_map = [];
if (isset($_GET['ui_tax'])) {
    $raw_ui_tax = wp_unslash($_GET['ui_tax']);

    if (is_array($raw_ui_tax)) {
        foreach ($raw_ui_tax as $tax_slug => $raw_terms) {
            $tax_slug = sanitize_key((string) $tax_slug);
            if ($tax_slug === '') {
                continue;
            }

            $term_ids = $parse_term_ids($raw_terms);
            if (empty($term_ids)) {
                continue;
            }

            $active_tax_map[$tax_slug] = $term_ids;
        }
    }
}

$active_term_ids = $is_for_this_instance
    ? ($active_tax_map[$taxonomy] ?? [])
    : [];
$scoped_active_tax_map = $is_for_this_instance ? $active_tax_map : [];

if (!empty($scoped_active_tax_map)) {
    // Exclude current taxonomy for disjunctive facet counts (OR inside taxonomy, AND across taxonomies).
    $contextualCounts = uicore_filters_terms_get_contextual_counts(
        $taxonomy,
        $scoped_active_tax_map,
        [$taxonomy]
    );

    $contextualTerms = [];
    foreach ((array) $terms as $term) {
        $termId = (int) ($term->term_id ?? 0);
        $nextCount = (int) ($contextualCounts[$termId] ?? 0);

        if ($hideEmpty && $nextCount <= 0) {
            continue;
        }

        $term->count = $nextCount;
        $contextualTerms[] = $term;
    }

    $terms = $contextualTerms;
}

if ($hideEmpty && empty($terms)) {
    echo '';
    return;
}

$queryArgsToRemove = ['ui_tax', 'ui_qid', 'paged', 'query-page'];

foreach (array_keys((array) $_GET) as $queryKey) {
    if (preg_match('/^query-\d+-page$/', (string) $queryKey)) {
        $queryArgsToRemove[] = (string) $queryKey;
    }
}

$baseUrl = remove_query_arg(array_values(array_unique($queryArgsToRemove)));

$termsBlockClass = ($blockId !== '') ? 'uicore-block-' . sanitize_html_class($blockId) : '';
$wrapClass = trim(
    'wp-block-uicore-filters-terms' .
        ($displayType !== 'select' && $termsBlockClass !== '' ? ' ' . $termsBlockClass : '') .
        ' uicore-bl-content'
);

$template = uicore_filters_terms_get_item_template($block);
$termItemBlockId = '';
$termItemClass = '';
$termNameClass = '';
$termCountClass = '';
$termTemplateHtml = '';
$withCount = false;
if (is_array($template)) {
    $termTemplateHtml = render_block($template);
    $termItemBlockId = (string) (($template['attrs']['blockId'] ?? ''));
    if ($termItemBlockId !== '') {
        $termItemClass = 'uicore-block-' . sanitize_html_class($termItemBlockId);
    }

    $templateInnerBlocks = is_array($template['innerBlocks'] ?? null) ? $template['innerBlocks'] : [];
    foreach ($templateInnerBlocks as $templateInnerBlock) {
        if (!is_array($templateInnerBlock)) {
            continue;
        }

        $templateInnerName = (string) ($templateInnerBlock['blockName'] ?? '');
        if ($templateInnerName === 'uicore/filters-term-count') {
            $withCount = true;
        }

        $templateInnerId = (string) (($templateInnerBlock['attrs']['blockId'] ?? ''));
        if ($templateInnerId === '') {
            continue;
        }

        if ($templateInnerName === 'uicore/filters-term-name') {
            $termNameClass = 'uicore-block-' . sanitize_html_class($templateInnerId);
        }

        if ($templateInnerName === 'uicore/filters-term-count') {
            $termCountClass = 'uicore-block-' . sanitize_html_class($templateInnerId);
        }
    }
}

/** ---------------- select mode ---------------- */

if ($displayType === 'select') {
    $selectClass = trim($wrapClass . ' uicore-bl-choices');
    $hasActiveSelection = !empty($active_term_ids) || ($isSingleSelect && $includeAll);

    echo '<div class="' . esc_attr($wrapClass) . '" data-tax="' . esc_attr($taxonomy) . '" >';

    $select_attrs = ' class="choices" data-tax="' . esc_attr($taxonomy) . '" data-query-id="' . esc_attr((string) $queryId) . '"';
    if ($termsBlockClass !== '') {
        $select_attrs .= ' data-terms-block-class="' . esc_attr($termsBlockClass) . '"';
    }
    if ($termItemClass !== '') {
        $select_attrs .= ' data-term-item-class="' . esc_attr($termItemClass) . '"';
    }

    if ($multiSelect) {
        $select_attrs .= ' multiple="multiple" data-multi-select="1"';
    }

    if ($multiSelect) {
        $select_attrs .= ' data-placeholder-text="' . esc_attr($placeholderText) . '"';
    }
    $select_attrs .= ' data-has-active="' . ($hasActiveSelection ? '1' : '0') . '"';
    if ($termTemplateHtml !== '') {
        $select_attrs .= ' data-term-markup="' . esc_attr($termTemplateHtml) . '"';
    }

    echo '<select' . $select_attrs . '>';

    if ($includeAll) {
        $allSelected = ($isSingleSelect && empty($active_term_ids))
            ? ' selected="selected"'
            : '';
        echo '<option value="0"' . $allSelected .
            ' data-term-name="' . esc_attr($includeAllText) . '"' .
            ' data-term-count="" data-term-is-all="1">' .
            esc_html($includeAllText) . '</option>';
    }

    foreach ((array) $terms as $term) {
        $id = (int) $term->term_id;

        $name = $term->name ?? '';

        if ($withCount && !empty($term->count)) {
            $name = $name . ' (' . $term->count . ')';
        }

        $selected = in_array($id, $active_term_ids, true)
            ? ' selected="selected"'
            : '';

        echo '<option value="' . esc_attr((string) $id) . '"' . $selected .
            ' data-term-name="' . esc_attr((string) ($term->name ?? '')) . '"' .
            ' data-term-count="' . esc_attr((string) $term->count) . '"' .
            ' data-term-is-all="0">' .
            esc_html($name) . '</option>';
    }

    echo '</select>';
    if ($termTemplateHtml !== '') {
        echo '<template class="uicore-filters-term-template">' . $termTemplateHtml . '</template>';
    }
    echo '</div>';
    return;
}

/** ---------------- list mode ---------------- */

echo '<div class="' . esc_attr($wrapClass) . '"' .
    ' data-tax="' . esc_attr($taxonomy) . '"' .
    '>';

if (!$template) {
    echo '</div>';
    return;
}

// All (list mode): active when no specific term is selected for this taxonomy.
if ($includeAll) {
    $all_map = $active_tax_map;
    unset($all_map[$taxonomy]);
    $isAllActive = empty($active_term_ids);

    $allHref = $baseUrl;
    if (!empty($all_map)) {
        $all_map_url = [];
        foreach ($all_map as $tax_slug => $term_ids) {
            $tax_slug = sanitize_key((string) $tax_slug);
            $term_ids = array_values(array_unique(array_filter(array_map('absint', (array) $term_ids))));

            if ($tax_slug === '' || empty($term_ids)) {
                continue;
            }

            $all_map_url[$tax_slug] = implode(',', $term_ids);
        }

        if (!empty($all_map_url)) {
            $allHref = add_query_arg(['ui_tax' => $all_map_url], $allHref);
        }
    }

    if ($queryId > 0 && !empty($all_map)) {
        $allHref = add_query_arg(['ui_qid' => $queryId], $allHref);
    }

    echo uicore_filters_terms_render_item(
        $template,
        $allHref,
        $isAllActive,
        $includeAllText,
        0,
        true, // isAll
        $withCount
    );
}

foreach ((array) $terms as $term) {
    $id = (int) $term->term_id;
    $next_map = $active_tax_map;

    if ($multiSelect) {
        $next_term_ids = isset($next_map[$taxonomy]) ? (array) $next_map[$taxonomy] : [];

        if (in_array($id, $next_term_ids, true)) {
            $next_term_ids = array_values(array_filter(
                $next_term_ids,
                static fn($term_id) => (int) $term_id !== $id
            ));
        } else {
            $next_term_ids[] = $id;
            $next_term_ids = array_values(array_unique(array_map('intval', $next_term_ids)));
        }

        if (!empty($next_term_ids)) {
            $next_map[$taxonomy] = $next_term_ids;
        } else {
            unset($next_map[$taxonomy]);
        }
    } else {
        $next_map[$taxonomy] = [$id];
    }

    $href = $baseUrl;
    if (!empty($next_map)) {
        $next_map_url = [];
        foreach ($next_map as $tax_slug => $term_ids) {
            $tax_slug = sanitize_key((string) $tax_slug);
            $term_ids = array_values(array_unique(array_filter(array_map('absint', (array) $term_ids))));

            if ($tax_slug === '' || empty($term_ids)) {
                continue;
            }

            $next_map_url[$tax_slug] = implode(',', $term_ids);
        }

        if (!empty($next_map_url)) {
            $href = add_query_arg(['ui_tax' => $next_map_url], $href);

            if ($queryId > 0) {
                $href = add_query_arg(['ui_qid' => $queryId], $href);
            }
        }
    }

    $name = $term->name ?? '';
    if ($isDescendantsMode && !empty($term->parent)) {
        $name = '- ' . $name;
    }

    echo uicore_filters_terms_render_item(
        $template,
        $href,
        in_array($id, $active_term_ids, true),
        $name,
        $term->count,
        false,
        $withCount
    );
}

echo '</div>';
