<?php
/**
 * Detects overlapping Bricks theme style conditions.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Governance;

use LolaCoreForBricks\Support\BricksMetaKeys;
use LolaCoreForBricks\Support\PaginatedList;

/**
 * Finds theme style pairs that can apply to the same page at once.
 */
class ThemeStyleConflicts
{
    private const EXAMPLE_PAGE_LIMIT = 5;

    private const SCAN_PAGE_LIMIT = 200;

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function findConflicts(array $params = []): array
    {
        $limit  = (int) ($params['limit'] ?? 50);
        $offset = (int) ($params['offset'] ?? 0);

        $styles = $this->loadStylesWithConditions();
        $pairs  = [];

        $count = count($styles);
        for ($i = 0; $i < $count; ++$i) {
            for ($j = $i + 1; $j < $count; ++$j) {
                $conflict = $this->analyzePair($styles[$i], $styles[$j]);
                if ($conflict !== null) {
                    $pairs[] = $conflict;
                }
            }
        }

        $page = PaginatedList::slice($pairs, $limit, $offset);

        return [
            'conflicts' => $page['items'],
            'total'     => $page['total'],
            'truncated' => $page['truncated'],
            'limit'     => $page['limit'],
            'offset'    => $page['offset'],
        ];
    }

    /**
     * @param array<string, mixed> $left
     * @param array<string, mixed> $right
     * @return array<string, mixed>|null
     */
    private function analyzePair(array $left, array $right): ?array
    {
        if (!$this->conditionsCanOverlap(
            is_array($left['conditions'] ?? null) ? $left['conditions'] : [],
            is_array($right['conditions'] ?? null) ? $right['conditions'] : []
        )) {
            return null;
        }

        $examples = $this->findExamplePages(
            (string) $left['id'],
            is_array($left['conditions'] ?? null) ? $left['conditions'] : [],
            (string) $right['id'],
            is_array($right['conditions'] ?? null) ? $right['conditions'] : []
        );

        if ($examples === []) {
            return null;
        }

        return [
            'style_a'             => $this->summarizeStyle($left),
            'style_b'             => $this->summarizeStyle($right),
            'overlapping_rules'   => $this->describeOverlap(
                is_array($left['conditions'] ?? null) ? $left['conditions'] : [],
                is_array($right['conditions'] ?? null) ? $right['conditions'] : []
            ),
            'example_pages'       => $examples,
            'example_pages_total' => count($examples),
        ];
    }

    /**
     * @param array<int, mixed> $left
     * @param array<int, mixed> $right
     * @return array<int, string>
     */
    private function describeOverlap(array $left, array $right): array
    {
        $descriptions = [];

        if ($this->hasAnyCondition($left) || $this->hasAnyCondition($right)) {
            $descriptions[] = 'At least one style targets the entire website.';
        }

        $sharedPostTypes = array_values(array_intersect(
            $this->extractPostTypes($left),
            $this->extractPostTypes($right)
        ));
        if ($sharedPostTypes !== []) {
            $descriptions[] = 'Shared post types: ' . implode(', ', $sharedPostTypes);
        }

        $sharedIds = array_values(array_intersect(
            $this->extractIds($left),
            $this->extractIds($right)
        ));
        if ($sharedIds !== []) {
            $descriptions[] = 'Shared page/post IDs in conditions.';
        }

        if ($descriptions === []) {
            $descriptions[] = 'Condition scopes can both be true on the same page context.';
        }

        return $descriptions;
    }

    /**
     * @param array<int, mixed> $left
     * @param array<int, mixed> $right
     */
    private function conditionsCanOverlap(array $left, array $right): bool
    {
        if ($left === [] || $right === []) {
            return false;
        }

        if ($this->hasAnyCondition($left) || $this->hasAnyCondition($right)) {
            return true;
        }

        $leftPostTypes  = $this->extractPostTypes($left);
        $rightPostTypes = $this->extractPostTypes($right);
        if ($leftPostTypes !== [] && $rightPostTypes !== [] && array_intersect($leftPostTypes, $rightPostTypes) === []) {
            return false;
        }

        $leftIds  = $this->extractIds($left);
        $rightIds = $this->extractIds($right);
        if ($leftIds !== [] && $rightIds !== [] && array_intersect($leftIds, $rightIds) === []) {
            return false;
        }

        return true;
    }

