<?php
/**
 * Global class reads and writes from Bricks options.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Gateways;

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

/**
 * Reads and writes bricks_global_classes, categories, trash, and locked state.
 */
class ClassGateway
{
    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function getGlobalClasses(array $params): array
    {
        $category     = isset($params['category']) ? (string) $params['category'] : '';
        $includeTrash = (bool) ($params['include_trash'] ?? false);
        $limit        = (int) ($params['limit'] ?? 50);
        $offset       = (int) ($params['offset'] ?? 0);

        $classes    = $this->normalizeClasses(get_option(BricksMetaKeys::OPTION_GLOBAL_CLASSES, []));
        $categories = $this->categoryMap(get_option('bricks_global_classes_categories', []));
        $locked     = $this->lockedIds();
        $trash      = $includeTrash ? $this->normalizeClasses(get_option('bricks_global_classes_trash', [])) : [];

        $items = [];
        foreach ($classes as $class) {
            if ($category !== '' && ($class['category'] ?? '') !== $category) {
                continue;
            }

            $items[] = [
                'id'             => $class['id'],
                'name'           => $class['name'],
                'category'       => $class['category'] ?? '',
                'category_label' => $categories[$class['category'] ?? ''] ?? '',
                'settings_count' => $class['settings_count'],
                'locked'         => in_array($class['id'], $locked, true),
                'in_trash'       => false,
            ];
        }

        foreach ($trash as $class) {
            if ($category !== '' && ($class['category'] ?? '') !== $category) {
                continue;
            }

            $items[] = [
                'id'             => $class['id'],
                'name'           => $class['name'],
                'category'       => $class['category'] ?? '',
                'category_label' => $categories[$class['category'] ?? ''] ?? '',
                'settings_count' => $class['settings_count'],
                'locked'         => in_array($class['id'], $locked, true),
                'in_trash'       => true,
            ];
        }

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

        return [
            'classes'    => $page['items'],
            'categories' => $this->formatCategories($categories),
            'total'      => $page['total'],
            'truncated'  => $page['truncated'],
            'limit'      => $page['limit'],
            'offset'     => $page['offset'],
        ];
    }

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

        if ($classRef === '') {
            return [
                'error' => 'missing_class',
            ];
        }

        $resolved = $this->resolveClassRef($classRef);
        if ($resolved === null) {
            return [
                'error' => 'class_not_found',
                'class' => $classRef,
            ];
        }

        $grouped = (new IndexReader())->usageGroupedByPost(BricksMetaKeys::REF_CLASS, $resolved['id']);
        $page    = PaginatedList::slice($grouped, $limit, $offset);

        return [
            'class'        => $resolved,
            'pages'        => $page['items'],
            'total_pages'  => $page['total'],
            'truncated'    => $page['truncated'],
            'limit'        => $page['limit'],
            'offset'       => $page['offset'],
            'coverage'     => CoveragePayload::forResponse(),
        ];
    }

    /**
     * @return array{id: string, name: string}|null
     */
    public function resolveClassRef(string $ref): ?array
    {
        $classes = $this->normalizeClasses(get_option(BricksMetaKeys::OPTION_GLOBAL_CLASSES, []));

        foreach ($classes as $class) {
            if ($class['id'] === $ref || strcasecmp($class['name'], $ref) === 0) {
                return ['id' => $class['id'], 'name' => $class['name']];
            }
        }

        return null;
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function writeGateway(): ClassWriteGateway
    {
        return new ClassWriteGateway($this);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function createGlobalClass(array $params): array
    {
        return $this->writeGateway()->createGlobalClass($params);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function updateGlobalClassSettings(array $params): array
    {
        return $this->writeGateway()->updateGlobalClassSettings($params);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function renameGlobalClass(array $params): array
    {
        return $this->writeGateway()->renameGlobalClass($params);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function mergeGlobalClasses(array $params): array
    {
        return $this->writeGateway()->mergeGlobalClasses($params);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function trashGlobalClasses(array $params): array
    {
        return $this->writeGateway()->trashGlobalClasses($params);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function restoreGlobalClass(array $params): array
    {
        return $this->writeGateway()->restoreGlobalClass($params);
    }

    /**
     * @param mixed $raw
     * @return array<int, array{id: string, name: string, category: string, settings_count: int}>
     */
    private function normalizeClasses(mixed $raw): array
    {
        if (!is_array($raw)) {
            return [];
        }

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

            $settings = is_array($class['settings'] ?? null) ? $class['settings'] : [];

            $items[] = [
                'id'             => (string) $class['id'],
                'name'           => (string) ($class['name'] ?? $class['id']),
                'category'       => (string) ($class['category'] ?? ''),
                'settings_count' => count($settings),
            ];
        }

        return $items;
    }

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

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

            $map[(string) $category['id']] = (string) ($category['name'] ?? $category['id']);
        }

        return $map;
    }

    /**
     * @param array<string, string> $categories
     * @return array<int, array<string, string>>
     */
    private function formatCategories(array $categories): array
    {
        $items = [];
        foreach ($categories as $id => $name) {
            $items[] = ['id' => $id, 'name' => $name];
        }

        return $items;
    }

    /**
     * @return string[]
     */
    private function lockedIds(): array
    {
        $locked = get_option('bricks_global_classes_locked', []);
        if (!is_array($locked)) {
            return [];
        }

        return array_values(array_map('strval', $locked));
    }
}
