<?php

namespace Storeplugin\PDFInvoice\Framework\Traits;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Framework\Rest\TermMetaRegistrar;

/**
 * Shared REST registration and guard behavior for term-meta builders.
 */
trait SupportsRegisteredTermMeta
{
    /**
     * In-flight context for registered term meta writes triggered by this instance.
     *
     * @var array<string, array{term_id:int,current_value:mixed,allow_locked_write:bool}>
     */
    private $registeredMetaWriteContext = [];

    /**
     * Whether the direct term-meta guard filters have been registered.
     *
     * @var bool
     */
    private $registeredMetaGuardsAdded = false;

    /**
     * Register meta keys with WordPress.
     *
     * @internal WordPress callback method - do not call directly
     */
    public function registerMeta(): void
    {
        $this->ensureRegisteredMetaGuards();
        TermMetaRegistrar::register($this);
    }

    /**
     * Ensure direct term-meta mutation filters are registered once.
     */
    private function ensureRegisteredMetaGuards(): void
    {
        if ($this->registeredMetaGuardsAdded) {
            return;
        }

        $this->registeredMetaGuardsAdded = true;

        \add_filter('add_term_metadata', [$this, 'filterRegisteredMetaMutation'], 10, 5);
        \add_filter('update_term_metadata', [$this, 'filterRegisteredMetaMutation'], 10, 5);
    }

    /**
     * Sanitize registered term meta using the field's configured type.
     *
     * register_meta() sanitize callbacks receive the meta key as the second
     * argument, not the field type, so this callback must resolve the field
     * definition before delegating to sanitizeField().
     *
     * @internal WordPress callback method - do not call directly
     * @param mixed  $value
     * @param string $metaKey
     * @param string $objectType
     * @return mixed
     */
    public function sanitizeRegisteredMeta($value, string $metaKey = '', string $objectType = '')
    {
        unset($objectType);

        $field = $this->findFieldById($metaKey);
        if ($field === null) {
            return $this->sanitizeField($value);
        }

        $type = (string) ($field['type'] ?? 'text');
        if ($type === 'repeater' && is_array($value) && !empty($field['fields']) && is_array($field['fields'])) {
            $context = $this->registeredMetaWriteContext[$metaKey] ?? null;
            $currentRows = [];
            if (is_array($context) && isset($context['current_value']) && is_array($context['current_value'])) {
                $currentRows = $context['current_value'];
            }
            $allowLockedSubmittedValues = is_array($context) && !empty($context['allow_locked_write']);

            return $this->sanitizeRepeaterRows(
                $value,
                $this->getSanitizableRepeaterSubFields($field),
                $currentRows,
                $allowLockedSubmittedValues
            );
        }

        return $this->sanitizeField($value, $type);
    }

    /**
     * Authorize registered term meta writes for this field definition.
     *
     * Locked fields may only be written through the save flow that explicitly
     * provides write context for the target term/meta key.
     *
     * @param bool     $allowed
     * @param string   $metaKey
     * @param int      $termId
     * @param int      $userId
     * @param string   $cap
     * @param string[] $caps
     * @return bool
     */
    public function authorizeRegisteredMetaWrite(
        bool $allowed,
        string $metaKey,
        int $termId,
        int $userId,
        string $cap,
        array $caps
    ): bool {
        unset($userId, $cap, $caps);

        if (!$this->managesTerm($termId)) {
            return $allowed;
        }

        $field = $this->findFieldById($metaKey);
        if ($field === null || !$this->isFieldLocked($field)) {
            return $allowed;
        }

        return $this->hasRegisteredMetaWriteContext($metaKey, $termId, true) ? $allowed : false;
    }

    /**
     * Block direct add/update_term_meta() writes for locked registered fields.
     *
     * @param mixed  $check
     * @param int    $termId
     * @param string $metaKey
     * @param mixed  $metaValue
     * @param mixed  $extra
     * @return mixed
     */
    public function filterRegisteredMetaMutation($check, int $termId, string $metaKey, $metaValue, $extra)
    {
        unset($metaValue, $extra);

        if (!$this->managesTerm($termId)) {
            return $check;
        }

        $field = $this->findFieldById($metaKey);
        if ($field === null || !$this->isFieldLocked($field)) {
            return $check;
        }

        if ($this->hasRegisteredMetaWriteContext($metaKey, $termId, true)) {
            return $check;
        }

        return false;
    }

    /**
     * Update term meta while exposing current-value context to the registered sanitizer.
     *
     * @param int   $termId
     * @param array $field
     * @param mixed $value
     * @param mixed $currentValue
     * @return void
     */
    protected function updateRegisteredTermMeta(int $termId, array $field, $value, $currentValue): void
    {
        $metaKey = \sanitize_key((string) ($field['id'] ?? ''));
        if ($metaKey === '') {
            return;
        }

        $this->registeredMetaWriteContext[$metaKey] = [
            'term_id' => $termId,
            'current_value' => $currentValue,
            'allow_locked_write' => $this->isFieldLocked($field),
        ];

        try {
            \update_term_meta($termId, $metaKey, $value);
        } finally {
            unset($this->registeredMetaWriteContext[$metaKey]);
        }
    }

    /**
     * Find a top-level field definition by its registered meta key.
     *
     * @param string $id
     * @return array|null
     */
    private function findFieldById(string $id): ?array
    {
        $id = \sanitize_key($id);
        if ($id === '') {
            return null;
        }

        foreach ($this->restSections() as $section) {
            $fields = isset($section['fields']) && is_array($section['fields']) ? $section['fields'] : [];
            foreach ($fields as $field) {
                if (!is_array($field)) {
                    continue;
                }

                $fieldId = \sanitize_key((string) ($field['id'] ?? ''));
                if ($fieldId === $id) {
                    return $field;
                }
            }
        }

        return null;
    }

    /**
     * Determine whether the given term belongs to one of this form's taxonomies.
     *
     * @param int $termId
     * @return bool
     */
    private function managesTerm(int $termId): bool
    {
        if ($termId <= 0) {
            return false;
        }

        $term = \get_term($termId);
        if (!$term instanceof \WP_Term) {
            return false;
        }

        return in_array((string) $term->taxonomy, $this->restTaxonomies(), true);
    }

    /**
     * Determine whether the current write context matches the target meta key.
     *
     * @param string $metaKey
     * @param int    $termId
     * @param bool   $requireLockedWriteAllowance
     * @return bool
     */
    private function hasRegisteredMetaWriteContext(
        string $metaKey,
        int $termId,
        bool $requireLockedWriteAllowance = false
    ): bool {
        $context = $this->registeredMetaWriteContext[$metaKey] ?? null;
        if (!is_array($context) || (int) ($context['term_id'] ?? 0) !== $termId) {
            return false;
        }

        if ($requireLockedWriteAllowance && empty($context['allow_locked_write'])) {
            return false;
        }

        return true;
    }
}
