<?php

declare(strict_types=1);

namespace FecaMailshots\Admin;

trait AdminRequestHelpers
{
    private function renderAdminUiStyles(): string
    {
        return '<style id="feca-mailshots-admin-ui">'
            . '.feca-mailshots-admin{--feca-space-1:6px;--feca-space-2:10px;--feca-space-3:14px;--feca-space-4:18px;--feca-radius:4px;}'
            . '.feca-mailshots-admin h1{margin-bottom:var(--feca-space-3);}'
            . '.feca-mailshots-admin h2{margin:var(--feca-space-4) 0 var(--feca-space-2) 0;line-height:1.3;}'
            . '.feca-mailshots-admin h3{margin:var(--feca-space-3) 0 var(--feca-space-2) 0;line-height:1.3;}'
            . '.feca-mailshots-admin p{line-height:1.45;}'
            . '.feca-mailshots-admin .feca-panel{padding:var(--feca-space-3);border:1px solid #dcdcde;background:#fff;margin-bottom:var(--feca-space-3);border-radius:var(--feca-radius);}'
            . '.feca-mailshots-admin .feca-panel-tight{margin-bottom:0;}'
            . '.feca-mailshots-admin .feca-panel-flex{flex:1;margin:0;}'
            . '.feca-mailshots-admin .feca-control-row{display:flex;gap:var(--feca-space-3);align-items:flex-end;flex-wrap:wrap;margin:var(--feca-space-2) 0;}'
            . '.feca-mailshots-admin .feca-control{display:flex;align-items:center;gap:var(--feca-space-2);min-width:260px;padding:var(--feca-space-1) 0;}'
            . '.feca-mailshots-admin .feca-control-min-140{min-width:140px;}'
            . '.feca-mailshots-admin .feca-control-min-170{min-width:170px;}'
            . '.feca-mailshots-admin .feca-control-min-220{min-width:220px;}'
            . '.feca-mailshots-admin .feca-control-min-240{min-width:240px;}'
            . '.feca-mailshots-admin .feca-control-min-260{min-width:260px;}'
            . '.feca-mailshots-admin .feca-control-min-280{min-width:280px;}'
            . '.feca-mailshots-admin .feca-control-min-320{min-width:320px;}'
            . '.feca-mailshots-admin .feca-control-min-360{min-width:360px;}'
            . '.feca-mailshots-admin .feca-control-min-380{min-width:380px;}'
            . '.feca-mailshots-admin .feca-control-min-420{min-width:420px;}'
            . '.feca-mailshots-admin .feca-minw-140{min-width:140px;}'
            . '.feca-mailshots-admin .feca-minw-170{min-width:170px;}'
            . '.feca-mailshots-admin .feca-minw-220{min-width:220px;}'
            . '.feca-mailshots-admin .feca-minw-240{min-width:240px;}'
            . '.feca-mailshots-admin .feca-control input,.feca-mailshots-admin .feca-control select,.feca-mailshots-admin .feca-control textarea{margin:0;}'
            . '.feca-mailshots-admin .feca-control label{white-space:nowrap;padding-left:var(--feca-space-1);font-weight:600;}'
            . '.feca-mailshots-admin .feca-button-row{display:flex;gap:var(--feca-space-2);align-items:center;flex-wrap:wrap;margin-top:var(--feca-space-3);margin-bottom:0;padding:0;}'
            . '.feca-mailshots-admin .feca-button-row .button{margin:0 !important;}'
            . '.feca-mailshots-admin .feca-button-danger{color:#b32d2e;border-color:#d63638;}'
            . '.feca-mailshots-admin .feca-button-danger:hover{color:#8a2424;border-color:#8a2424;background:#fff5f5;}'
            . '.feca-mailshots-admin .feca-banner{padding:var(--feca-space-2) var(--feca-space-3);border:1px solid #dcdcde;background:#fff;margin:var(--feca-space-3) 0;border-radius:var(--feca-radius);}'
            . '.feca-mailshots-admin .feca-banner-success{border-color:#8bc34a;background:#f1f8e9;}'
            . '.feca-mailshots-admin .feca-banner-error{border-color:#ef9a9a;background:#ffebee;}'
            . '.feca-mailshots-admin .feca-banner-info{border-color:#c8d7e1;background:#f6fbff;}'
            . '.feca-mailshots-admin .feca-banner-muted{background:#f9f9f9;}'
            . '.feca-mailshots-admin .feca-banner-note{margin:var(--feca-space-1) 0 0 0;}'
            . '.feca-mailshots-admin .feca-note-danger{color:#b32d2e;}'
            . '.feca-mailshots-admin .feca-form{padding:var(--feca-space-3);border:1px solid #dcdcde;background:#fff;margin-bottom:var(--feca-space-3);border-radius:var(--feca-radius);}'
            . '.feca-mailshots-admin .feca-form-max-860{max-width:860px;}'
            . '.feca-mailshots-admin .feca-form .form-table{margin-top:0;}'
            . '.feca-mailshots-admin .feca-form .form-table th,.feca-mailshots-admin .feca-form .form-table td{padding-top:12px;padding-bottom:12px;}'
            . '.feca-mailshots-admin .feca-form .form-table th{width:220px;}'
            . '.feca-mailshots-admin .feca-form .description{margin-top:var(--feca-space-1);}'
            . '.feca-mailshots-admin .feca-inline-form{display:inline;}'
            . '.feca-mailshots-admin .feca-hidden{display:none;}'
            . '.feca-mailshots-admin .feca-meta-line{margin:4px 0 var(--feca-space-3) 0;color:#6b7280;font-size:12px;}'
            . '.feca-mailshots-admin .feca-modal-overlay{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.4);z-index:9998;}'
            . '.feca-mailshots-admin .feca-modal-overlay-high{display:none;position:fixed;inset:0;background:rgba(0,0,0,0.45);z-index:9999;}'
            . '.feca-mailshots-admin .feca-modal-shell{max-width:980px;margin:30px auto;background:#fff;padding:var(--feca-space-3);max-height:88vh;overflow:auto;border-radius:var(--feca-radius);}'
            . '.feca-mailshots-admin .feca-modal-shell-wide{max-width:1100px;}'
            . '.feca-mailshots-admin .feca-modal-shell-narrow{max-width:900px;}'
            . '.feca-mailshots-admin .feca-modal-title{margin-top:0;}'
            . '.feca-mailshots-admin .feca-section-title{margin:0 0 8px 0;}'
            . '.feca-mailshots-admin .feca-modal-subtitle{margin:0 0 8px 0;}'
            . '.feca-mailshots-admin .feca-close-row{justify-content:flex-end;margin-top:0;}'
            . '.feca-mailshots-admin .feca-truncate-220{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}'
            . '.feca-mailshots-admin .feca-truncate-360{max-width:360px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}'
            . '.feca-mailshots-admin .feca-truncate-420{max-width:420px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}'
            . '.feca-mailshots-admin .feca-warning-inline{display:none;margin-top:8px;padding:8px;border-left:4px solid #dba617;background:#fff8e1;}'
            . '.feca-mailshots-admin .feca-progress-box{display:none;margin-top:var(--feca-space-2);padding:var(--feca-space-2);border:1px solid #c8d7e1;background:#f6fbff;max-width:520px;border-radius:var(--feca-radius);}'
            . '.feca-mailshots-admin .feca-builder-shell{max-width:980px;margin:30px auto;background:#fff;padding:14px;border-radius:4px;max-height:88vh;overflow:auto;}'
            . '.feca-mailshots-admin .feca-builder-grid{display:flex;gap:14px;flex-wrap:wrap;}'
            . '.feca-mailshots-admin .feca-builder-col-left{flex:1;min-width:260px;border:1px solid #dcdcde;padding:10px;}'
            . '.feca-mailshots-admin .feca-builder-col-right{flex:2;min-width:340px;border:1px solid #dcdcde;padding:10px;}'
            . '.feca-mailshots-admin .feca-builder-col-left label,.feca-mailshots-admin .feca-builder-col-right label{display:inline-flex;align-items:center;gap:6px;margin:4px 0;}'
            . '.feca-mailshots-admin .feca-builder-col-left select,.feca-mailshots-admin .feca-builder-col-right select,.feca-mailshots-admin .feca-builder-col-left input,.feca-mailshots-admin .feca-builder-col-right input{margin:6px 0;}'
            . '.feca-mailshots-admin .feca-builder-row{border:1px solid #dcdcde;padding:10px;margin-bottom:10px;}'
            . '.feca-mailshots-admin .feca-builder-row > *{margin:6px 8px 6px 0;}'
            . '.feca-mailshots-admin .feca-builder-filter-select{display:block;min-width:260px;}'
            . '.feca-mailshots-admin .feca-builder-output{margin-top:12px;border:1px solid #dcdcde;padding:10px;background:#f9f9f9;}'
            . '.feca-mailshots-admin .feca-error-text{color:#a00;margin:6px 0 0 0;}'
            . '.feca-mailshots-admin .feca-template-panel{max-width:980px;margin:30px auto;background:#fff;padding:12px;height:88vh;max-height:88vh;display:flex;flex-direction:column;overflow:hidden;}'
            . '.feca-mailshots-admin .feca-template-title{margin:0 0 8px 0;flex:0 0 auto;}'
            . '.feca-mailshots-admin .feca-template-error{display:none;margin:8px 0;padding:10px;border:1px solid #ef9a9a;background:#ffebee;}'
            . '.feca-mailshots-admin .feca-template-token-controls{margin-bottom:10px;}'
            . '.feca-mailshots-admin .feca-template-assets{display:none;margin-bottom:10px;}'
            . '.feca-mailshots-admin .feca-template-editor-wrap{flex:1 1 auto;min-height:320px;height:60vh;max-height:60vh;overflow:auto;}'
            . '.feca-mailshots-admin .feca-template-editor{height:100%;}'
            . '.feca-mailshots-admin .feca-template-actions{margin:10px 0 0 0;flex:0 0 auto;}'
            . '.feca-mailshots-admin .feca-template-panel .jodit-container{height:100% !important;}'
            . '.feca-mailshots-admin .feca-details{margin-top:8px;}'
            . '.feca-mailshots-admin .feca-details pre{margin-top:8px;}'
            . '.feca-mailshots-admin .feca-scroll-frame{border:1px solid #dcdcde;background:#fff;max-width:calc(100vw - 80px);border-radius:var(--feca-radius);margin-top:var(--feca-space-2);}'
            . '.feca-mailshots-admin .feca-scroll-pane{overflow:scroll;max-height:68vh;scrollbar-gutter:stable both-edges;}'
            . '.feca-mailshots-admin .feca-table-wide{min-width:100%;width:max-content;margin:0;border-collapse:separate;border-spacing:0;}'
            . '.feca-mailshots-admin .feca-rr-head-cell{position:sticky;top:0;z-index:2;background:#f6f7f7;white-space:normal;vertical-align:bottom;max-width:170px;padding:8px;}'
            . '.feca-mailshots-admin .feca-rr-head-wrap{line-height:1.2;}'
            . '.feca-mailshots-admin .feca-rr-head-prefix{display:block;font-size:11px;color:#646970;word-break:break-word;}'
            . '.feca-mailshots-admin .feca-rr-head-field{display:block;font-size:12px;font-weight:600;word-break:break-word;}'
            . '.feca-mailshots-admin .feca-rr-data-cell{white-space:nowrap;max-width:20ch;width:20ch;min-width:20ch;overflow:hidden;text-overflow:ellipsis;}'
            . '.feca-mailshots-admin .feca-rr-row-selected td{background:#e8f4ff;}'
            . '.feca-mailshots-admin .feca-preview-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;}'
            . '.feca-mailshots-admin .feca-preview-iframe{width:100%;height:320px;border:1px solid #dcdcde;background:#fff;}'
            . '@media (max-width: 782px){'
            . '.feca-mailshots-admin .feca-control-row{display:block;}'
            . '.feca-mailshots-admin .feca-control{min-width:0;width:100%;}'
            . '}'
            . '</style>';
    }

    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 class="feca-banner feca-banner-error">';
        $html .= '<strong>Please fix the following:</strong><ul>';
        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>';
    }
}
