<?php

namespace App\Services;

use App\Models\PromptLandingPage;
use App\Support\HtmlDocument;
use App\Support\PromptLandingPageLeadPayload;
use DOMDocument;
use DOMElement;

class PublicPromptLandingPageDocument
{
    public function render(PromptLandingPage $page, ?string $pageLoadId = null): string
    {
        $action = $page->leadCaptureUrl();

        abort_unless(is_string($action), 404);

        return $this->withLeadCapture(
            $page->renderedDocument(),
            $action,
            $page->returnCtaLabel(),
            $pageLoadId,
            $page->isAppDownload(),
            $page->downloadClickoutUrl(),
        );
    }

    public function withLeadCapture(
        string $html,
        string $action,
        string $returnLabel = 'the website',
        ?string $pageLoadId = null,
        bool $trackDownloadCtas = false,
        ?string $downloadClickoutUrl = null,
    ): string {
        // Script bodies and SVG names survive this round trip; see HtmlDocument.
        if (! $document = HtmlDocument::from($html, 'public prompt landing page')) {
            return $html;
        }

        $dom = $document->dom();

        foreach (iterator_to_array($dom->getElementsByTagName('form')) as $form) {
            if (! $form instanceof DOMElement) {
                continue;
            }

            $form->setAttribute('action', $action);
            $form->setAttribute('method', 'post');
            $form->setAttribute('data-admedia-lead', '1');
            $form->setAttribute('novalidate', 'novalidate');
            $form->removeAttribute('target');
            $this->enforceLeadFieldValidation($form);
        }

        if ($trackDownloadCtas) {
            $this->removeContinueSignupCtas($dom);
            $this->markDownloadCtas($dom);
        } else {
            $this->rewriteHomeCtas($dom, $returnLabel);
        }

        $this->appendCaptureScript($dom, $pageLoadId, $trackDownloadCtas, $downloadClickoutUrl);

        return $document->html();
    }

    private function appendCaptureScript(
        DOMDocument $dom,
        ?string $pageLoadId = null,
        bool $trackDownloadCtas = false,
        ?string $downloadClickoutUrl = null,
    ): void {
        $script = $dom->createElement('script');
        $script->appendChild($dom->createTextNode($this->captureScript($pageLoadId, $trackDownloadCtas, $downloadClickoutUrl)));

        $bodies = $dom->getElementsByTagName('body');

        if ($bodies->length > 0) {
            $bodies->item(0)?->appendChild($script);
        } else {
            $dom->appendChild($script);
        }
    }

