<?php

/**
 * LolaCore — Hub Scope
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Hub;

use LolaCore\Admin\Settings;

// phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.PreparedSQL.NotPrepared -- Custom LolaCore config table queries with validated inputs.

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

/**
 * Per-master scope on the child: coarse domain toggles the site owner can narrow (C2).
 */
final class HubScope
{
    private const LEGACY_ABILITIES_KEY = 'hub_scope_abilities';

    private const DENIED_DOMAINS_KEY = 'hub_scope_denied_domains';

    public function __construct(
        private ?RemoteAbilityAllowList $allowList = null,
        private ?HubAbilityDomainMapper $domainMapper = null,
    ) {
        $this->allowList     = $allowList ?? new RemoteAbilityAllowList();
        $this->domainMapper  = $domainMapper ?? new HubAbilityDomainMapper();
    }

    /**
     * @param array<string, mixed> $params Ability params (needed for lolacore/execute-action).
     */
    public function isAllowed(string $abilityName, array $params = []): bool
    {
        if (!$this->allowList->isAllowed($abilityName)) {
            return false;
        }

        $legacy = $this->getLegacyScopedAbilities();
        if ($legacy !== null && !in_array($abilityName, $legacy, true)) {
            return false;
        }

        $domain = $this->domainMapper->resolveDomain($abilityName, $params);
        if ($domain === null) {
            return true;
        }

        return !in_array($domain, $this->getDeniedDomains(), true);
    }

    /**
     * @return string[] Domain slugs the owner has unchecked (denied to the master).
     */
    public function getDeniedDomains(): array
    {
        $raw = $this->getConfigValue(self::DENIED_DOMAINS_KEY, '');

        if ($raw === null || $raw === '') {
            return [];
        }

        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return [];
        }

        return array_values(array_filter($decoded, static fn($item): bool => is_string($item) && $item !== ''));
    }

    /**
     * @param string[] $deniedDomainSlugs Slugs from Settings::DOMAIN_MAP (+ addon slugs).
     */
    public function setDeniedDomains(array $deniedDomainSlugs): void
    {
        $deniedDomainSlugs = array_values(array_unique(array_filter(
            $deniedDomainSlugs,
            static fn($slug): bool => is_string($slug) && $slug !== ''
        )));

        if ($deniedDomainSlugs === []) {
            $this->deleteConfigValue(self::DENIED_DOMAINS_KEY);
            return;
        }

        $this->setConfigValue(
            self::DENIED_DOMAINS_KEY,
            wp_json_encode($deniedDomainSlugs) ?: '[]'
        );
    }

    /**
     * Human-readable summary of what the master may do (allowed domain labels).
     *
     * @return string[]
     */
    public function getAllowedDomainLabels(): array
    {
        $selectable = $this->domainMapper->getSelectableDomains();
        $denied     = $this->getDeniedDomains();
        $labels     = [];

        foreach ($selectable as $slug => $label) {
            if (!in_array($slug, $denied, true)) {
                $labels[] = $label;
            }
        }

        return $labels;
    }

    /**
     * @return string[]|null Legacy C1 ability whitelist, if still stored.
     */
    public function getLegacyScopedAbilities(): ?array
    {
        $raw = $this->getConfigValue(self::LEGACY_ABILITIES_KEY, '');

        if ($raw === null || $raw === '' || $raw === '*') {
            return null;
        }

        $decoded = json_decode($raw, true);
        if (!is_array($decoded)) {
            return null;
        }

        $abilities = array_values(array_filter($decoded, static fn($item): bool => is_string($item) && $item !== ''));

        return $abilities === [] ? null : $abilities;
    }

    /**
     * @param string[] $abilityNames Empty array resets legacy whitelist.
     */
    public function setScopedAbilities(array $abilityNames): void
    {
        if ($abilityNames === []) {
            $this->deleteConfigValue(self::LEGACY_ABILITIES_KEY);
            return;
        }

        $this->setConfigValue(
            self::LEGACY_ABILITIES_KEY,
            wp_json_encode(array_values(array_unique($abilityNames))) ?: '[]'
        );
    }

    public function clearAll(): void
    {
        $this->deleteConfigValue(self::LEGACY_ABILITIES_KEY);
        $this->deleteConfigValue(self::DENIED_DOMAINS_KEY);
    }

    private function getConfigValue(string $key, ?string $default = null): ?string
    {
        global $wpdb;
        $table = $wpdb->prefix . 'lolacore_config';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $value = $wpdb->get_var(
            $wpdb->prepare(
                "SELECT config_value FROM {$table} WHERE config_key = %s",
                $key,
            )
        );

        return $value ?? $default;
    }

    private function setConfigValue(string $key, string $value): void
    {
        global $wpdb;
        $table = $wpdb->prefix . 'lolacore_config';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->query(
            $wpdb->prepare(
                "INSERT INTO {$table} (config_key, config_value) VALUES (%s, %s)
                 ON DUPLICATE KEY UPDATE config_value = VALUES(config_value)",
                $key,
                $value,
            )
        );
    }

    private function deleteConfigValue(string $key): void
    {
        global $wpdb;
        $table = $wpdb->prefix . 'lolacore_config';

        // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
        $wpdb->delete($table, ['config_key' => $key], ['%s']);
    }
}
