<?php

namespace WPSN\Modules\ArchiveControls;

use WPSN\Core\AbstractModule;
use WPSN\Domain\SeoDocumentContext;

defined('ABSPATH') || exit;

/**
 * Archive Controls
 *
 * Adds specific robots defaults for archive sub-types that WordPress often leaves too broad
 * (author/date/post type archives), can fully disable archive routes, and handles thin media
 * attachment pages.
 */
final class ArchiveControlsModule extends AbstractModule
{
    public const ARCHIVE_MODE_INDEX = 'index';
    public const ARCHIVE_MODE_NOINDEX = 'noindex';
    public const ARCHIVE_MODE_DISABLE_301_TO_HOME = 'disable_301_to_home';
    public const ARCHIVE_MODE_DISABLE_301_TO_CUSTOM = 'disable_301_to_custom';
    public const ARCHIVE_MODE_DISABLE_404 = 'disable_404';

    public const ATTACHMENT_MODE_DEFAULT = 'default';
    public const ATTACHMENT_MODE_NOINDEX = 'noindex';
    public const ATTACHMENT_MODE_REDIRECT_FILE = 'redirect_file';
    public const ATTACHMENT_MODE_REDIRECT_PARENT = 'redirect_parent';

    public function id(): string
    {
        return 'archive_controls';
    }

    public function name(): string
    {
        return __('Archive Controls', 'wp-seo-next');
    }

    public function description(): string
    {
        return __('Specific robots defaults and disable modes for author/date/post type archives plus attachment page controls.', 'wp-seo-next');
    }

    public function defaultEnabled(): bool
    {
        return true;
    }

    public function register(): void
    {
        add_filter('wpsn/meta/robots', [$this, 'filterRobots'], 20, 2);
        add_action('template_redirect', [$this, 'maybeHandleArchiveRequest'], 0);
        add_action('template_redirect', [$this, 'maybeRedirectAttachmentPage'], 0);
    }

    /**
     * @param array<string,mixed> $robots
     * @return array<string,mixed>
     */
    public function filterRobots(array $robots, SeoDocumentContext $ctx): array
    {
        $rule = $this->resolveArchiveRule($ctx);
        if (is_array($rule) && !empty($rule['enabled'])) {
            $mode = (string) ($rule['mode'] ?? self::ARCHIVE_MODE_INDEX);
            if (in_array($mode, [self::ARCHIVE_MODE_INDEX, self::ARCHIVE_MODE_NOINDEX], true)) {
                $robots['noindex'] = ($mode === self::ARCHIVE_MODE_NOINDEX);
                $robots['nofollow'] = empty($rule['follow']);
            }
        }

        if ($this->isAttachmentContext($ctx) && $this->attachmentMode() === self::ATTACHMENT_MODE_NOINDEX) {
            $robots['noindex'] = true;
            $robots['nofollow'] = false;
        }

        return $robots;
    }

    public function maybeHandleArchiveRequest(): void
    {
        $bucket = $this->currentArchiveBucket();
        if ($bucket === '') {
            return;
        }

        $rule = $this->archiveRule($bucket);
        if (empty($rule['enabled'])) {
            return;
        }

        $mode = (string) ($rule['mode'] ?? self::ARCHIVE_MODE_INDEX);
        if ($mode === self::ARCHIVE_MODE_DISABLE_404) {
            $this->render404();
            return;
        }

        if (!in_array($mode, [self::ARCHIVE_MODE_DISABLE_301_TO_HOME, self::ARCHIVE_MODE_DISABLE_301_TO_CUSTOM], true)) {
            return;
        }

        $target = $this->resolveArchiveRedirectTarget($rule);
        if ($target === '') {
            return;
        }

        $currentUrl = $this->currentRequestUrl();
        if ($currentUrl !== '' && $this->sameUrl($target, $currentUrl)) {
            return;
        }

        wp_safe_redirect($target, 301, 'WP SEO Next');
        exit;
    }

