<?php
/**
 * Page settings, element conditions, interactions, and dynamic data.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Handlers;

use LolaCoreForBricks\BricksEnv;
use LolaCoreForBricks\Elements\ElementTree;
use LolaCoreForBricks\Gateways\ElementGateway;
use LolaCoreForBricks\Gateways\PageSettingsGateway;
use LolaCoreForBricks\Support\BricksCodeGuard;
use LolaCoreForBricks\Support\BricksMetaKeys;
use LolaCoreForBricks\Support\DynamicDataCatalog;
use LolaCoreForBricks\Support\ElementConditions;
use LolaCoreForBricks\Support\ElementInteractions;

/**
 * Executes B13 page-behavior abilities.
 */
class PageBehaviorHandler
{
    public function __construct(
        private readonly ?PageSettingsGateway $pages = null,
        private readonly ?ElementGateway $elements = null,
        private readonly ?DynamicDataCatalog $catalog = null
    ) {
    }

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

        $postId = absint($params['post_id'] ?? $params['postId'] ?? 0);
        $settings = is_array($params['settings'] ?? null) ? $params['settings'] : [];
        if ($settings === []) {
            return $this->failure('I need the page settings to change. Which ones?', 'missing_settings');
        }

        if ($rejection = BricksCodeGuard::rejectionResponse($settings)) {
            return $rejection;
        }

        return $this->pages()->write($postId, $settings, $dryRun);
    }

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

        $normalized = ElementConditions::normalize($params['conditions'] ?? null);
        if (($normalized['success'] ?? false) !== true) {
            return $normalized;
        }

        return $this->patchElementSetting(
            $params,
            '_conditions',
            $normalized['conditions'],
            $dryRun,
            'Condition preview is ready.',
            $normalized['conditions'] === []
                ? 'I cleared the display conditions. The element shows everywhere again.'
                : 'I set the display conditions. Refresh the front to see where it appears.'
        );
    }

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

        $normalized = ElementInteractions::normalize($params['interactions'] ?? null);
        if (($normalized['success'] ?? false) !== true) {
            return $normalized;
        }

        return $this->patchElementSetting(
            $params,
            '_interactions',
            $normalized['interactions'],
            $dryRun,
            'Interaction preview is ready.',
            $normalized['interactions'] === []
                ? 'I cleared the interactions on that element.'
                : 'I set the interactions. Refresh the front to try them.'
        );
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function listDynamicDataTags(array $params): array
    {
        unset($params);

        if (!BricksEnv::userCanReadDesignSystem()) {
            return $this->failure('I need administrator access to list Bricks dynamic data tags.', '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'
            );
        }

        $grouped = $this->catalog()->grouped();
        $flat    = $this->catalog()->list();

        return [
            'success'   => true,
            'message'   => $flat === []
                ? 'I did not find dynamic data tags yet. They appear once Bricks finishes loading its providers.'
                : 'Here are the dynamic data tags available on this site.',
            'providers' => $grouped,
            'tags'      => $flat,
            'total'     => count($flat),
        ];
    }

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

        $setting = (string) ($params['setting'] ?? $params['key'] ?? 'text');
        if ($setting === '' || $setting === '_conditions' || $setting === '_interactions') {
            return $this->failure('I need the setting to fill with dynamic data. Which one?', 'missing_setting');
        }

        $tag = $this->catalog()->wrap((string) ($params['tag'] ?? $params['dynamic_tag'] ?? ''));
        if ($tag === '') {
            return $this->failure('I need a dynamic data tag such as {post_title}.', 'missing_tag');
        }

        if (!$this->catalog()->exists($tag)) {
            return $this->failure(
                'I cannot use that tag. It is not in the list of dynamic data tags on this site.',
                'unknown_dynamic_tag'
            );
        }

        return $this->patchElementSetting(
            $params,
            $setting,
            $tag,
            $dryRun,
            'Dynamic content preview is ready.',
            'I assigned the dynamic tag. Refresh the front to see the resolved value.'
        );
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function patchElementSetting(
        array $params,
        string $key,
        mixed $value,
        bool $dryRun,
        string $previewMessage,
        string $doneMessage
    ): array {
        $loaded = $this->loadTree($params);
        if (isset($loaded['error'])) {
            return $loaded;
        }

        $elementId = (string) ($params['element_id'] ?? '');
        if ($elementId === '') {
            return $this->failure('I need the element to update. Which one?', 'missing_element');
        }

        /** @var ElementTree $tree */
        $tree    = $loaded['tree'];
        $element = $tree->findById($elementId);
        if ($element === null) {
            return $this->failure('I could not find that element on this page.', 'element_not_found');
        }

        $list = $tree->toList();
        foreach ($list as $index => $item) {
            if ((string) ($item['id'] ?? '') !== $elementId) {
                continue;
            }

            $settings         = is_array($item['settings'] ?? null) ? $item['settings'] : [];
            $settings[$key]   = $value;
            $list[$index]['settings'] = $settings;
            break;
        }

        $written = $this->elements()->write($loaded['post_id'], $loaded['area'], $list, $dryRun);
        if (($written['success'] ?? false) !== true) {
            return $written;
        }

        return array_merge($written, [
            'message'    => $dryRun ? $previewMessage : $doneMessage,
            'element_id' => $elementId,
            'setting'    => $key,
            'value'      => $value,
        ]);
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    private function loadTree(array $params): array
    {
        $postId = absint($params['post_id'] ?? $params['postId'] ?? 0);
        $area   = $this->elements()->resolveArea((string) ($params['area'] ?? BricksMetaKeys::AREA_CONTENT));
        if ($postId <= 0 || get_post($postId) === null) {
            return $this->failure('I could not find that page. It may have been deleted.', 'post_not_found');
        }

        return [
            'post_id' => $postId,
            'area'    => $area,
            'tree'    => ElementTree::fromList($this->elements()->read($postId, $area)),
        ];
    }

    /**
     * @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 page behavior.', '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,
        ];
    }

    private function pages(): PageSettingsGateway
    {
        return $this->pages ?? new PageSettingsGateway();
    }

    private function elements(): ElementGateway
    {
        return $this->elements ?? new ElementGateway();
    }

    private function catalog(): DynamicDataCatalog
    {
        return $this->catalog ?? new DynamicDataCatalog();
    }
}
