<?php
declare(strict_types=1);

namespace Ekesto\CacheInvalidator\Integrations\Forms\Adapters;

use Ekesto\CacheInvalidator\Support\Debug;
use Ekesto\CacheInvalidator\Integrations\Forms\FormAdapterInterface;
use Ekesto\CacheInvalidator\Integrations\Forms\FormIdentifierAliasProviderInterface;

/**
 * Contact Form 7 adapter.
 */
final class ContactForm7Adapter implements FormAdapterInterface, FormIdentifierAliasProviderInterface
{
    use NormalizesFormIds;

    /**
     * Return adapter key.
     *
     * @return string
     */
    public function key(): string
    {
        return 'contact_form_7';
    }

    /**
     * Return adapter label.
     *
     * @return string
     */
    public function label(): string
    {
        return 'Contact Form 7';
    }

    /**
     * Return whether CF7 is available.
     *
     * @return bool
     */
    public function isAvailable(): bool
    {
        return defined('WPCF7_VERSION') || post_type_exists('wpcf7_contact_form');
    }

    /**
     * Register form change hooks.
     *
     * @param callable $onFormChanged Change callback.
     * @return void
     */
    public function registerHooks(callable $onFormChanged): void
    {
        Debug::verbose('forms adapter hooks registered: contact_form_7');

        add_action('save_post_wpcf7_contact_form', static function (int $postId, \WP_Post $post, bool $update) use ($onFormChanged): void {
            Debug::verbose(sprintf(
                'forms adapter event: contact_form_7 hook=save_post_wpcf7_contact_form post_id=%d update=%s',
                $postId,
                $update ? 'yes' : 'no'
            ));

            if (wp_is_post_autosave($postId) || wp_is_post_revision($postId)) {
                Debug::verbose(sprintf(
                    'forms adapter skip: contact_form_7 hook=save_post_wpcf7_contact_form post_id=%d reason=autosave_or_revision',
                    $postId
                ));
                return;
            }

            Debug::verbose(sprintf(
                'forms adapter dispatch: contact_form_7 form_id=%d hook=save_post_wpcf7_contact_form event=%s',
                $postId,
                $update ? 'update' : 'create'
            ));
            $onFormChanged('contact_form_7', (string) $postId, [
                'event' => $update ? 'update' : 'create',
                'source_hook' => 'save_post_wpcf7_contact_form',
                'post_status' => (string) $post->post_status,
            ]);
        }, 20, 3);

        add_action('deleted_post', static function (int $postId, \WP_Post $post) use ($onFormChanged): void {
            if ($post->post_type !== 'wpcf7_contact_form') {
                return;
            }

            Debug::verbose(sprintf(
                'forms adapter dispatch: contact_form_7 form_id=%d hook=deleted_post event=delete',
                $postId
            ));
            $onFormChanged('contact_form_7', (string) $postId, [
                'event' => 'delete',
                'source_hook' => 'deleted_post',
            ]);
        }, 20, 2);

        add_action('trashed_post', static function (int $postId) use ($onFormChanged): void {
            $post = get_post($postId);
            if (!$post instanceof \WP_Post || $post->post_type !== 'wpcf7_contact_form') {
                return;
            }

            Debug::verbose(sprintf(
                'forms adapter dispatch: contact_form_7 form_id=%d hook=trashed_post event=delete',
                $postId
            ));
            $onFormChanged('contact_form_7', (string) $postId, [
                'event' => 'delete',
                'source_hook' => 'trashed_post',
            ]);
        }, 20, 1);
    }

    /**
     * Return shortcode tags.
     *
     * @return string[]
     */
    public function shortcodes(): array
    {
        return ['contact-form-7'];
    }

    /**
     * Return block names.
     *
     * @return string[]
     */
    public function blockNames(): array
    {
        return ['contact-form-7/contact-form-selector'];
    }

    /**
     * Return candidate needles.
     *
     * @return string[]
     */
    public function candidateNeedles(): array
    {
        return ['contact-form-7', 'contact-form-selector'];
    }

