<?php

namespace Storeplugin\PDFInvoice\Framework\Traits;

defined('ABSPATH') || exit;

use Storeplugin\PDFInvoice\Framework\Rest\PostMetaRegistrar;

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

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

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

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

        $this->registeredPostMetaGuardsAdded = true;

        \add_filter('add_post_metadata', [$this, 'filterRegisteredMetaMutation'], 10, 5);
        \add_filter('update_post_metadata', [$this, 'filterRegisteredMetaMutation'], 10, 5);
    }

    /**
     * Sanitize registered post meta using the field's configured type.
     *
     * @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->registeredPostMetaWriteContext[$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 post meta writes for this field definition.
     *
     * Locked fields may only be written through the save flow that explicitly
     * provides write context for the target object/meta key.
     *
     * @param bool     $allowed
     * @param string   $metaKey
     * @param int      $objectId
     * @param int      $userId
     * @param string   $cap
     * @param string[] $caps
     * @return bool
     */
    public function authorizeRegisteredMetaWrite(
        bool $allowed,
        string $metaKey,
        int $objectId,
        int $userId,
        string $cap,
        array $caps
    ): bool {
        unset($userId, $cap, $caps);

        if (!$this->managesPost($objectId)) {
            return $allowed;
        }

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

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

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

        if (!$this->managesPost($objectId)) {
            return $check;
        }

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

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

        return false;
    }

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

        $this->registeredPostMetaWriteContext[$metaKey] = [
            'object_id' => $objectId,
            'current_value' => $currentValue,
            'allow_locked_write' => $this->isFieldLocked($field),
        ];

        try {
            \update_post_meta($objectId, $metaKey, $value);
        } finally {
            unset($this->registeredPostMetaWriteContext[$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->restFields() as $field) {
            if (!is_array($field)) {
                continue;
            }

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

        return null;
    }

    /**
     * Determine whether the given post belongs to one of this metabox's post types.
     *
     * @param int $objectId
     * @return bool
     */
    private function managesPost(int $objectId): bool
    {
        if ($objectId <= 0) {
            return false;
        }

        $postType = \get_post_type($objectId);
        if (!is_string($postType) || $postType === '') {
            return false;
        }

        return in_array($postType, $this->restPostTypes(), true);
    }

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

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

        return true;
    }
}
