'use strict';

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

const RETURNING_KEY = 'ferienpass.returningUser';
const METHOD_KEY = 'ferienpass.lastLoginMethod';

type View = 'magic' | 'password' | 'passkeyOnly';

export default class extends Controller {
    static values = {
        requestToken: String,
        targetPath: String,
        magicUrl: String,
    };

    static targets = [
        "error", "username", "password", "remember_me",
        "emailBlock", "magicActions", "magicSent", "passwordBlock",
        "secondary", "passwordEye", "passwordEyeOff", "magicButton", "sentEmail",
    ];

    declare readonly targetPathValue?: string;
    declare readonly requestTokenValue?: string;
    declare readonly magicUrlValue?: string;

    declare readonly errorTarget: HTMLElement;
    declare readonly usernameTarget: HTMLInputElement;
    declare readonly passwordTarget: HTMLInputElement;
    declare readonly hasPasswordTarget: boolean;
    declare readonly hasEmailBlockTarget: boolean;
    declare readonly emailBlockTarget: HTMLElement;
    declare readonly hasMagicActionsTarget: boolean;
    declare readonly magicActionsTarget: HTMLElement;
    declare readonly hasMagicSentTarget: boolean;
    declare readonly magicSentTarget: HTMLElement;
    declare readonly hasPasswordBlockTarget: boolean;
    declare readonly passwordBlockTarget: HTMLElement;
    declare readonly hasSecondaryTarget: boolean;
    declare readonly secondaryTarget: HTMLElement;
    declare readonly hasPasswordEyeTarget: boolean;
    declare readonly passwordEyeTarget: HTMLElement;
    declare readonly hasPasswordEyeOffTarget: boolean;
    declare readonly passwordEyeOffTarget: HTMLElement;
    declare readonly magicButtonTargets: HTMLButtonElement[];
    declare readonly hasSentEmailTarget: boolean;
    declare readonly sentEmailTarget: HTMLElement;

    declare component?: Component;

    view: View = 'magic';
    pwVisible = false;
    magicSent = false;

    async initialize() {
        if (!(this.element instanceof HTMLElement)) {
            return;
        }

        try {
            this.component = await getComponent(this.element);
            // Re-apply the client-side state after every server re-render.
            this.component.on('render:finished', () => this.applyState());
        } catch (e) {
            // Not (yet) a live component — state is still managed locally.
        }
    }

    connect() {
        let last: string | null = null;
        try {
            last = localStorage.getItem(METHOD_KEY);
        } catch (e) {
        }

        this.view = 'password' === last ? 'password' : ('passkey' === last ? 'passkeyOnly' : 'magic');
        this.applyState();
    }

    revealPassword() {
        this.view = 'password';
        this.applyState();
        if (this.hasPasswordTarget) {
            this.passwordTarget.focus();
        }
    }

    revealEmail() {
        this.view = 'magic';
        this.applyState();
        this.usernameTarget.focus();
    }

    togglePassword() {
        this.pwVisible = !this.pwVisible;
        this.applyState();
    }

    enterEmail(event: KeyboardEvent) {
        // In the passwordless views, Enter sends the magic link instead of
        // submitting the (hidden) password form.
        if ('password' !== this.view) {
            event.preventDefault();
            this.sendMagicLink();
        }
    }

    // Magic-link can only be requested once an e-mail address is present.
    syncMagic() {
        const hasEmail = !!this.usernameTarget.value.trim();
        this.magicButtonTargets.forEach((b) => {
            b.disabled = !hasEmail;
        });
    }

    isValidEmail(): boolean {
        const value = this.usernameTarget.value.trim();

        return value.length > 0 && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
    }

    // Blur handler: flag a non-empty but malformed address.
    validateEmail() {
        this.setEmailError(this.usernameTarget.value.trim().length > 0 && !this.isValidEmail());
    }

    // Input handler: keep the magic button in sync and clear the error as soon
    // as the address becomes valid (without nagging mid-typing).
    emailInput() {
        this.syncMagic();
        if (this.isValidEmail()) {
            this.setEmailError(false);
        }
    }

