<?php

declare(strict_types=1);

namespace FecaMailshots\Admin;

trait AdminRequestHelpers
{
    private function requestMethod(): string
    {
        $method = isset($_SERVER['REQUEST_METHOD']) ? (string) $_SERVER['REQUEST_METHOD'] : 'GET';
        return strtoupper(trim($method));
    }

    private function requestString(string $name, string $default = ''): string
    {
        return (string) ($this->wp->requestParam($name, $default) ?? $default);
    }

    private function requestInt(string $name, int $default = 0): int
    {
        return (int) ($this->wp->requestParam($name, (string) $default) ?? (string) $default);
    }

    private function requestOptionalInt(string $name = 'id'): ?int
    {
        $raw = trim($this->requestString($name, ''));
        return $raw === '' ? null : (int) $raw;
    }

    private function enforceCapabilityOrJson(string $capability): bool
    {
        if ($this->wp->currentUserCan($capability)) {
            return true;
        }
        $this->wp->sendJson(['ok' => false, 'error' => 'Permission denied'], 403);
        return false;
    }

    private function enforcePostOrJson(): bool
    {
        if ($this->requestMethod() === 'POST') {
            return true;
        }
        $this->wp->sendJson(['ok' => false, 'error' => 'Method not allowed. Use POST.'], 405);
        return false;
    }

    private function enforceNonceOrJson(string $nonceAction): bool
    {
        $nonce = trim($this->requestString('_wpnonce', ''));
        if ($nonce !== '' && $this->wp->verifyNonce($nonce, $nonceAction)) {
            return true;
        }
        $this->wp->sendJson(['ok' => false, 'error' => 'Invalid or missing nonce.'], 403);
        return false;
    }

    private function enforceMutationGuardOrJson(string $capability, string $nonceAction): bool
    {
        if ($this->mutationGuardValid($capability, $nonceAction)) {
            return true;
        }
        if (!$this->wp->currentUserCan($capability)) {
            $this->wp->sendJson(['ok' => false, 'error' => 'Permission denied'], 403);
            return false;
        }
        if ($this->requestMethod() !== 'POST') {
            $this->wp->sendJson(['ok' => false, 'error' => 'Method not allowed. Use POST.'], 405);
            return false;
        }
        $this->wp->sendJson(['ok' => false, 'error' => 'Invalid or missing nonce.'], 403);
        return false;
    }

    private function mutationGuardValid(string $capability, string $nonceAction): bool
    {
        if (!$this->wp->currentUserCan($capability)) {
            return false;
        }
        if ($this->requestMethod() !== 'POST') {
            return false;
        }
        $nonce = trim($this->requestString('_wpnonce', ''));
        if ($nonce === '') {
            return false;
        }
        return $this->wp->verifyNonce($nonce, $nonceAction);
    }

    private function hiddenNonceField(string $nonceAction): string
    {
        return '<input type="hidden" name="_wpnonce" value="'
            . htmlspecialchars($this->wp->createNonce($nonceAction), ENT_QUOTES)
            . '">';
    }

    private function redirectTo(string $url): void
    {
        if (!headers_sent()) {
            header('Location: ' . $url, true, 302);
            exit;
        }
    }

    private function redirectToAdminPage(string $page, ?int $editId = null): void
    {
        $url = $this->wp->adminUrl('admin.php?page=' . $page . ($editId !== null && $editId > 0 ? '&edit_id=' . $editId : ''));
        $this->redirectTo($url);
    }

    private function uploadedFileToBase64(string $fileField): ?string
    {
        if (!isset($_FILES[$fileField]) || !is_array($_FILES[$fileField])) {
            return null;
        }

        $error = (int) ($_FILES[$fileField]['error'] ?? UPLOAD_ERR_NO_FILE);
        if ($error === UPLOAD_ERR_NO_FILE) {
            return null;
        }
        if ($error !== UPLOAD_ERR_OK) {
            throw new \RuntimeException('File upload failed for "' . $fileField . '" with error code ' . $error . '.');
        }

        $tmp = (string) ($_FILES[$fileField]['tmp_name'] ?? '');
        if ($tmp === '' || !is_file($tmp)) {
            throw new \RuntimeException('Uploaded file for "' . $fileField . '" is not available.');
        }

        $bytes = file_get_contents($tmp);
        if ($bytes === false) {
            throw new \RuntimeException('Unable to read uploaded file for "' . $fileField . '".');
        }

        return base64_encode($bytes);
    }

    /** @return array<string,mixed>|null */
    private function consumeOptionArray(string $optionKey): ?array
    {
        $raw = $this->wp->getOption($optionKey, null);
        $this->wp->deleteOption($optionKey);
        return is_array($raw) ? $raw : null;
    }

    /**
     * Persists result banner payload and draft state in a consistent way:
     * - always stores result payload
     * - clears draft on success
     * - stores draft on failure when provided
     * @param array<string,mixed> $result
     * @param array<string,mixed>|null $draft
     */
    private function persistResultAndDraft(string $resultOptionKey, string $draftOptionKey, array $result, ?array $draft): void
    {
        $this->wp->updateOption($resultOptionKey, $result);
        if (!empty($result['ok'])) {
            $this->wp->deleteOption($draftOptionKey);
            return;
        }
        if (is_array($draft)) {
            $this->wp->updateOption($draftOptionKey, $draft);
        }
    }

