<?php
/**
 * WPAbilitiesRegistrar — exposes Bricks tools via the WordPress Abilities API.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks;

use LolaCoreForBricks\Handlers\IndexHandler;
use LolaCoreForBricks\Handlers\OrientationHandler;
use LolaCoreForBricks\Support\AbilityI18n;

/**
 * Door 3 — REST + MCP ability registration.
 */
class WPAbilitiesRegistrar
{
    public const ABILITY_CATEGORY = 'lolacore-bricks';

    public const ABILITY_GET_BRICKS_OVERVIEW = 'lolacore-bricks/get-bricks-overview';
    public const ABILITY_REBUILD_DESIGN_INDEX = 'lolacore-bricks/rebuild-design-index';

    /**
     * Hand-written summaries for the consumer discovery index (CW9).
     * One or two sentences: what the tool does and when to pick it.
     * Hard ceiling: 200 characters (ConsumerCatalogProjector::SUMMARY_MAX_CHARS).
     */
    public const SUMMARIES = [
        self::ABILITY_GET_BRICKS_OVERVIEW          => 'Report Bricks version, CSS mode, design-system counts, and index coverage. Use first to orient on a Bricks site.',
        self::ABILITY_REBUILD_DESIGN_INDEX         => 'Advance the persisted Bricks design index in batches until finished. Use when coverage is stale or after bulk Bricks edits.',
        'lolacore-bricks/get-global-classes'       => 'List Bricks global classes with categories and lock state. Use before renaming or cleaning up the class library.',
        'lolacore-bricks/get-class-usage'          => 'Show where one global class is applied across pages and elements. Use before deleting or merging a class.',
        'lolacore-bricks/get-global-variables'     => 'List Bricks global variables and the color palette. Use to audit tokens before changing theme styles.',
        'lolacore-bricks/get-variable-usage'       => 'Show which elements, classes, or theme styles reference one variable. Use before removing a token.',
        'lolacore-bricks/get-theme-styles'         => 'List Bricks theme styles with their conditions. Use to understand which style wins on which pages.',
        'lolacore-bricks/get-components'           => 'List Bricks components with instance counts. Use to map reusable blocks across the site.',
        'lolacore-bricks/get-component-usage'      => 'List every instance of one Bricks component with page and element context. Use before editing a shared component.',
        'lolacore-bricks/get-templates'            => 'List Bricks templates with type and conditions. Use to map header, footer, and layout coverage.',
        'lolacore-bricks/resolve-template-for-page' => 'Resolve which Bricks template wins for a page and why. Use when a page renders the wrong header or layout.',
        'lolacore-bricks/describe-element-controls' => 'Describe the control contract for a Bricks element type. Use before styling or composing unfamiliar elements.',
        'lolacore-bricks/get-bricks-operating-guide' => 'Return Bricks domain SOP and architect guidance for MCP clients. Use when chat-only skills are unavailable.',
        'lolacore-bricks/find-unused-classes'      => 'Find global classes with no indexed usage. Use during design-system cleanup — check coverage first.',
        'lolacore-bricks/find-duplicate-classes'   => 'Find groups of equivalent global classes by normalized settings. Use before merging duplicates.',
        'lolacore-bricks/find-unused-variables'    => 'Find global variables with no indexed references. Use during token cleanup — check coverage first.',
        'lolacore-bricks/find-orphan-components'   => 'Find Bricks components with zero indexed instances. Use to retire unused library entries.',
        'lolacore-bricks/find-theme-style-conflicts' => 'Find theme styles whose conditions overlap on the same pages. Use before simplifying style rules.',
        'lolacore-bricks/create-global-class'          => 'Create a Bricks global class with category and initial settings. Warns when a near-duplicate already exists.',
        'lolacore-bricks/update-global-class-settings' => 'Update Bricks global class settings across breakpoints and states. Use after inspecting getGlobalClasses.',
        'lolacore-bricks/rename-global-class'          => 'Rename a Bricks global class and regenerate CSS where it is used. Call with dry_run first to review impact.',
        'lolacore-bricks/merge-global-classes'         => 'Replace one global class with another everywhere and trash the source. Call with dry_run first to review impact.',
        'lolacore-bricks/trash-global-classes'           => 'Move Bricks global classes to the native trash (reversible). Call with dry_run first to review impact.',
        'lolacore-bricks/restore-global-class'           => 'Restore a Bricks global class from the native trash without losing its settings.',
        'lolacore-bricks/set-global-variable'            => 'Create or update Bricks global variables in one batch. Warns when a value duplicates another token.',
        'lolacore-bricks/delete-global-variable'         => 'Remove a Bricks global variable. Call with dry_run first to see where it is referenced.',
        'lolacore-bricks/update-color-palette'           => 'Create or update Bricks color palettes and their swatches.',
        'lolacore-bricks/update-theme-style'             => 'Create or update a Bricks theme style, including settings and conditions.',
        'lolacore-bricks/set-design-convention'          => 'Save the Bricks naming convention and variable policy. Reports how many existing classes already match.',
        'lolacore-bricks/get-page-structure'             => 'Summarize the Bricks element tree for a page area. Use when you need structure without dumping every element.',
        'lolacore-bricks/insert-element'                 => 'Insert a Bricks element into a page area. Rejects code-execution elements. Call with dry_run first.',
        'lolacore-bricks/move-element'                   => 'Move a Bricks element to another parent or position. Call with dry_run first to preview the tree.',
        'lolacore-bricks/duplicate-element'              => 'Duplicate a Bricks element branch with new IDs. Call with dry_run first to preview the copy.',
        'lolacore-bricks/delete-element'                 => 'Remove a Bricks element branch from a page. Call with dry_run first to review the removal.',
        'lolacore-bricks/style-element'                  => 'Apply universal Bricks style controls to one element, including breakpoint and hover. Call with dry_run first.',
        'lolacore-bricks/apply-classes'                  => 'Apply Bricks global classes to one or more elements in one confirmation. Call with dry_run first.',
        'lolacore-bricks/remove-classes'                 => 'Remove Bricks global classes from one or more elements. Call with dry_run first.',
        'lolacore-bricks/update-element-settings'        => 'Update element-specific Bricks settings. Unknown keys abort with closest valid keys. Call with dry_run first.',
        'lolacore-bricks/set-element-content'            => 'Update text, image, or link on one or more existing elements. Use when the copy changes and the structure stays.',
        'lolacore-bricks/add-section'                    => 'Insert a Bricks section with optional content and site classes. Use when starting a new page block.',
        'lolacore-bricks/add-container'                  => 'Insert a Bricks container with optional content and site classes. Use inside a section.',
        'lolacore-bricks/add-div'                        => 'Insert a Bricks div with optional content and site classes. Use for a simple wrapper.',
        'lolacore-bricks/add-heading'                    => 'Insert a Bricks heading with its text and site classes already set. Use for titles.',
        'lolacore-bricks/add-text'                       => 'Insert a Bricks text element with its copy and site classes already set. Use for paragraphs.',
        'lolacore-bricks/add-image'                      => 'Insert a Bricks image from the media library. External URLs are rejected. Call with dry_run first.',
        'lolacore-bricks/add-button'                     => 'Insert a Bricks button with its label, link, and site classes already set. Use for CTAs.',
        'lolacore-bricks/create-template'                => 'Create a Bricks header, footer, or content template. Use when starting site chrome from chat.',
        'lolacore-bricks/set-template-content'           => 'Write the element tree of one template area. Use after createTemplate to fill header or footer.',
        'lolacore-bricks/update-template-conditions'     => 'Set where a template applies and preview how many pages change. Call with dry_run first.',
        'lolacore-bricks/duplicate-template'             => 'Duplicate a Bricks template with new element IDs. Use before experimenting on a copy.',
        'lolacore-bricks/delete-template'                => 'Move a Bricks template to the WordPress trash. Reversible. Call with dry_run first.',
        'lolacore-bricks/create-component-from-element'  => 'Convert a Bricks element branch into a component and leave an instance. Call with dry_run first.',
        'lolacore-bricks/update-component-properties'    => 'Define component properties and types. Always previews instance count. Call with dry_run first.',
        'lolacore-bricks/update-component'               => 'Edit a component structure or style. Always previews how many instances change. Call with dry_run first.',
        'lolacore-bricks/insert-component-instance'      => 'Insert a component instance with property values. Warns at the nesting limit of 10.',
        'lolacore-bricks/update-component-instance'      => 'Change property values on one component instance. Other instances keep their own values.',
        'lolacore-bricks/detach-component-instance'      => 'Turn a component instance into regular elements on that page. Call with dry_run first.',
        'lolacore-bricks/update-page-settings'           => 'Edit Bricks page settings such as SEO, layout, and page CSS. Template conditions stay on the template.',
        'lolacore-bricks/set-element-conditions'         => 'Set when a Bricks element is shown. Call with dry_run first.',
        'lolacore-bricks/set-element-interactions'       => 'Set Bricks element interactions. Custom JavaScript is rejected. Call with dry_run first.',
        'lolacore-bricks/list-dynamic-data-tags'         => 'List Bricks dynamic data tags grouped by provider, including ACF when it is active.',
        'lolacore-bricks/set-dynamic-content'            => 'Assign a discovered dynamic data tag to an element setting. Unknown tags are rejected.',
        'lolacore-bricks/get-woo-bricks-surface'         => 'Show which Bricks shop templates exist, which are missing, and which WooCommerce elements the site already uses.',
        'lolacore-bricks/create-woo-template'            => 'Create a Bricks shop template for product, archive, cart, checkout, or account. Call with dry_run first.',
        'lolacore-bricks/add-product-element'            => 'Insert a WooCommerce product element such as title, price, or gallery. Call with dry_run first.',
        'lolacore-bricks/get-product-elements-catalog'   => 'List Bricks WooCommerce elements grouped by shop function. Use before inserting a product piece.',
        'lolacore-bricks/clone-structure'                => 'Copy a Bricks branch onto several pages with new IDs. Call with dry_run first. Large sets run in batches.',
        'lolacore-bricks/bulk-update-content'            => 'Update text, images, or links on matching elements across pages. Call with dry_run first.',
        'lolacore-bricks/bulk-apply-classes'             => 'Add or remove global classes on matching elements across pages. Call with dry_run first.',
    ];

