<?php
/**
 * Design-system write handlers with dry-run previews.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Handlers;

use LolaCoreForBricks\BricksEnv;
use LolaCoreForBricks\Gateways\ThemeStyleWriteGateway;
use LolaCoreForBricks\Gateways\VariableWriteGateway;
use LolaCoreForBricks\Index\IndexBuilder;
use LolaCoreForBricks\Index\IndexReader;
use LolaCoreForBricks\Index\IndexRepository;
use LolaCoreForBricks\Support\BricksCodeGuard;
use LolaCoreForBricks\Support\BricksMetaKeys;
use LolaCoreForBricks\Support\CoveragePayload;
use LolaCoreForBricks\Support\CssRegenerator;
use LolaCoreForBricks\Support\ImpactPayload;
use LolaCoreForBricks\Support\SystemVocabulary;

/**
 * Executes B5 variable, palette, and theme style write abilities.
 */
class DesignSystemHandler
{
    public function __construct(
        private readonly ?VariableWriteGateway $variableGateway = null,
        private readonly ?ThemeStyleWriteGateway $themeStyleGateway = null,
        private readonly ?SystemVocabulary $vocabulary = null,
        private readonly ?CssRegenerator $cssRegenerator = null,
        private readonly ?IndexReader $indexReader = null
    ) {
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function setGlobalVariable(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $items = $this->normalizeVariableItems($params);
        $notes = ($this->vocabulary ?? new SystemVocabulary())->notesBeforeVariableSet($items);
        $batch = $this->buildBatchPreview($items);

        $payload = [
            'batch'            => $batch,
            'vocabulary_notes' => $notes,
            'coverage'         => CoveragePayload::forResponse(),
        ];

        if ($dryRun) {
            return $this->preview('Variable batch preview is ready.', $payload);
        }

        $result = ($this->variableGateway ?? new VariableWriteGateway())->setGlobalVariables($params);
        if (($result['success'] ?? false) !== true) {
            return $result;
        }

        $this->afterGlobalOptionWrite(BricksMetaKeys::OPTION_GLOBAL_VARIABLES);

        return $this->wrapExecuted($result, $payload);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function deleteGlobalVariable(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $ref = trim((string) ($params['variable'] ?? $params['name'] ?? ''));
        if ($ref === '') {
            return $this->failure('Tell me which variable to remove.', 'missing_variable');
        }

        $payload = [
            'impact'   => ImpactPayload::forVariable($ref, $this->indexReader),
            'coverage' => CoveragePayload::forResponse(),
        ];

        if ($dryRun) {
            return $this->preview('Delete preview is ready.', $payload);
        }

        $result = ($this->variableGateway ?? new VariableWriteGateway())->deleteGlobalVariable($params);
        if (($result['success'] ?? false) !== true) {
            return $result;
        }

        $this->afterGlobalOptionWrite(BricksMetaKeys::OPTION_GLOBAL_VARIABLES);

        return $this->wrapExecuted($result, $payload);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function updateColorPalette(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $payload = [
            'coverage' => CoveragePayload::forResponse(),
        ];

        if ($dryRun) {
            return $this->preview('Color palette preview is ready.', $payload);
        }

        $result = ($this->variableGateway ?? new VariableWriteGateway())->updateColorPalette($params);
        if (($result['success'] ?? false) !== true) {
            return $result;
        }

        $this->afterGlobalOptionWrite('bricks_color_palette');

        return $this->wrapExecuted($result, $payload);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function updateThemeStyle(array $params, bool $dryRun = false): array
    {
        $guard = $this->environmentGuard($params);
        if ($guard !== null) {
            return $guard;
        }

        $payload = [
            'coverage' => CoveragePayload::forResponse(),
        ];

        if ($dryRun) {
            return $this->preview('Theme style preview is ready.', $payload);
        }

        $result = ($this->themeStyleGateway ?? new ThemeStyleWriteGateway())->updateThemeStyle($params);
        if (($result['success'] ?? false) !== true) {
            return $result;
        }

        $this->afterGlobalOptionWrite(BricksMetaKeys::OPTION_THEME_STYLES);

        return $this->wrapExecuted($result, $payload);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<int, array<string, mixed>>
     */
    private function normalizeVariableItems(array $params): array
    {
        if (isset($params['variables']) && is_array($params['variables'])) {
            return array_values(array_filter($params['variables'], 'is_array'));
        }

        $name = trim((string) ($params['name'] ?? ''));
        if ($name === '') {
            return [];
        }

        return [[
            'name'  => $name,
            'value' => $params['value'] ?? '',
        ]];
    }

    /**
     * @param array<int, array<string, mixed>> $items
     * @return array{create: array<int, array<string, string>>, update: array<int, array<string, string>>}
     */
    private function buildBatchPreview(array $items): array
    {
        $existing = [];
        $raw      = get_option(BricksMetaKeys::OPTION_GLOBAL_VARIABLES, []);
        if (is_array($raw)) {
            foreach ($raw as $variable) {
                if (is_array($variable) && !empty($variable['name'])) {
                    $existing[strtolower((string) $variable['name'])] = true;
                }
            }
        }

        $create = [];
        $update = [];

        foreach ($items as $item) {
            $name = trim((string) ($item['name'] ?? ''));
            if ($name === '') {
                continue;
            }

            $entry = [
                'name'  => $name,
                'value' => (string) ($item['value'] ?? ''),
            ];

            if (isset($existing[strtolower($name)])) {
                $update[] = $entry;
                continue;
            }

            $create[] = $entry;
        }

        return [
            'create' => $create,
            'update' => $update,
        ];
    }

    private function afterGlobalOptionWrite(string $option): void
    {
        (new IndexBuilder())->indexGlobalScopes(new IndexRepository());
        ($this->cssRegenerator ?? new CssRegenerator())->afterOptionWrite($option);
    }

    /**
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function preview(string $message, array $payload): array
    {
        return array_merge(
            [
                'success' => true,
                'message' => $message,
                'dry_run' => true,
            ],
            $payload
        );
    }

    /**
     * @param array<string, mixed> $result
     * @param array<string, mixed> $payload
     * @return array<string, mixed>
     */
    private function wrapExecuted(array $result, array $payload): array
    {
        return array_merge($result, $payload);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>|null
     */
    private function environmentGuard(array $params = []): ?array
    {
        if ($rejection = BricksCodeGuard::rejectionResponse($params)) {
            return $rejection;
        }

        if (!BricksEnv::userCanReadDesignSystem()) {
            return $this->failure(
                'I need administrator access to change Bricks design tokens.',
                'forbidden'
            );
        }

        if (!BricksEnv::isAvailable() || !BricksEnv::meetsFloor()) {
            return $this->failure(
                'Bricks is not available on this site yet. Activate Bricks '
                . BricksEnv::FLOOR_VERSION . ' or newer, then try again.',
                'bricks_unavailable'
            );
        }

        return null;
    }

    /**
     * @return array{success: false, message: string, error: string}
     */
    private function failure(string $message, string $error): array
    {
        return [
            'success' => false,
            'message' => $message,
            'error'   => $error,
        ];
    }
}
