<?php

/**
 * LolaCore — Hub Ability Domain Mapper
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Hub;

use LolaCore\Actions\ActionExecutor;
use LolaCore\Admin\Settings;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Maps a remote ability name (and optional params) to a coarse domain slug for HubScope.
 */
final class HubAbilityDomainMapper
{
    /**
     * @var array<string, string>
     */
    private const META_ABILITY_DOMAINS = [
        'lolacore/get-site-info'          => 'site_diagnostics',
        'lolacore/get-site-health'        => 'site_diagnostics',
        'lolacore/get-scan-data'          => 'site_diagnostics',
        'lolacore/get-action-history'     => 'site_diagnostics',
        'lolacore/get-memory-context'     => 'site_diagnostics',
        'lolacore/add-memory-fact'        => 'site_diagnostics',
        'lolacore/add-skill'              => 'plugin_builder',
        'lolacore/apply-agency-profile'   => 'plugin_builder',
        'lolacore/set-provider'           => 'site_diagnostics',
        'lolacore/receive-memory-reconcile' => 'site_diagnostics',
        'lolacore/read-site-memory'       => 'site_diagnostics',
    ];

    /**
     * @var array<string, string>
     */
    private const ADDON_NAMESPACE_DOMAINS = [
        'lolacore-fse'        => 'block_builder',
        'lolacore-statistics' => 'site_diagnostics',
        'lolacore-yoast'      => 'site_diagnostics',
        'lolacore-wpforms'    => 'content_metadata',
    ];

    /**
     * Action types written directly by specialized handlers (not registered abilities,
     * so the registry lookup below can't resolve them). Keeps the action log grouped.
     *
     * @var array<string, string>
     */
    private const HANDLER_ACTION_DOMAINS = [
        'create_block'         => 'block_builder',
        'update_block_code'    => 'block_builder',
        'install_block'        => 'block_builder',
        'uninstall_block'      => 'block_builder',
        'create_plugin'        => 'plugin_builder',
        'update_plugin_code'   => 'plugin_builder',
        'save_plugin_code'     => 'plugin_builder',
        'install_plugin_local' => 'plugin_builder',
        'delete_built_plugin'  => 'plugin_builder',
        'add_skill'            => 'plugin_builder',
        'activate_skill'       => 'plugin_builder',
        'deactivate_skill'     => 'plugin_builder',
        'update_skill'         => 'plugin_builder',
        'delete_skill'         => 'plugin_builder',
        'create_playbook'      => 'plugin_builder',
        'update_playbook'      => 'plugin_builder',
        'delete_playbook'      => 'plugin_builder',
        'activate_playbook'    => 'plugin_builder',
        'deactivate_playbook'  => 'plugin_builder',
    ];

    /**
     * Memoized ability map (name => schema). Injectable for testing.
     *
     * @var array<string, mixed>|null
     */
    private ?array $registeredAbilities;

    /**
     * @param array<string, mixed>|null $registeredAbilities Inject to avoid the registry
     *                                                        rebuild (also used by tests).
     */
    public function __construct(?array $registeredAbilities = null)
    {
        $this->registeredAbilities = $registeredAbilities;
    }

    /**
     * Resolve the coarse domain slug for a stored action-log `action_type` value.
     *
     * Covers all three shapes written to wp_lolacore_log: full ability names
     * (Hub remote execution), specialized handler action types, and bare action ids
     * from the generic executor. Returns null when nothing resolves (caller buckets
     * it under a generic "specialized work" section — never a broken layout).
     */
    public function resolveDomainForActionType(string $actionType): ?string
    {
        $actionType = trim($actionType);
        if ($actionType === '') {
            return null;
        }

        // 1. Full ability names (Hub remote execution logs the ability; meta abilities).
        $direct = $this->resolveDomain($actionType);
        if ($direct !== null) {
            return $direct;
        }

        // 2. Specialized handler action types (logged directly, not registered abilities).
        if (isset(self::HANDLER_ACTION_DOMAINS[$actionType])) {
            return self::HANDLER_ACTION_DOMAINS[$actionType];
        }

        // 3. Bare action id from the generic executor → registry category → domain.
        $actionId = ActionExecutor::normalizeActionId($actionType);

        return $actionId !== '' ? $this->resolveActionIdDomain($actionId) : null;
    }

