'use strict';

import { Controller } from '@hotwired/stimulus';

export function createRetryableLoader(factory) {
    let loader = null;

    return () => {
        if (loader === null) {
            loader = Promise.resolve()
                .then(factory)
                .catch((error) => {
                    loader = null;

                    throw error;
                });
        }

        return loader;
    };
}

const loadAltcha = createRetryableLoader(() => Promise.all([
            import('altcha'),
            import('altcha/i18n/de'),
            import('altcha/i18n/es-es'),
            import('altcha/i18n/fr-fr'),
            import('altcha/i18n/it'),
            import('altcha/i18n/nl'),
        ]).then(() => {
            globalThis.$altcha.algorithms.set('ARGON2ID', () => new Worker(
                new URL('altcha/workers/argon2id', import.meta.url),
                { type: 'module', name: 'altcha-argon2id' },
            ));
        }));

export default class extends Controller {
    static targets = ['honeypot', 'widget'];

    static values = {
        finalLiveActions: Array,
        honeypotInputId: String,
        inputId: String,
        minimumDuration: { type: Number, default: 500 },
    };

    connect() {
        this.connectionToken = Symbol('altcha-connection');
        this.submissionPending = false;
        this.verifiedListener = (event) => this.handleVerified(event);
        this.stateListener = (event) => this.handleState(event);
        this.expiredListener = () => this.writePayload('');
        this.errorListener = () => this.reset();
        this.submitListener = (event) => this.prepareSubmission(event);
        this.formResetListener = () => this.reset();
        this.liveRequestStartedListener = (requestConfig) => this.handleLiveRequestStarted(requestConfig);
        this.liveRenderFinishedListener = () => this.handleLiveResponse();
        this.liveResponseErrorListener = () => this.handleLiveResponse();
        this.formElement = this.element.closest('form');
        this.element.setAttribute('data-loading', 'addAttribute(inert)');
        this.formElement?.addEventListener('submit', this.submitListener, { capture: true });
        this.formElement?.addEventListener('reset', this.formResetListener);
        this.attachLiveComponentHooks();

        this.writePayload('');
        this.syncHoneypot();
        if (!this.hasWidgetTarget) {
            return;
        }

        this.widgetTarget.addEventListener('verified', this.verifiedListener);
        this.widgetTarget.addEventListener('statechange', this.stateListener);
        this.widgetTarget.addEventListener('expired', this.expiredListener);
        this.widgetTarget.addEventListener('error', this.errorListener);
        this.configureWidget(this.connectionToken);
    }

    disconnect() {
        this.connectionToken = null;
        this.detachLiveComponentHooks();
        this.formElement?.removeEventListener('submit', this.submitListener, { capture: true });
        this.formElement?.removeEventListener('reset', this.formResetListener);
        if (this.hasWidgetTarget) {
            this.widgetTarget.removeEventListener('verified', this.verifiedListener);
            this.widgetTarget.removeEventListener('statechange', this.stateListener);
            this.widgetTarget.removeEventListener('expired', this.expiredListener);
            this.widgetTarget.removeEventListener('error', this.errorListener);
            this.widgetTarget.reset?.();
        }
        this.writePayload('');
        this.formElement = null;
        this.submissionPending = false;
    }

    async configureWidget(connectionToken) {
        try {
            await loadAltcha();
            await customElements.whenDefined('altcha-widget');
        } catch {
            if (this.connectionToken === connectionToken) {
                this.writePayload('');
            }

            return;
        }

        if (
            this.connectionToken !== connectionToken
            || !this.hasWidgetTarget
            || !this.widgetTarget.isConnected
            || typeof this.widgetTarget.configure !== 'function'
        ) {
            return;
        }

        this.widgetTarget.configure({
            challenge: this.widgetTarget.dataset.cpcreationAltchaChallengeUrl,
            humanInteractionSignature: false,
            language: this.language(),
            minDuration: this.minimumDurationValue,
            workers: 2,
        });
    }

    reset(event = null) {
        if (!this.targetsThisForm(event)) {
            return;
        }

        this.submissionPending = false;
        this.writePayload('');
        this.syncHoneypot();
        if (this.hasWidgetTarget) {
            this.widgetTarget.reset?.();
            this.configureWidget(this.connectionToken);
        }
    }

