components_login_dialog.js

import { firebaseAuth, signInWithEmailAndPassword } from '../firebase.js';

/**
 * Login dialog with Sign In and Create Account views.
 */
export class LoginDialog {
    /** Create a new LoginDialog instance. */
    constructor() {
        this._overlay = null;
        this._isRequired = false;
    }

    /** Show the dialog as a dismissable overlay. */
    open() {
        if (this._overlay) return;
        this._isRequired = false;
        this._overlay = this._build();
        document.body.appendChild(this._overlay);
        this._overlay.querySelector('.login-email-input').focus();
    }

    /**
     * Show the dialog as a blocking gate — no close button, no backdrop dismiss.
     * Use this when a sign-in is required to access the app.
     */
    openRequired() {
        if (this._overlay) return;
        this._isRequired = true;
        this._overlay = this._build();
        document.body.appendChild(this._overlay);
        this._overlay.querySelector('.login-email-input').focus();
    }

    /** Hide and destroy the dialog. */
    close() {
        if (this._overlay) {
            this._overlay.remove();
            this._overlay = null;
            this._isRequired = false;
        }
    }

    /**
     * Show a server-side sign-in error and re-enable the form.
     * Called when Firebase auth succeeded but the backend rejected the user.
     * No-op if the dialog is not open.
     * @param {string} msg - The error message to display.
     */
    showConnectError(msg) {
        if (!this._overlay) return;
        const submitBtn = this._overlay._signInSubmit;
        const errorMsg  = this._overlay._signInError;
        if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Sign In'; }
        if (errorMsg)  { errorMsg.textContent = msg; }
    }

    // ── Private ─────────────────────────────────────────────────────────────