    public function register(): void
    {
        add_action('wp_abilities_api_categories_init', [$this, 'registerCategory']);
        add_action('wp_abilities_api_init', [$this, 'registerAbilities']);
    }

    /**
     * @return string[]
     */
    public static function getAbilitySlugs(): array
    {
        return array_merge(
            [
                self::ABILITY_GET_BRICKS_OVERVIEW,
                self::ABILITY_REBUILD_DESIGN_INDEX,
            ],
            KnowledgeAbilityMap::doorThreeSlugs(),
            GovernanceAbilityMap::doorThreeSlugs(),
            ClassWriteAbilityMap::doorThreeSlugs(),
            DesignSystemAbilityMap::doorThreeSlugs(),
            ConventionAbilityMap::doorThreeSlugs(),
            CompositionAbilityMap::doorThreeSlugs(),
            ElementStyleAbilityMap::doorThreeSlugs(),
            ContentShortcutAbilityMap::doorThreeSlugs(),
            TemplateWriteAbilityMap::doorThreeSlugs(),
            ComponentWriteAbilityMap::doorThreeSlugs(),
            PageBehaviorAbilityMap::doorThreeSlugs(),
            BatchCompositionAbilityMap::doorThreeSlugs(),
            BricksEnv::isWooCommerceActive() ? WooSurfaceAbilityMap::doorThreeSlugs() : []
        );
    }