    prepareSubmission(event) {
        this.syncHoneypot();
        if (this.submissionPending) {
            event.preventDefault();
            event.stopImmediatePropagation();

            return;
        }

        this.sanitizeWidgetLoadingAttributes();
    }

    handleLiveRequestStarted(requestConfig) {
        this.sanitizeWidgetLoadingAttributes();

        const actions = Array.isArray(requestConfig?.actions) ? requestConfig.actions : [];
        const finalLiveActions = new Set(
            Array.isArray(this.finalLiveActionsValue) ? this.finalLiveActionsValue : [],
        );
        this.submissionPending = actions.some((action) => (
            typeof action?.name === 'string' && finalLiveActions.has(action.name)
        ));
    }

    sanitizeWidgetLoadingAttributes() {
        if (!this.hasWidgetTarget) {
            return;
        }

        for (const element of this.widgetTarget.querySelectorAll('[data-loading]')) {
            if (['true', 'false'].includes(element.getAttribute('data-loading'))) {
                element.setAttribute('data-loading', 'addAttribute(data-altcha-live-loading)');
            }
        }
    }

    attachLiveComponentHooks() {
        const liveComponent = this.element.closest('[data-controller~="live"]')?.__component;
        if (!liveComponent || typeof liveComponent.on !== 'function') {
            return;
        }

        this.liveComponent = liveComponent;
        this.liveComponent.on('request:started', this.liveRequestStartedListener);
        this.liveComponent.on('render:finished', this.liveRenderFinishedListener);
        this.liveComponent.on('response:error', this.liveResponseErrorListener);
    }

    detachLiveComponentHooks() {
        if (!this.liveComponent || typeof this.liveComponent.off !== 'function') {
            this.liveComponent = null;

            return;
        }

        this.liveComponent.off('request:started', this.liveRequestStartedListener);
        this.liveComponent.off('render:finished', this.liveRenderFinishedListener);
        this.liveComponent.off('response:error', this.liveResponseErrorListener);
        this.liveComponent = null;
    }

    handleLiveResponse() {
        if (!this.submissionPending) {
            return;
        }

        this.reset();
    }

    handleVerified(event) {
        const payload = event?.detail?.payload;
        this.writePayload(typeof payload === 'string' ? payload : '');
    }

    handleState(event) {
        const state = event?.detail?.state;
        const payload = event?.detail?.payload;
        this.writePayload(state === 'verified' && typeof payload === 'string' ? payload : '');
    }

    writePayload(payload) {
        if (!this.hasInputIdValue) {
            return;
        }

        const input = document.getElementById(this.inputIdValue);
        if (!(input instanceof HTMLInputElement) || input.value === payload) {
            return;
        }

        input.value = payload;
        input.dispatchEvent(new Event('input', { bubbles: true }));
        input.dispatchEvent(new Event('change', { bubbles: true }));
    }

    syncHoneypot() {
        const value = this.hasHoneypotTarget && typeof this.honeypotTarget.value === 'string'
            ? this.honeypotTarget.value
            : 'uninitialized';

        this.writeHoneypotMirror(value);
    }

    writeHoneypotMirror(value) {
        if (!this.hasHoneypotInputIdValue) {
            return;
        }

        const input = document.getElementById(this.honeypotInputIdValue);
        if (!(input instanceof HTMLInputElement)) {
            return;
        }

        if (input.value !== value) {
            input.value = value;
        }
        input.dispatchEvent(new Event('input', { bubbles: true }));
        input.dispatchEvent(new Event('change', { bubbles: true }));
    }

    language() {
        const language = document.documentElement.lang?.toLowerCase().split('-')[0];

        return ['de', 'en', 'es', 'fr', 'it', 'nl'].includes(language) ? language : 'fr';
    }

    targetsThisForm(event) {
        if (!event || event.type === 'turbo:before-cache' || event.type === 'reset') {
            return true;
        }

        const explicitForm = event.detail?.form;
        if (explicitForm) {
            return explicitForm === this.formElement;
        }

        return typeof event.target?.contains === 'function' && event.target.contains(this.element);
    }
}