    /**
     * Build and return the dialog overlay element.
     * @returns {HTMLElement} The overlay containing the modal.
     */
    _build() {
        const overlay = document.createElement('div');
        overlay.className = 'login-dialog-overlay';

        const modal = document.createElement('div');
        modal.className = 'login-dialog-modal';

        // ── Header ──────────────────────────────────────────────────────────
        const header = document.createElement('div');
        header.className = 'login-dialog-header';
        const title = document.createElement('span');
        title.className = 'login-dialog-title';
        title.textContent = 'Sign In';
        header.appendChild(title);
        if (!this._isRequired) {
            const closeBtn = document.createElement('button');
            closeBtn.className = 'login-dialog-close';
            closeBtn.innerHTML = '<span class="icon icon-close" style="width:12px;height:12px;"></span>';
            closeBtn.title = 'Close';
            closeBtn.addEventListener('click', () => this.close());
            header.appendChild(closeBtn);
        }
        modal.appendChild(header);

        // ── Sign-in view ─────────────────────────────────────────────────────
        const viewSignIn = document.createElement('div');
        viewSignIn.className = 'login-dialog-panel active';

        const emailInput = document.createElement('input');
        emailInput.type = 'email';
        emailInput.className = 'login-dialog-input login-email-input';
        emailInput.placeholder = 'Email';
        emailInput.autocomplete = 'email';

        const passwordInput = document.createElement('input');
        passwordInput.type = 'password';
        passwordInput.className = 'login-dialog-input login-password-input';
        passwordInput.placeholder = 'Password';
        passwordInput.autocomplete = 'current-password';

        const signInBtn = document.createElement('button');
        signInBtn.className = 'login-dialog-submit';
        signInBtn.textContent = 'Sign In';

        const signInError = document.createElement('div');
        signInError.className = 'login-dialog-error';

        const createAccountBtn = document.createElement('button');
        createAccountBtn.className = 'login-dialog-secondary-btn';
        createAccountBtn.textContent = 'Create Account';

        viewSignIn.appendChild(emailInput);
        viewSignIn.appendChild(passwordInput);
        viewSignIn.appendChild(signInBtn);
        viewSignIn.appendChild(signInError);
        viewSignIn.appendChild(createAccountBtn);

        // Store refs for showConnectError
        overlay._signInSubmit = signInBtn;
        overlay._signInError  = signInError;

        // ── Create-account view ──────────────────────────────────────────────
        const viewCreate = document.createElement('div');
        viewCreate.className = 'login-dialog-panel';

        const notice = document.createElement('div');
        notice.className = 'login-dialog-notice';
        notice.innerHTML = `
            <span class="login-dialog-notice-badge">Hosted Cloud Service</span>
            <p>Create an account to use the hosted version of Waveform Studio, which includes AI transcription
            and cloud storage. The hosted service is currently invite-only — you'll receive an email when
            your account is activated.</p>
            <p style="margin-top:0.5rem;">Prefer to run it yourself? The app is
            <a href="https://gitlab.com/vtleavs/waveform-studio/" target="_blank" rel="noopener noreferrer"
               style="color:var(--accent);">open source</a> and free to self-host.</p>`;
        viewCreate.appendChild(notice);

        const nameInput = document.createElement('input');
        nameInput.type = 'text';
        nameInput.className = 'login-dialog-input';
        nameInput.placeholder = 'Display name';
        nameInput.autocomplete = 'name';

        const caEmailInput = document.createElement('input');
        caEmailInput.type = 'email';
        caEmailInput.className = 'login-dialog-input';
        caEmailInput.placeholder = 'Email address';
        caEmailInput.autocomplete = 'email';

        const caPasswordInput = document.createElement('input');
        caPasswordInput.type = 'password';
        caPasswordInput.className = 'login-dialog-input';
        caPasswordInput.placeholder = 'Password (min 8 chars)';
        caPasswordInput.autocomplete = 'new-password';

        const caConfirmInput = document.createElement('input');
        caConfirmInput.type = 'password';
        caConfirmInput.className = 'login-dialog-input';
        caConfirmInput.placeholder = 'Confirm password';
        caConfirmInput.autocomplete = 'new-password';

        const requestBtn = document.createElement('button');
        requestBtn.className = 'login-dialog-submit';
        requestBtn.textContent = 'Request Access';

        const caError = document.createElement('div');
        caError.className = 'login-dialog-error';

        const backBtn = document.createElement('button');
        backBtn.className = 'login-dialog-secondary-btn';
        backBtn.textContent = 'Back to Sign In';

        viewCreate.appendChild(nameInput);
        viewCreate.appendChild(caEmailInput);
        viewCreate.appendChild(caPasswordInput);
        viewCreate.appendChild(caConfirmInput);
        viewCreate.appendChild(requestBtn);
        viewCreate.appendChild(caError);
        viewCreate.appendChild(backBtn);

        // ── Create-account success view ──────────────────────────────────────
        const viewSuccess = document.createElement('div');
        viewSuccess.className = 'login-dialog-panel login-dialog-success';

        const successIcon = document.createElement('div');
        successIcon.className = 'login-dialog-success-icon';
        successIcon.innerHTML = '<span class="icon icon-check" style="width:32px;height:32px;"></span>';

        const successTitle = document.createElement('p');
        successTitle.className = 'login-dialog-success-title';
        successTitle.textContent = 'Request received';

        const successBody = document.createElement('p');
        successBody.className = 'login-dialog-success-body';
        successBody.textContent = "The administrators have been notified. You'll receive an email if your account is activated.";

        const successBackBtn = document.createElement('button');
        successBackBtn.className = 'login-dialog-secondary-btn';
        successBackBtn.textContent = 'Back to Sign In';

        viewSuccess.appendChild(successIcon);
        viewSuccess.appendChild(successTitle);
        viewSuccess.appendChild(successBody);
        viewSuccess.appendChild(successBackBtn);

        modal.appendChild(viewSignIn);
        modal.appendChild(viewCreate);
        modal.appendChild(viewSuccess);

        // ── View switching ───────────────────────────────────────────────────
        const showSignIn = () => {
            title.textContent = 'Sign In';
            viewSignIn.classList.add('active');
            viewCreate.classList.remove('active');
            viewSuccess.classList.remove('active');
            emailInput.focus();
        };

        const showCreate = () => {
            title.textContent = 'Create Account';
            viewSignIn.classList.remove('active');
            viewCreate.classList.add('active');
            viewSuccess.classList.remove('active');
            nameInput.focus();
        };

        const showSuccess = () => {
            title.textContent = 'Create Account';
            viewSignIn.classList.remove('active');
            viewCreate.classList.remove('active');
            viewSuccess.classList.add('active');
        };

        createAccountBtn.addEventListener('click', showCreate);
        backBtn.addEventListener('click', showSignIn);
        successBackBtn.addEventListener('click', showSignIn);

        // ── Sign-in logic ────────────────────────────────────────────────────
        const doSignIn = async () => {
            const email    = emailInput.value.trim();
            const password = passwordInput.value;
            if (!email || !password) {
                signInError.textContent = 'Please enter your email and password.';
                return;
            }
            if (!firebaseAuth) {
                signInError.textContent = 'Firebase is not configured on this server.';
                return;
            }
            signInBtn.disabled = true;
            signInError.textContent = '';
            try {
                await signInWithEmailAndPassword(firebaseAuth, email, password);
                // Keep the dialog open with a connecting state.
                // onAuthStateChanged in main.js handles the server connection and
                // will call close() on success or showConnectError() on failure.
                signInBtn.textContent = 'Connecting…';
            } catch (e) {
                signInError.textContent = _friendlyFirebaseError(e.code);
                signInBtn.disabled = false;
            }
        };

        signInBtn.addEventListener('click', doSignIn);
        passwordInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') doSignIn();
            e.stopPropagation();
        });
        emailInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') passwordInput.focus();
            e.stopPropagation();
        });

        // ── Create-account logic ─────────────────────────────────────────────
        const doCreateAccount = async () => {
            const name     = nameInput.value.trim();
            const email    = caEmailInput.value.trim();
            const password = caPasswordInput.value;
            const confirm  = caConfirmInput.value;

            caError.textContent = '';
            if (!email)              { caError.textContent = 'Email is required.'; return; }
            if (password.length < 8) { caError.textContent = 'Password must be at least 8 characters.'; return; }
            if (password !== confirm) { caError.textContent = 'Passwords do not match.'; return; }

            requestBtn.disabled = true;
            requestBtn.textContent = 'Submitting…';

            try {
                const res  = await fetch('/api/beta-signup', {
                    method:  'POST',
                    headers: { 'Content-Type': 'application/json' },
                    body:    JSON.stringify({ name, email, password }),
                });
                const data = await res.json();
                if (res.ok) {
                    showSuccess();
                } else {
                    caError.textContent = data.error || 'Something went wrong. Please try again.';
                    requestBtn.disabled = false;
                    requestBtn.textContent = 'Request Access';
                }
            } catch {
                caError.textContent = 'Network error. Check your connection and try again.';
                requestBtn.disabled = false;
                requestBtn.textContent = 'Request Access';
            }
        };

        requestBtn.addEventListener('click', doCreateAccount);
        [nameInput, caEmailInput, caPasswordInput].forEach(input => {
            input.addEventListener('keydown', (e) => { e.stopPropagation(); });
        });
        caConfirmInput.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') doCreateAccount();
            e.stopPropagation();
        });

        // TODO: Custom server sign-in has been disabled pending review.
        // Re-enable by adding a tab that switches to a custom-server panel where
        // the user can supply an alternate base URL and authenticate against it.

        if (this._isRequired) {
            overlay.classList.add('login-dialog-overlay--required');
            const wrap = document.createElement('div');
            wrap.className = 'login-dialog-branded-wrap';
            const brandName = document.createElement('div');
            brandName.className = 'login-dialog-brand-name';
            brandName.textContent = 'Waveform Studio';
            wrap.appendChild(brandName);
            wrap.appendChild(modal);
            overlay.appendChild(wrap);
        } else {
            overlay.appendChild(modal);
        }

        // Close on outside click (only when mousedown also started on the overlay, and not in required mode)
        if (!this._isRequired) {
            let _mouseDownOnOverlay = false;
            overlay.addEventListener('mousedown', (e) => { _mouseDownOnOverlay = e.target === overlay; });
            overlay.addEventListener('click', (e) => {
                if (e.target === overlay && _mouseDownOnOverlay) this.close();
            });
        }

        return overlay;
    }
}

/**
 * Map a Firebase auth error code to a human-readable message.
 * @param {string} code - The Firebase error code (e.g. 'auth/wrong-password').
 * @returns {string} A user-facing error message.
 */
function _friendlyFirebaseError(code) {
    switch (code) {
        case 'auth/invalid-email':          return 'Invalid email address.';
        case 'auth/user-not-found':
        case 'auth/wrong-password':
        case 'auth/invalid-credential':     return 'Incorrect email or password.';
        case 'auth/too-many-requests':      return 'Too many attempts. Please try again later.';
        case 'auth/user-disabled':          return 'This account has been disabled.';
        case 'auth/network-request-failed': return 'Network error. Check your connection.';
        default:                            return 'Sign-in failed. Please try again.';
    }
}