    /**
     * @param array<string, mixed> $params
     */
    public function resolveDomain(string $ability, array $params = []): ?string
    {
        if (isset(self::META_ABILITY_DOMAINS[$ability])) {
            return self::META_ABILITY_DOMAINS[$ability];
        }

        if ($ability === 'lolacore/execute-action') {
            $actionId = ActionExecutor::normalizeActionId((string) ($params['action_id'] ?? ''));

            return $actionId !== '' ? $this->resolveActionIdDomain($actionId) : null;
        }

        if (str_contains($ability, '/') && str_starts_with($ability, 'lolacore-')) {
            [$namespace] = explode('/', $ability, 2);

            return self::ADDON_NAMESPACE_DOMAINS[$namespace] ?? null;
        }

        if (str_starts_with($ability, 'lolacore/')) {
            $suffix   = substr($ability, strlen('lolacore/'));
            $actionId = ActionExecutor::normalizeActionId(str_replace('-', '_', $suffix));

            return $this->resolveActionIdDomain($actionId);
        }

        return null;
    }

    /**
     * Domain slugs the transparency UI can expose (core + licensed addons).
     *
     * @return array<string, string> slug => label
     */
    public function getSelectableDomains(): array
    {
        $domains = [];

        foreach (array_keys(Settings::DOMAIN_MAP) as $slug) {
            $domains[$slug] = Settings::getDomainLabel($slug);
        }

        foreach (apply_filters('lolacore_addon_domains', []) as $addonSlug => $addonData) {
            if (empty($addonData['license_active'])) {
                continue;
            }

            $label = is_string($addonData['label'] ?? null) ? $addonData['label'] : ucfirst((string) $addonSlug);
            $domains[(string) $addonSlug] = $label;
        }

        return $domains;
    }

    /**
     * @return array<string, mixed> ability name => schema (memoized per instance)
     */
    private function getRegisteredAbilities(): array
    {
        if ($this->registeredAbilities === null) {
            $this->registeredAbilities = (new \LolaCore\Actions\AbilitiesRegistry())->getRegisteredAbilities();
        }

        return $this->registeredAbilities;
    }

    private function resolveActionIdDomain(string $actionId): ?string
    {
        $tools = $this->getRegisteredAbilities();

        foreach ($tools as $toolName => $tool) {
            if (!is_string($toolName)) {
                continue;
            }

            $normalized = ActionExecutor::normalizeActionId($toolName);
            if ($normalized !== $actionId) {
                continue;
            }

            $category = is_array($tool) ? (string) ($tool['category'] ?? '') : '';

            return $this->categoryToDomain($category);
        }

        return $this->categoryToDomain($this->guessCategoryFromActionId($actionId));
    }

    private function categoryToDomain(string $category): ?string
    {
        if ($category === '') {
            return null;
        }

        foreach (Settings::DOMAIN_MAP as $domainSlug => $categories) {
            if (in_array($category, $categories, true)) {
                return $domainSlug;
            }
        }

        if ($category === 'lolacore') {
            return 'site_diagnostics';
        }

        return null;
    }

    private function guessCategoryFromActionId(string $actionId): string
    {
        if (str_starts_with($actionId, 'wc_')) {
            return 'woocommerce';
        }

        if (str_contains($actionId, 'plugin')) {
            return 'plugins';
        }

        if (str_contains($actionId, 'theme')) {
            return 'themes';
        }

        if (str_contains($actionId, 'content') || str_contains($actionId, 'post')) {
            return 'content';
        }

        return '';
    }
}