    private setEmailError(hasError: boolean) {
        ['border-red-500', 'ring-4', 'ring-red-500/30'].forEach((c) => this.usernameTarget.classList.toggle(c, hasError));
        this.usernameTarget.setAttribute('aria-invalid', hasError ? 'true' : 'false');
    }

    async sendMagicLink() {
        // Validate client-side to avoid a needless POST for an invalid address.
        if (!this.isValidEmail()) {
            this.validateEmail();
            this.usernameTarget.focus();
            return;
        }

        const email = this.usernameTarget.value.trim();

        if (this.hasSentEmailTarget) {
            this.sentEmailTarget.textContent = email;
        }

        if (this.magicUrlValue) {
            try {
                await fetch(this.magicUrlValue, {
                    method: 'POST',
                    headers: {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'},
                    body: JSON.stringify({email}),
                });
            } catch (e) {
            }
        }

        // Remember the magic link as the last method so it is offered first
        // next time. No name or e-mail is stored.
        try {
            localStorage.setItem(RETURNING_KEY, '1');
            localStorage.setItem(METHOD_KEY, 'magicLink');
        } catch (e) {
        }

        this.magicSent = true;
        this.applyState();
    }

    // Go back from the "link sent" state to the form to try another address.
    tryOtherEmail() {
        this.magicSent = false;
        this.applyState();
        this.usernameTarget.focus();
        this.usernameTarget.select();
    }

    applyState() {
        const sent = this.magicSent;

        this.toggle(this.hasEmailBlockTarget, this.hasEmailBlockTarget ? this.emailBlockTarget : null, !sent && 'passkeyOnly' !== this.view);
        this.toggle(this.hasMagicActionsTarget, this.hasMagicActionsTarget ? this.magicActionsTarget : null, !sent && 'magic' === this.view);
        this.toggle(this.hasPasswordBlockTarget, this.hasPasswordBlockTarget ? this.passwordBlockTarget : null, !sent && 'password' === this.view);
        this.toggle(this.hasSecondaryTarget, this.hasSecondaryTarget ? this.secondaryTarget : null, !sent && 'passkeyOnly' === this.view);
        this.toggle(this.hasMagicSentTarget, this.hasMagicSentTarget ? this.magicSentTarget : null, sent);

        if (this.hasPasswordTarget) {
            this.passwordTarget.type = this.pwVisible ? 'text' : 'password';
        }
        this.toggle(this.hasPasswordEyeTarget, this.hasPasswordEyeTarget ? this.passwordEyeTarget : null, !this.pwVisible);
        this.toggle(this.hasPasswordEyeOffTarget, this.hasPasswordEyeOffTarget ? this.passwordEyeOffTarget : null, this.pwVisible);

        this.syncMagic();
    }

    private toggle(has: boolean, el: HTMLElement | null, visible: boolean) {
        if (has && el) {
            // Inline style always wins over class-based display utilities
            // (avoids `hidden` losing to `inline-flex`/`grid`). Empty string
            // falls back to the element's class-defined display.
            el.style.display = visible ? '' : 'none';
        }
    }

    async submit(event: SubmitEvent) {
        // Validate the e-mail client-side to avoid a needless POST.
        if (!this.isValidEmail()) {
            this.validateEmail();
            this.usernameTarget.focus();
            return;
        }

        const response = await fetch('/check_login', {
            method: 'POST',
            headers: {'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest'},
            body: JSON.stringify({
                username: this.usernameTarget.value,
                password: this.passwordTarget.value,
                REQUEST_TOKEN: this.requestTokenValue
            })
        })

        const json = await response.json()

        if ("redirect_to" in json) {
            window.location.href = json.redirect_to
            return
        }

        if (!("user" in json)) {
            this.errorTarget.classList.remove('hidden')
        } else {
            this.errorTarget.classList.add('hidden')
            // Remember the visitor + that the last successful method was a
            // password, so the password form is shown upfront next time. No
            // name or e-mail is stored.
            try {
                localStorage.setItem(RETURNING_KEY, '1')
                localStorage.setItem(METHOD_KEY, 'password')
            } catch (e) {
            }
            if (this.targetPathValue) {
                window.location.href = this.targetPathValue
            }
        }
    }
}