    public function maybeRedirectAttachmentPage(): void
    {
        $mode = $this->attachmentMode();
        if (!in_array($mode, [self::ATTACHMENT_MODE_REDIRECT_FILE, self::ATTACHMENT_MODE_REDIRECT_PARENT], true)) {
            return;
        }

        if (!function_exists('is_attachment') || !is_attachment()) {
            return;
        }

        $attachmentId = function_exists('get_queried_object_id') ? (int) get_queried_object_id() : 0;
        if ($attachmentId <= 0) {
            return;
        }

        $target = $this->resolveAttachmentRedirectTarget($attachmentId, $mode);
        if ($target === '') {
            return;
        }

        $currentUrl = $this->currentRequestUrl();
        if ($currentUrl !== '' && $this->sameUrl($target, $currentUrl)) {
            return;
        }

        // Attachment files may live on a CDN or external object storage,
        // so use wp_redirect() instead of wp_safe_redirect().
        wp_redirect($target, 301, 'WP SEO Next');
        exit;
    }

    public function resolveAttachmentRedirectTarget(int $attachmentId, ?string $mode = null): string
    {
        if ($attachmentId <= 0) {
            return '';
        }

        $mode = is_string($mode) && $mode !== '' ? $mode : $this->attachmentMode();
        if (!in_array($mode, [self::ATTACHMENT_MODE_REDIRECT_FILE, self::ATTACHMENT_MODE_REDIRECT_PARENT], true)) {
            return '';
        }

        if ($mode === self::ATTACHMENT_MODE_REDIRECT_PARENT) {
            $target = $this->resolveAttachmentParentUrl($attachmentId);
            if ($target !== '') {
                return $target;
            }
        }

        $fileUrl = wp_get_attachment_url($attachmentId);
        $fileUrl = is_string($fileUrl) ? esc_url_raw($fileUrl) : '';

        return $fileUrl;
    }

    /**
     * @return array{enabled:bool,mode:string,follow:bool,redirect_url:string}|null
     */
    private function resolveArchiveRule(SeoDocumentContext $ctx): ?array
    {
        if ($ctx->type !== SeoDocumentContext::TYPE_ARCHIVE) {
            return null;
        }

        $subType = (string) ($ctx->objectSubType ?? '');

        if ($subType === 'author' || (function_exists('is_author') && is_author())) {
            return $this->archiveRule('author');
        }

        if ($subType === 'date' || (function_exists('is_date') && is_date())) {
            return $this->archiveRule('date');
        }

        if ($subType === 'post_type' || (function_exists('is_post_type_archive') && is_post_type_archive())) {
            return $this->archiveRule('post_type');
        }

        return null;
    }

    /**
     * @return array{enabled:bool,mode:string,follow:bool,redirect_url:string}
     */
    private function archiveRule(string $bucket): array
    {
        $rule = $this->settings->get('archive_controls.' . $bucket, []);
        if (!is_array($rule)) {
            $rule = [];
        }

        $legacyIndex = !array_key_exists('index', $rule) || !empty($rule['index']);
        $mode = isset($rule['mode']) ? sanitize_key((string) $rule['mode']) : '';
        if (!in_array($mode, [
            self::ARCHIVE_MODE_INDEX,
            self::ARCHIVE_MODE_NOINDEX,
            self::ARCHIVE_MODE_DISABLE_301_TO_HOME,
            self::ARCHIVE_MODE_DISABLE_301_TO_CUSTOM,
            self::ARCHIVE_MODE_DISABLE_404,
        ], true)) {
            $mode = $legacyIndex ? self::ARCHIVE_MODE_INDEX : self::ARCHIVE_MODE_NOINDEX;
        }

        $redirectUrl = isset($rule['redirect_url']) ? esc_url_raw((string) $rule['redirect_url']) : '';

        return [
            'enabled' => !empty($rule['enabled']),
            'mode' => $mode,
            'follow' => !array_key_exists('follow', $rule) || !empty($rule['follow']),
            'redirect_url' => $redirectUrl,
        ];
    }