    public function registerCategory(): void
    {
        if (!function_exists('wp_register_ability_category')) {
            return;
        }

        wp_register_ability_category(self::ABILITY_CATEGORY, [
            'label'       => __('LolaCore for Bricks', 'lolacore-for-bricks'),
            'description' => __('Bricks design system, pages, templates, and shop layouts.', 'lolacore-for-bricks'),
        ]);
    }

    public function registerAbilities(): void
    {
        if (!function_exists('wp_register_ability')) {
            return;
        }

        $this->registerReadAbility(
            self::ABILITY_GET_BRICKS_OVERVIEW,
            AbilityI18n::label('get_bricks_overview'),
            AbilityI18n::description('get_bricks_overview'),
            [],
            [
                'success'        => ['type' => 'boolean'],
                'message'        => ['type' => 'string'],
                'bricks_version' => ['type' => 'string'],
                'meets_floor'    => ['type' => 'boolean'],
                'css_loading'    => ['type' => 'string'],
                'counts'         => ['type' => 'object'],
                'index'          => ['type' => 'object'],
            ],
            'executeGetBricksOverview'
        );

        $this->registerWriteAbility(
            self::ABILITY_REBUILD_DESIGN_INDEX,
            AbilityI18n::label('rebuild_design_index'),
            AbilityI18n::description('rebuild_design_index'),
            [
                'batch_size' => ['type' => 'integer'],
                'restart'    => ['type' => 'boolean'],
                'dry_run'    => ['type' => 'boolean'],
            ],
            [
                'success'    => ['type' => 'boolean'],
                'message'    => ['type' => 'string'],
                'done'       => ['type' => 'integer'],
                'total'      => ['type' => 'integer'],
                'finished'   => ['type' => 'boolean'],
                'elapsed_ms' => ['type' => 'integer'],
                'coverage'   => ['type' => 'object'],
            ],
            'executeRebuildDesignIndex',
            false,
            true
        );

        (new KnowledgeWpRegistrar())->registerAbilities($this);
        (new GovernanceWpRegistrar())->registerAbilities($this);
        (new ClassWriteWpRegistrar())->registerAbilities($this);
        (new DesignSystemWpRegistrar())->registerAbilities($this);
        (new ConventionWpRegistrar())->registerAbilities($this);
        (new CompositionWpRegistrar())->registerAbilities($this);
        (new ElementStyleWpRegistrar())->registerAbilities($this);
        (new ContentShortcutWpRegistrar())->registerAbilities($this);
        (new TemplateWriteWpRegistrar())->registerAbilities($this);
        (new ComponentWriteWpRegistrar())->registerAbilities($this);
        (new PageBehaviorWpRegistrar())->registerAbilities($this);
        (new BatchCompositionWpRegistrar())->registerAbilities($this);
        if (BricksEnv::isWooCommerceActive()) {
            (new WooSurfaceWpRegistrar())->registerAbilities($this);
        }
    }

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

