'use strict';

import {Controller} from '@hotwired/stimulus';
// @ts-ignore
import {Component, getComponent} from '@symfony/ux-live-component';

export default class extends Controller {
    static targets = ['decision', 'submit', 'blockerOk', 'blockerOpen'];

    declare readonly decisionTargets: HTMLElement[];
    declare readonly submitTargets: HTMLButtonElement[];
    declare readonly blockerOkTargets: HTMLElement[];
    declare readonly blockerOpenTargets: HTMLElement[];

    private comp: Component | null = null;

    async initialize(): Promise<void> {
        try {
            this.comp = await getComponent(this.element as HTMLElement);
        } catch {
            this.comp = null;
        }
    }

    connect(): void {
        this.sync();
    }

    /**
     * Change handler for all decision controls. The input carries:
     *   data-model-name — writable prop path, e.g. `participantDecisions[«uuid»]`
     *   data-decide     — 'switch' (checked ⇒ '1', unchecked ⇒ '0')
     *                     'check'  (checked ⇒ '1', unchecked ⇒ null)
     *                     'radio'  (the input's own value)
     */
    decide(event: Event): void {
        const input = event.currentTarget as HTMLInputElement;
        const model = input.dataset.modelName;
        if (!model) {
            return;
        }

        let value: string | null;
        switch (input.dataset.decide) {
            case 'switch':
                value = input.checked ? '1' : '0';
                break;
            case 'check':
                value = input.checked ? '1' : null;
                break;
            default:
                value = input.value;
        }

        try {
            (this.comp as any)?.set(model, value, false);
        } catch {
            // Model not writable / component gone — server re-validates on submit.
        }

        this.reflectSeg(input);
        this.sync();
    }

    /**
     * Mirror a radio group's state onto its pill labels and hide the "please
     * decide" caption. The on/off utility classes are declared on the labels
     * (data-on-classes / data-off-classes) and swapped here — plain class
     * toggling is the only styling path whose invalidation every browser gets
     * right (CSS :has()/attribute-variant invalidation proved flaky).
     */
    private reflectSeg(input: HTMLInputElement): void {
        const seg = input.closest('[data-seg]');
        if (!seg) {
            return;
        }

        seg.querySelectorAll('label').forEach((label) => {
            const on = (label.dataset.onClasses ?? '').split(/\s+/).filter(Boolean);
            const off = (label.dataset.offClasses ?? '').split(/\s+/).filter(Boolean);
            const icon = label.querySelector('svg');
            if (label.querySelector<HTMLInputElement>('input')?.checked) {
                label.setAttribute('data-on', '');
                label.classList.add(...on);
                label.classList.remove(...off);
                icon?.classList.remove('hidden');
            } else {
                label.removeAttribute('data-on');
                label.classList.remove(...on);
                label.classList.add(...off);
                icon?.classList.add('hidden');
            }
            // Re-insert the same node: forces a fresh style resolution on
            // engines whose recalc goes stale for morphed radio labels
            // (observed in embedded Chromium). No-op otherwise — same element,
            // listeners and state preserved.
            label.replaceWith(label);
        });

        seg.parentElement?.querySelector('[data-caption]')?.classList.add('hidden');
    }

    /** Recompute the submit gate from the DOM state of all decisions. */
    sync(): void {
        let ok = true;
        for (const el of this.decisionTargets) {
            const mode = el.dataset.mode;
            if ('compulsory' === mode) {
                const box = el.querySelector<HTMLInputElement>('input[type="checkbox"]');
                if (box && !box.checked) {
                    ok = false;
                }
            } else if ('force_decision' === mode) {
                if (!el.querySelector('input[type="radio"]:checked')) {
                    ok = false;
                }
            } else if ('extra' === mode) {
                // Offer-specific question. Only the ones the admin marked as
                // required block the submit — an optional question may stay
                // empty (data-required is rendered by _extras.html.twig, and
                // the server re-checks the same rule on submit).
                if ('1' !== el.dataset.required) {
                    continue;
                }

                const fileBox = el.querySelector<HTMLElement>('[data-extra-file]');
                if (fileBox) {
                    // File answers are uploaded server-side the moment they are
                    // picked, so the stored state — not an input value — says
                    // whether the question is answered.
                    if ('1' !== fileBox.dataset.extraFile) {
                        ok = false;
                    }
                } else if (el.querySelector('input[type="radio"]')) {
                    if (!el.querySelector('input[type="radio"]:checked')) {
                        ok = false;
                    }
                } else {
                    const field = el.querySelector<HTMLInputElement | HTMLTextAreaElement>('textarea, input');
                    if (!field || '' === field.value.trim()) {
                        ok = false;
                    }
                }
            }
        }

        this.submitTargets.forEach((btn) => {
            btn.disabled = !ok;
            // The Cms:Button component also bakes these classes into a
            // server-rendered disabled button — toggle them along with the
            // property, or the button stays visually dead and unclickable.
            ['pointer-events-none', 'opacity-50', 'cursor-not-allowed'].forEach((cls) =>
                btn.classList.toggle(cls, !ok),
            );
        });
        this.blockerOkTargets.forEach((el) => el.classList.toggle('hidden', !ok));
        this.blockerOpenTargets.forEach((el) => el.classList.toggle('hidden', ok));
    }
}