    /**
     * Extract form IDs from text.
     *
     * @param string $text Source text.
     * @return string[]
     */
    public function extractFormIdsFromContent(string $text): array
    {
        if ($text === '') {
            return [];
        }

        $matches = [];

        if (preg_match_all('/\\[contact-form-7[^\]]*\\bid\\s*=\\s*["\']?([^"\'\]\s]+)["\']?/i', $text, $shortcodeMatches) > 0) {
            $matches = array_merge($matches, $shortcodeMatches[1] ?? []);
        }

        if (preg_match_all('/wp:contact-form-7\\/contact-form-selector\\s+\{[^\}]*"id"\\s*:\\s*"?([A-Za-z0-9_-]+)"?/i', $text, $blockMatches) > 0) {
            $matches = array_merge($matches, $blockMatches[1] ?? []);
        }

        if (preg_match_all('/wp:contact-form-7\\/contact-form-selector\\s+\{[^\}]*"hash"\\s*:\\s*"([0-9a-fA-F]{7,64})"/i', $text, $blockHashMatches) > 0) {
            $matches = array_merge($matches, $blockHashMatches[1] ?? []);
        }

        return $this->normalizeIds($matches);
    }

    /**
     * Expand CF7 identifier aliases (numeric ID + hash representations).
     *
     * @param string|int          $formId Changed form identifier.
     * @param array<string,mixed> $context Event context.
     * @return string[]
     */
    public function expandTargetIdentifiers($formId, array $context = []): array
    {
        unset($context);

        $identifier = trim((string) $formId);
        if ($identifier === '') {
            return [];
        }

        $identifiers = [$identifier];
        $formPost = $this->resolveFormPostFromIdentifier($identifier);
        if (!$formPost instanceof \WP_Post) {
            return $this->normalizeIds($identifiers);
        }

        $identifiers[] = (string) $formPost->ID;

        $hash = strtolower(trim((string) get_post_meta((int) $formPost->ID, '_hash', true)));
        if ($this->isValidHash($hash)) {
            $identifiers[] = $hash;
            $identifiers[] = substr($hash, 0, 7);
        }

        return $this->normalizeIds($identifiers);
    }

    /**
     * Resolve a CF7 form post from either numeric ID or hash identifier.
     *
     * @param string $identifier CF7 form identifier.
     * @return \WP_Post|null
     */
    private function resolveFormPostFromIdentifier(string $identifier): ?\WP_Post
    {
        if (ctype_digit($identifier)) {
            $post = get_post(absint($identifier));
            if ($post instanceof \WP_Post && $post->post_type === 'wpcf7_contact_form') {
                return $post;
            }

            return null;
        }

        $hash = strtolower($identifier);
        if (!$this->isValidHash($hash)) {
            return null;
        }

        // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query -- Event-driven CF7 hash resolution is bounded to one ID result for the form post type.
        $postIds = get_posts([
            'post_type' => 'wpcf7_contact_form',
            'post_status' => 'any',
            'posts_per_page' => 1,
            'fields' => 'ids',
            'orderby' => 'ID',
            'order' => 'ASC',
            'no_found_rows' => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
            'meta_query' => [
                [
                    'key' => '_hash',
                    'compare' => 'REGEXP',
                    'value' => '^' . preg_quote($hash, '/'),
                ],
            ],
        ]);
        // phpcs:enable WordPress.DB.SlowDBQuery.slow_db_query_meta_query

        if (!is_array($postIds) || empty($postIds)) {
            return null;
        }

        $post = get_post(absint((int) $postIds[0]));
        if ($post instanceof \WP_Post && $post->post_type === 'wpcf7_contact_form') {
            return $post;
        }

        return null;
    }

    /**
     * Return whether a value is a valid CF7 hash identifier.
     *
     * @param string $value Candidate identifier.
     * @return bool
     */
    private function isValidHash(string $value): bool
    {
        return (bool) preg_match('/^[0-9a-f]{7,64}$/', strtolower($value));
    }

}
