<?php
/**
 * Index-backed dead code queries for Bricks globals.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Governance;

use LolaCoreForBricks\Index\IndexReader;
use LolaCoreForBricks\Index\IndexRepository;
use LolaCoreForBricks\Support\BricksMetaKeys;
use LolaCoreForBricks\Support\PaginatedList;

/**
 * Finds unused classes, variables, and orphan components via the design index.
 */
class DeadCodeFinder
{
    public function __construct(
        private readonly ?IndexReader $reader = null,
        private readonly ?IndexRepository $repository = null
    ) {
    }

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

        $classes = $this->loadClasses(includeTrash: $includeTrash);
        $unused  = [];

        foreach ($classes as $class) {
            $classId = (string) ($class['id'] ?? '');
            if ($classId === '') {
                continue;
            }

            if ($this->usageCount(BricksMetaKeys::REF_CLASS, $classId) > 0) {
                continue;
            }

            $unused[] = [
                'id'       => $classId,
                'name'     => (string) ($class['name'] ?? $classId),
                'category' => (string) ($class['category'] ?? ''),
                'modified' => isset($class['modified']) ? (int) $class['modified'] : null,
                'in_trash' => (bool) ($class['in_trash'] ?? false),
            ];
        }

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

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

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

        $variables = get_option(BricksMetaKeys::OPTION_GLOBAL_VARIABLES, []);
        $unused    = [];

        if (is_array($variables)) {
            foreach ($variables as $variable) {
                if (!is_array($variable)) {
                    continue;
                }

                $name = (string) ($variable['name'] ?? $variable['id'] ?? '');
                if ($name === '') {
                    continue;
                }

                if ($this->usageCount(BricksMetaKeys::REF_VARIABLE, ltrim($name, '-')) > 0) {
                    continue;
                }

                $unused[] = [
                    'name'     => $name,
                    'value'    => (string) ($variable['value'] ?? ''),
                    'type'     => (string) ($variable['type'] ?? 'custom'),
                    'category' => (string) ($variable['category'] ?? ''),
                ];
            }
        }

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

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

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

        $components = get_option(BricksMetaKeys::OPTION_COMPONENTS, []);
        $orphans    = [];

        if (is_array($components)) {
            foreach ($components as $component) {
                if (!is_array($component) || empty($component['id'])) {
                    continue;
                }

                $id = (string) $component['id'];
                if ($this->usageCount(BricksMetaKeys::REF_COMPONENT, $id) > 0) {
                    continue;
                }

                $orphans[] = [
                    'id'   => $id,
                    'name' => (string) ($component['name'] ?? $component['label'] ?? $id),
                ];
            }
        }

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

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

    private function usageCount(string $refType, string $refId): int
    {
        return count(($this->reader ?? new IndexReader())->usageOf($refType, $refId));
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function loadClasses(bool $includeTrash): array
    {
        $items = [];

        foreach ($this->normalizeClassList(get_option(BricksMetaKeys::OPTION_GLOBAL_CLASSES, []), false) as $class) {
            $items[] = $class;
        }

        if ($includeTrash) {
            foreach ($this->normalizeClassList(get_option('bricks_global_classes_trash', []), true) as $class) {
                $items[] = $class;
            }
        }

        return $items;
    }

    /**
     * @param mixed $raw
     * @return array<int, array<string, mixed>>
     */
    private function normalizeClassList(mixed $raw, bool $inTrash): array
    {
        if (!is_array($raw)) {
            return [];
        }

        $items = [];
        foreach ($raw as $class) {
            if (!is_array($class) || empty($class['id'])) {
                continue;
            }

            $items[] = array_merge($class, ['in_trash' => $inTrash]);
        }

        return $items;
    }
}