        return (new OrientationHandler())->getBricksOverview([]);
    }

    /**
     * @param array<string, mixed> $input
     * @return array<string, mixed>
     */
    public function executeRebuildDesignIndex(array $input): array
    {
        $dryRun = (bool) ($input['dry_run'] ?? false);

        return (new IndexHandler())->rebuildDesignIndex($input, $dryRun);
    }

    public function permissionCallback(): bool
    {
        return BricksEnv::userCanReadDesignSystem();
    }

    /**
     * @param array<string, array<string, string>> $outputProperties
     * @param callable|array{0: object, 1: string} $callback
     */
    public function registerKnowledgeReadAbility(
        string $slug,
        string $label,
        string $description,
        array $inputProperties,
        array $outputProperties,
        callable $callback
    ): void {
        wp_register_ability($slug, [
            'label'               => $label,
            'description'         => $description,
            'category'            => self::ABILITY_CATEGORY,
            'input_schema'        => [
                'type'       => 'object',
                'properties' => $inputProperties,
            ],
            'output_schema'       => self::envelopeOutputSchema([
                'type'       => 'object',
                'properties' => $outputProperties,
            ]),
            'permission_callback' => [$this, 'permissionCallback'],
            'execute_callback'    => static function (array $input) use ($callback, $slug) {
                return self::envelopeResponse($slug, call_user_func($callback, $input), true);
            },
            'meta'                => $this->withSummary($this->readMeta(), $slug),
        ]);
    }

    /**
     * @param array<string, mixed>                   $inputProperties
     * @param array<string, array<string, string>> $outputProperties
     */
    private function registerReadAbility(
        string $slug,
        string $label,
        string $description,
        array $inputProperties,
        array $outputProperties,
        string $callback,
        string $permissionCallback = 'permissionCallback'
    ): void {
        wp_register_ability($slug, [
            'label'               => $label,
            'description'         => $description,
            'category'            => self::ABILITY_CATEGORY,
            'input_schema'        => [
                'type'       => 'object',
                'properties' => $inputProperties,
            ],
            'output_schema'       => self::envelopeOutputSchema([
                'type'       => 'object',
                'properties' => $outputProperties,
            ]),
            'permission_callback' => [$this, $permissionCallback],
            'execute_callback'    => function (array $input) use ($callback, $slug) {
                return self::envelopeResponse($slug, call_user_func([$this, $callback], $input), true);
            },
            'meta'                => $this->withSummary($this->readMeta(), $slug),
        ]);
    }

    /**
     * @param array<string, mixed>                   $inputProperties
     * @param array<string, array<string, string>> $outputProperties
     * @param callable|array{0: object, 1: string} $callback
     */
    public function registerMappedWriteAbility(
        string $slug,
        string $label,
        string $description,
        array $inputProperties,
        array $outputProperties,
        callable $callback,
        bool $destructive,
        bool $idempotent,
        string $permissionCallback = 'permissionCallback'
    ): void {
        $this->registerWriteAbility(
            $slug,
            $label,
            $description,
            $inputProperties,
            $outputProperties,
            '',
            $destructive,
            $idempotent,
            $permissionCallback,
            $callback
        );
    }

    /**
     * @param array<string, mixed>                   $inputProperties
     * @param array<string, array<string, string>> $outputProperties
     */
    private function registerWriteAbility(
        string $slug,
        string $label,
        string $description,
        array $inputProperties,
        array $outputProperties,
        string $callback,
        bool $destructive,
        bool $idempotent,
        string $permissionCallback = 'permissionCallback',
        ?callable $executeCallback = null
    ): void {
        wp_register_ability($slug, [
            'label'               => $label,
            'description'         => $description,
            'category'            => self::ABILITY_CATEGORY,
            'input_schema'        => [
                'type'       => 'object',
                'properties' => $inputProperties,
            ],
            'output_schema'       => self::envelopeOutputSchema([
                'type'       => 'object',
                'properties' => $outputProperties,
            ]),
            'permission_callback' => [$this, $permissionCallback],
            'execute_callback'    => function (array $input) use ($callback, $slug, $executeCallback) {
                $dryRun = (bool) ($input['dry_run'] ?? false);
                if ($executeCallback !== null) {
                    return self::envelopeResponse($slug, call_user_func($executeCallback, $input), false, $dryRun);
                }

                return self::envelopeResponse($slug, call_user_func([$this, $callback], $input), false, $dryRun);
            },
            'meta'                => $this->withSummary($this->writeMeta($destructive, $idempotent), $slug),
        ]);
    }

    /**
     * Standard LolaCore response envelope, matching ActionExecutor's shape.
     *
     * @param mixed $payload Unwrapped handler payload.
     * @return mixed
     */
    public static function envelopeResponse(string $slug, $payload, bool $readonly, bool $dryRun = false)
    {
        if ($payload instanceof \WP_Error) {
            return $payload;
        }

        $actionId = str_replace('-', '_', ltrim((string) strrchr($slug, '/'), '/'));
        $result   = is_array($payload) ? $payload : ['value' => $payload];

        if (array_key_exists('success', $result) && $result['success'] === false) {
            $denied = $result;
            unset($denied['success']);
            if ($denied === [] || array_is_list($denied)) {
                $denied = ['error' => (string) ($result['error'] ?? 'denied')];
            }

            return [
                'action_id'             => $actionId,
                'mode'                  => 'denied',
                'result'                => $denied,
                'message'               => (string) ($result['message'] ?? $result['error'] ?? 'Action denied.'),
                'requires_confirmation' => false,
                'warnings'              => [],
            ];
        }

        $warnings = [];
        if (isset($result['warnings']) && is_array($result['warnings'])) {
            $warnings = array_values(array_filter($result['warnings'], 'is_string'));
        }

        $isPreview = $dryRun || !empty($result['dry_run']);

        return [
            'action_id'             => $actionId,
            'mode'                  => 'executed',
            'result'                => $result,
            'message'               => $readonly ? 'Read executed' : 'Action executed successfully.',
            'requires_confirmation' => !$readonly && $isPreview,
            'warnings'              => $warnings,
        ];
    }

    /**
     * @param array<string, mixed> $resultSchema Previous ability output schema.
     * @return array<string, mixed>
     */
    public static function envelopeOutputSchema(array $resultSchema): array
    {
        if (($resultSchema['type'] ?? '') !== 'object') {
            $resultSchema = ['type' => 'object'];
        }
        $resultSchema['description'] = 'Action-specific result payload.';

        return [
            'type'       => 'object',
            'properties' => [
                'action_id'             => ['type' => 'string'],
                'mode'                  => ['type' => 'string', 'enum' => ['executed', 'preview', 'denied']],
                'result'                => $resultSchema,
                'message'               => ['type' => 'string'],
                'requires_confirmation' => ['type' => 'boolean'],
                'warnings'              => ['type' => 'array', 'items' => ['type' => 'string']],
            ],
            'required'   => ['action_id', 'mode', 'message'],
        ];
    }

    /**
     * @param array<string, mixed> $meta Ability meta block.
     * @return array<string, mixed>
     */
    private function withSummary(array $meta, string $slug): array
    {
        $summary = self::SUMMARIES[$slug] ?? '';

        if ($summary !== '' && isset($meta['mcp']) && is_array($meta['mcp'])) {
            $meta['mcp']['summary'] = $summary;
        }

        return $meta;
    }

    /**
     * @return array<string, mixed>
     */
    private function readMeta(): array
    {
        return [
            'show_in_rest' => true,
            'mcp'          => ['public' => true, 'type' => 'tool'],
            'annotations'  => [
                'readonly'    => true,
                'destructive' => false,
                'idempotent'  => true,
            ],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    private function writeMeta(bool $destructive, bool $idempotent): array
    {
        return [
            'show_in_rest' => true,
            'mcp'          => ['public' => true, 'type' => 'tool'],
            'annotations'  => [
                'readonly'    => false,
                'destructive' => $destructive,
                'idempotent'  => $idempotent,
            ],
        ];
    }
}