    /** @param array<string,mixed>|null $result */
    private function modalErrorHtml(?array $result): string
    {
        if ($result === null || !empty($result['ok']) || empty($result['errors']) || !is_array($result['errors'])) {
            return '';
        }
        $html = '<div style="margin:8px 0;padding:10px;border:1px solid #ef9a9a;background:#ffebee;">';
        $html .= '<strong>Please fix the following:</strong><ul style="margin:8px 0 0 18px;">';
        foreach ($result['errors'] as $error) {
            $html .= '<li>' . htmlspecialchars((string) $error) . '</li>';
        }
        $html .= '</ul></div>';
        return $html;
    }

    /**
     * @param callable():array<string,mixed> $list
     * @param callable():array<string,mixed> $save
     * @param callable():array<string,mixed> $delete
     */
    private function handleBasicCrudApi(string $op, callable $list, callable $save, callable $delete): void
    {
        if ($op === 'list') {
            $this->wp->sendJson($list());
            return;
        }
        if ($op === 'save') {
            $this->wp->sendJson($save());
            return;
        }
        if ($op === 'delete') {
            $this->wp->sendJson($delete());
            return;
        }
        $this->wp->sendJson(['ok' => false, 'error' => 'Unknown operation'], 400);
    }

    /**
     * @param list<string> $fieldIds
     */
    private function modalEditorScript(
        string $modalId,
        string $openButtonId,
        string $closeButtonId,
        string $formId,
        string $editorIdFieldId,
        array $fieldIds,
        bool $hasEdit,
        bool $hasDraft,
        bool $hasResult,
        bool $resultOk,
        ?string $defaultSelectFieldId = null,
        ?string $defaultSelectValue = null,
        ?string $uploadFieldId = null,
        ?string $fileNameFieldId = null
    ): string {
        $cfg = [
            'modalId' => $modalId,
            'openButtonId' => $openButtonId,
            'closeButtonId' => $closeButtonId,
            'formId' => $formId,
            'editorIdFieldId' => $editorIdFieldId,
            'fieldIds' => array_values($fieldIds),
            'hasEdit' => $hasEdit,
            'hasDraft' => $hasDraft,
            'hasResult' => $hasResult,
            'resultOk' => $resultOk,
            'defaultSelectFieldId' => $defaultSelectFieldId,
            'defaultSelectValue' => $defaultSelectValue,
            'uploadFieldId' => $uploadFieldId,
            'fileNameFieldId' => $fileNameFieldId,
        ];
        return '<script>(function(){'
            . 'var cfg=' . json_encode($cfg, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ';'
            . 'var modal=document.getElementById(cfg.modalId);'
            . 'var openBtn=document.getElementById(cfg.openButtonId);'
            . 'var closeBtn=document.getElementById(cfg.closeButtonId);'
            . 'var form=document.getElementById(cfg.formId);'
            . 'var isDirty=false;'
            . 'function confirmDiscard(){if(!isDirty){return true;}return window.confirm("You have unsaved changes. Close without saving?");}'
            . 'if(form){form.querySelectorAll("input,select,textarea").forEach(function(el){el.addEventListener("input",function(){isDirty=true;});el.addEventListener("change",function(){isDirty=true;});});form.addEventListener("submit",function(){isDirty=false;});}'
            . 'if(cfg.uploadFieldId&&cfg.fileNameFieldId){var upload=document.getElementById(cfg.uploadFieldId);var fileNameInput=document.getElementById(cfg.fileNameFieldId);if(upload&&fileNameInput){upload.addEventListener("change",function(){var file=(upload.files&&upload.files[0])?upload.files[0]:null;if(!file||String(file.name||"").trim()===""){return;}if(String(fileNameInput.value||"").trim()===""){fileNameInput.value=String(file.name||"");isDirty=true;}});}}'
            . 'if(openBtn&&modal){openBtn.addEventListener("click",function(ev){if(ev&&typeof ev.preventDefault==="function"){ev.preventDefault();}var id=document.getElementById(cfg.editorIdFieldId);if(id){id.value="";}(cfg.fieldIds||[]).forEach(function(x){var el=document.getElementById(String(x||""));if(el){el.value="";}});if(cfg.defaultSelectFieldId){var j=document.getElementById(cfg.defaultSelectFieldId);if(j){j.value=String(cfg.defaultSelectValue||"");}}isDirty=false;modal.style.display="block";});}'
            . 'if(closeBtn&&modal){closeBtn.addEventListener("click",function(ev){if(ev&&typeof ev.preventDefault==="function"){ev.preventDefault();}if(!confirmDiscard()){return;}modal.style.display="none";});}'
            . 'if((cfg.hasEdit||cfg.hasDraft)&&modal&&(!cfg.hasResult||!cfg.resultOk)){modal.style.display="block";isDirty=false;}'
            . '})();</script>';
    }
}