    /**
     * @param array<int, mixed> $conditions
     * @return array<int, array{post_id: int, post_title: string}>
     */
    private function findExamplePages(string $leftId, array $leftConditions, string $rightId, array $rightConditions): array
    {
        if (!class_exists(\Bricks\Database::class)) {
            return [];
        }

        $examples = [];
        foreach ($this->candidatePostIds() as $postId) {
            if (!$this->styleMatchesPost($leftId, $leftConditions, $postId)) {
                continue;
            }

            if (!$this->styleMatchesPost($rightId, $rightConditions, $postId)) {
                continue;
            }

            $examples[] = [
                'post_id'    => $postId,
                'post_title' => (string) get_the_title($postId),
            ];

            if (count($examples) >= self::EXAMPLE_PAGE_LIMIT) {
                break;
            }
        }

        return $examples;
    }

    /**
     * @param array<int, mixed> $conditions
     */
    private function styleMatchesPost(string $styleId, array $conditions, int $postId): bool
    {
        $scores = \Bricks\Database::screen_conditions([], $styleId, $conditions, $postId, '');

        return is_array($scores) && $scores !== [];
    }

    /**
     * @return int[]
     */
    private function candidatePostIds(): array
    {
        global $wpdb;

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $rows = $wpdb->get_col(
            $wpdb->prepare(
                "SELECT DISTINCT post_id
                 FROM {$wpdb->postmeta}
                 WHERE meta_key = %s
                 AND meta_value <> ''
                 ORDER BY post_id ASC
                 LIMIT %d",
                BricksMetaKeys::PAGE_CONTENT,
                self::SCAN_PAGE_LIMIT
            )
        );

        if (!is_array($rows)) {
            return [];
        }

        return array_values(array_map('intval', $rows));
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function loadStylesWithConditions(): array
    {
        $raw = get_option(BricksMetaKeys::OPTION_THEME_STYLES, []);
        if (!is_array($raw)) {
            return [];
        }

        $styles = [];
        foreach ($raw as $styleId => $style) {
            if (!is_array($style)) {
                continue;
            }

            $conditions = $style['settings']['conditions']['conditions'] ?? $style['conditions'] ?? [];
            if (!is_array($conditions) || $conditions === []) {
                continue;
            }

            $styles[] = [
                'id'         => (string) $styleId,
                'name'       => (string) ($style['name'] ?? $styleId),
                'conditions' => $conditions,
            ];
        }

        return $styles;
    }

    /**
     * @param array<string, mixed> $style
     * @return array<string, string>
     */
    private function summarizeStyle(array $style): array
    {
        return [
            'id'   => (string) ($style['id'] ?? ''),
            'name' => (string) ($style['name'] ?? ''),
        ];
    }

    /**
     * @param array<int, mixed> $conditions
     */
    private function hasAnyCondition(array $conditions): bool
    {
        foreach ($conditions as $condition) {
            if (is_array($condition) && ($condition['main'] ?? '') === 'any') {
                return true;
            }
        }

        return false;
    }

    /**
     * @param array<int, mixed> $conditions
     * @return string[]
     */
    private function extractPostTypes(array $conditions): array
    {
        $types = [];
        foreach ($conditions as $condition) {
            if (!is_array($condition) || ($condition['main'] ?? '') !== 'postType') {
                continue;
            }

            foreach ((array) ($condition['postType'] ?? []) as $postType) {
                $types[] = (string) $postType;
            }
        }

        return array_values(array_unique($types));
    }

    /**
     * @param array<int, mixed> $conditions
     * @return int[]
     */
    private function extractIds(array $conditions): array
    {
        $ids = [];
        foreach ($conditions as $condition) {
            if (!is_array($condition) || ($condition['main'] ?? '') !== 'ids') {
                continue;
            }

            foreach ((array) ($condition['ids'] ?? []) as $id) {
                $ids[] = (int) $id;
            }
        }

        return array_values(array_unique($ids));
    }
}