    private function attachmentMode(): string
    {
        $mode = sanitize_key((string) $this->settings->get('archive_controls.attachments.mode', self::ATTACHMENT_MODE_DEFAULT));
        if (!in_array($mode, [
            self::ATTACHMENT_MODE_DEFAULT,
            self::ATTACHMENT_MODE_NOINDEX,
            self::ATTACHMENT_MODE_REDIRECT_FILE,
            self::ATTACHMENT_MODE_REDIRECT_PARENT,
        ], true)) {
            return self::ATTACHMENT_MODE_DEFAULT;
        }

        return $mode;
    }

    private function isAttachmentContext(SeoDocumentContext $ctx): bool
    {
        return $ctx->type === SeoDocumentContext::TYPE_SINGULAR && (string) ($ctx->objectSubType ?? '') === 'attachment';
    }

    private function currentArchiveBucket(): string
    {
        if (function_exists('is_author') && is_author()) {
            return 'author';
        }

        if (function_exists('is_date') && is_date()) {
            return 'date';
        }

        if (function_exists('is_post_type_archive') && is_post_type_archive()) {
            return 'post_type';
        }

        return '';
    }

    /**
     * @param array{enabled:bool,mode:string,follow:bool,redirect_url:string} $rule
     */
    private function resolveArchiveRedirectTarget(array $rule): string
    {
        $mode = (string) ($rule['mode'] ?? '');
        if ($mode === self::ARCHIVE_MODE_DISABLE_301_TO_HOME) {
            return esc_url_raw(home_url('/'));
        }

        if ($mode === self::ARCHIVE_MODE_DISABLE_301_TO_CUSTOM) {
            $target = isset($rule['redirect_url']) ? esc_url_raw((string) $rule['redirect_url']) : '';
            if ($target !== '') {
                return $target;
            }

            return esc_url_raw(home_url('/'));
        }

        return '';
    }

    private function render404(): void
    {
        global $wp_query;

        if ($wp_query instanceof \WP_Query) {
            $wp_query->set_404();
        }

        status_header(404);
        nocache_headers();

        if (function_exists('get_404_template')) {
            $template = get_404_template();
            if (is_string($template) && $template !== '') {
                include $template;
                exit;
            }
        }

        exit;
    }

    private function resolveAttachmentParentUrl(int $attachmentId): string
    {
        if (!function_exists('get_post')) {
            return '';
        }

        $attachment = get_post($attachmentId);
        if (!$attachment instanceof \WP_Post) {
            return '';
        }

        $parentId = isset($attachment->post_parent) ? (int) $attachment->post_parent : 0;
        if ($parentId <= 0 || $parentId === $attachmentId) {
            return '';
        }

        $parent = get_post($parentId);
        if (!$parent instanceof \WP_Post) {
            return '';
        }

        $url = get_permalink($parent);
        $url = is_string($url) ? esc_url_raw($url) : '';

        return $url;
    }

    private function currentRequestUrl(): string
    {
        $requestUri = isset($_SERVER['REQUEST_URI']) ? wp_unslash((string) $_SERVER['REQUEST_URI']) : '/';
        if ($requestUri === '') {
            $requestUri = '/';
        }

        return esc_url_raw(home_url($requestUri));
    }

    private function sameUrl(string $left, string $right): bool
    {
        return $this->normalizeUrlForCompare($left) === $this->normalizeUrlForCompare($right);
    }

    private function normalizeUrlForCompare(string $url): string
    {
        $url = trim($url);
        if ($url === '') {
            return '';
        }

        $parts = wp_parse_url($url);
        if (!is_array($parts)) {
            return rtrim($url, '/');
        }

        $scheme = isset($parts['scheme']) ? strtolower((string) $parts['scheme']) . '://' : '';
        $host = isset($parts['host']) ? strtolower((string) $parts['host']) : '';
        $port = isset($parts['port']) ? ':' . (string) ((int) $parts['port']) : '';
        $path = isset($parts['path']) ? rtrim((string) $parts['path'], '/') : '';
        $query = isset($parts['query']) ? '?' . (string) $parts['query'] : '';

        return $scheme . $host . $port . $path . $query;
    }
}