    private function captureScript(
        ?string $pageLoadId = null,
        bool $trackDownloadCtas = false,
        ?string $downloadClickoutUrl = null,
    ): string {
        $pageLoadIdJson = json_encode($pageLoadId, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);

        if (! is_string($pageLoadIdJson)) {
            $pageLoadIdJson = 'null';
        }

        $downloadClickoutUrlJson = json_encode($downloadClickoutUrl, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT | JSON_UNESCAPED_SLASHES);

        if (! is_string($downloadClickoutUrlJson)) {
            $downloadClickoutUrlJson = 'null';
        }

        $downloadTracking = $trackDownloadCtas ? $this->downloadCtaScript() : '';

        return '(function () { var pageLoadId = '.$pageLoadIdJson.'; var downloadClickoutUrl = '.$downloadClickoutUrlJson.';'."\n"
            .<<<'JS'
document.addEventListener('submit', function (event) {
    var form = event.target;
    if (!form || !form.getAttribute || form.getAttribute('data-admedia-lead') !== '1') {
        return;
    }

    event.preventDefault();

    var error = form.querySelector('[data-admedia-lead-error]');
    if (!error) {
        error = document.createElement('p');
        error.setAttribute('data-admedia-lead-error', '1');
        error.setAttribute('role', 'alert');
        error.style.color = '#b91c1c';
        error.style.margin = '0 0 12px';
        form.insertBefore(error, form.firstChild);
    }
    error.hidden = true;
    error.textContent = '';

    function showError(message) {
        error.textContent = message;
        error.hidden = false;
    }

    function fieldName(field) {
        return String(field.getAttribute('name') || field.id || '').toLowerCase().replace(/[\s-]+/g, '_');
    }

    function isEmailField(field) {
        return field.type === 'email' || /(^|_)e-?mail(_|$)/.test(fieldName(field)) || /email/.test(fieldName(field));
    }

    function isPhoneField(field) {
        return field.type === 'tel' || /(^|_)(phone|tel|mobile|cellphone|cell)(_|$)/.test(fieldName(field));
    }

    var fields = form.querySelectorAll('input, textarea, select');
    var filled = false;

    for (var i = 0; i < fields.length; i++) {
        var field = fields[i];
        var type = (field.getAttribute('type') || (field.tagName === 'TEXTAREA' ? 'textarea' : (field.tagName === 'SELECT' ? 'select' : 'text'))).toLowerCase();

        if (['hidden', 'submit', 'button', 'reset', 'checkbox', 'radio', 'file', 'image'].indexOf(type) !== -1) {
            continue;
        }

        var value = String(field.value || '').trim();

        if (value !== '') {
            filled = true;
        }

        if (field.required && value === '') {
            showError('Please fill in the required fields.');
            field.focus();
            return;
        }

        if (isEmailField(field) && (value === '' || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))) {
            showError('Please enter a valid email address.');
            field.focus();
            return;
        }

        if (isPhoneField(field) && (field.required || value !== '')) {
            var digits = value.replace(/\D+/g, '');
            if (digits.length < 7 || digits.length > 15) {
                showError('Please enter a valid phone number.');
                field.focus();
                return;
            }
        }
    }

    if (!filled) {
        showError('Please fill in the required fields.');
        return;
    }

    var body = new FormData(form);
    body.delete('page_load_id');
    if (pageLoadId) {
        body.append('page_load_id', pageLoadId);
    }

    fetch(form.action, {
        method: 'POST',
        body: body,
        headers: {
            'Accept': 'application/json',
            'X-Requested-With': 'XMLHttpRequest'
        }
    }).then(function (response) {
        return response.json().catch(function () { return {}; }).then(function (json) {
            return { ok: response.ok, json: json };
        });
    }).then(function (result) {
        if (!result.ok) {
            var errors = result.json.errors || {};
            var first = Object.keys(errors)[0];
            showError((first && errors[first][0]) || result.json.message || 'Could not send those details. Please try again.');
            return;
        }

        form.innerHTML = '<p>Thanks — we received your details.</p>';
        if (typeof fbq === 'function') {
            fbq('track', 'Lead');
        }
        if (typeof gtag_report_conversion === 'function') {
            gtag_report_conversion();
        }
    }).catch(function () {
        showError('Could not send those details. Please try again.');
    });
}, true);

document.addEventListener('click', function (event) {
    var el = event.target.closest('[data-admedia-home="1"]');
    if (!el) {
        el = event.target.closest('a, button');
        if (!el) {
            return;
        }

        var text = (el.textContent || '').replace(/\s+/g, ' ').trim().toLowerCase();
        if (!/^return to\b/.test(text)) {
            return;
        }

        if (el.closest('form[data-admedia-lead="1"]')) {
            var type = (el.getAttribute('type') || (el.tagName === 'BUTTON' ? 'submit' : '')).toLowerCase();
            if (type === 'submit' || (el.tagName === 'BUTTON' && type !== 'button')) {
                return;
            }
        }
    }

    event.preventDefault();
    window.location.assign('/');
}, true);
JS
            .$downloadTracking
            ."\n})();\n";
    }

    private function downloadCtaScript(): string
    {
        return <<<'JS'

function recordDownloadClick(href) {
    if (!downloadClickoutUrl || !pageLoadId) {
        return;
    }

    var body = new URLSearchParams();
    body.set('page_load_id', pageLoadId);
    body.set('destination_url', href || '');
    var payload = body.toString();
    var queued = false;

    if (navigator.sendBeacon) {
        queued = navigator.sendBeacon(downloadClickoutUrl, new Blob([payload], { type: 'application/x-www-form-urlencoded' }));
    }

    if (!queued && typeof fetch === 'function') {
        fetch(downloadClickoutUrl, {
            method: 'POST',
            body: payload,
            headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
            keepalive: true,
            credentials: 'omit'
        });
    }
}

document.addEventListener('click', function (event) {
    var el = event.target.closest('[data-admedia-download="1"]');
    if (!el) {
        return;
    }

    if (el.closest('form[data-admedia-lead="1"]')) {
        return;
    }

    var href = el.getAttribute('href') || '';
    recordDownloadClick(href);
    if (href === '' || href.charAt(0) === '#') {
        if (typeof fbq === 'function') {
            fbq('track', 'Lead');
        }
        if (typeof gtag_report_conversion === 'function') {
            gtag_report_conversion();
        }
        return;
    }

    event.preventDefault();
    if (typeof fbq === 'function') {
        fbq('track', 'Lead');
    }
    if (typeof gtag_report_conversion === 'function') {
        gtag_report_conversion(href);
        return;
    }
    window.location.assign(href);
}, true);
JS;
    }

    private function enforceLeadFieldValidation(DOMElement $form): void
    {
        $tags = ['input', 'textarea', 'select'];

        foreach ($tags as $tag) {
            foreach (iterator_to_array($form->getElementsByTagName($tag)) as $field) {
                if (! $field instanceof DOMElement) {
                    continue;
                }

                $this->applyLeadFieldRules($field);
            }
        }
    }

    private function applyLeadFieldRules(DOMElement $field): void
    {
        $type = strtolower($field->getAttribute('type') ?: (strtolower($field->tagName) === 'textarea' ? 'textarea' : (strtolower($field->tagName) === 'select' ? 'select' : 'text')));

        if (in_array($type, ['hidden', 'submit', 'button', 'reset', 'checkbox', 'radio', 'file', 'image'], true)) {
            return;
        }

        $name = $field->getAttribute('name') !== '' ? $field->getAttribute('name') : $field->getAttribute('id');

        if ($name === '') {
            return;
        }

        if (PromptLandingPageLeadPayload::looksLikeEmailField($name, $type)) {
            $field->setAttribute('type', 'email');
            $field->setAttribute('required', 'required');

            return;
        }

        if (PromptLandingPageLeadPayload::looksLikePhoneField($name, $type)) {
            $field->setAttribute('type', 'tel');
            $field->setAttribute('required', 'required');

            return;
        }

        if (PromptLandingPageLeadPayload::looksLikeNameField($name) || ! PromptLandingPageLeadPayload::isOptionalLeadField($name)) {
            $field->setAttribute('required', 'required');
        }
    }

    private function removeContinueSignupCtas(DOMDocument $dom): void
    {
        foreach ($this->ctaElements($dom) as $element) {
            if ($this->looksLikeRemovedCtaText($this->normalizedText($element))) {
                $element->parentNode?->removeChild($element);
            }
        }
    }

    private function markDownloadCtas(DOMDocument $dom): void
    {
        foreach ($this->ctaElements($dom) as $element) {
            if (strtolower($element->tagName) === 'button' && $this->isFormSubmitButton($element)) {
                continue;
            }

            if ($element->getAttribute('data-admedia-home') === '1') {
                continue;
            }

            if ($element->getAttribute('data-admedia-download') === '1' || $this->looksLikeDownloadCtaText($this->normalizedText($element))) {
                $element->setAttribute('data-admedia-download', '1');
            }
        }
    }

    /**
     * @return list<DOMElement>
     */
    private function ctaElements(DOMDocument $dom): array
    {
        $elements = [];

        foreach ([...iterator_to_array($dom->getElementsByTagName('a')), ...iterator_to_array($dom->getElementsByTagName('button'))] as $element) {
            if ($element instanceof DOMElement) {
                $elements[] = $element;
            }
        }

        return $elements;
    }

    private function looksLikeDownloadCtaText(string $text): bool
    {
        return (bool) preg_match('/\bdownload\b|get the app|app store|google play|play store/', $text);
    }

    private function rewriteHomeCtas(DOMDocument $dom, string $returnLabel): void
    {
        $returnCtas = [];
        $extraCtas = [];

        foreach ([...iterator_to_array($dom->getElementsByTagName('a')), ...iterator_to_array($dom->getElementsByTagName('button'))] as $element) {
            if (! $element instanceof DOMElement) {
                continue;
            }

            if (strtolower($element->tagName) === 'button' && $this->isFormSubmitButton($element)) {
                continue;
            }

            $text = $this->normalizedText($element);

            if ($this->looksLikeReturnCtaText($text)) {
                $returnCtas[] = $element;

                continue;
            }

            if ($this->looksLikeRemovedCtaText($text)) {
                $extraCtas[] = $element;
            }
        }

        foreach ($returnCtas as $element) {
            $this->sendCtaHome($element);
        }

        $converted = null;

        if ($returnCtas === [] && $extraCtas !== []) {
            $converted = array_shift($extraCtas);
            $this->relabelAsReturnCta($converted, $returnLabel);
            $this->sendCtaHome($converted);
        }

        foreach ($extraCtas as $element) {
            $element->parentNode?->removeChild($element);
        }

        if ($returnCtas === [] && $converted === null) {
            $this->appendReturnCta($dom, $returnLabel);
        }
    }

    private function sendCtaHome(DOMElement $element): void
    {
        if (strtolower($element->tagName) === 'a') {
            $element->setAttribute('href', '/');
            $element->removeAttribute('target');
        } else {
            $element->setAttribute('type', 'button');
        }

        $element->setAttribute('data-admedia-home', '1');
    }

    private function relabelAsReturnCta(DOMElement $element, string $returnLabel): void
    {
        while ($element->firstChild !== null) {
            $element->removeChild($element->firstChild);
        }

        $document = $element->ownerDocument;

        if (! $document instanceof DOMDocument) {
            return;
        }

        $element->appendChild($document->createTextNode('Return to '.$returnLabel));
    }

    private function appendReturnCta(DOMDocument $dom, string $returnLabel): void
    {
        $anchor = $dom->createElement('a');
        $anchor->setAttribute('href', '/');
        $anchor->setAttribute('data-admedia-home', '1');
        $anchor->appendChild($dom->createTextNode('Return to '.$returnLabel));

        $paragraph = $dom->createElement('p');
        $paragraph->appendChild($anchor);

        $bodies = $dom->getElementsByTagName('body');

        if ($bodies->length > 0) {
            $bodies->item(0)?->appendChild($paragraph);
        } else {
            $dom->appendChild($paragraph);
        }
    }

    private function looksLikeReturnCtaText(string $text): bool
    {
        return (bool) preg_match('/^return to\b/', $text);
    }

    private function looksLikeRemovedCtaText(string $text): bool
    {
        return (bool) preg_match('/^continue$|continue to|secure signup|verified signup|\bcheckout\b/', $text);
    }

    private function isFormSubmitButton(DOMElement $button): bool
    {
        $type = strtolower($button->getAttribute('type'));

        if ($type === 'button' || $type === 'reset') {
            return false;
        }

        $parent = $button->parentNode;

        while ($parent instanceof DOMElement) {
            if (strtolower($parent->tagName) === 'form') {
                return true;
            }

            $parent = $parent->parentNode;
        }

        return false;
    }

    private function normalizedText(DOMElement $element): string
    {
        return strtolower(trim((string) preg_replace('/\s+/', ' ', $element->textContent)));
    }
}
