<!--
     This file is part of the happyDomain (R) project.
     Copyright (c) 2022-2024 happyDomain
     Authors: Pierre-Olivier Mercier, et al.

     This program is offered under a commercial and under the AGPL license.
     For commercial licensing, contact us at <contact@happydomain.org>.

     For AGPL licensing:
     This program is free software: you can redistribute it and/or modify
     it under the terms of the GNU Affero General Public License as published by
     the Free Software Foundation, either version 3 of the License, or
     (at your option) any later version.

     This program is distributed in the hope that it will be useful,
     but WITHOUT ANY WARRANTY; without even the implied warranty of
     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     GNU Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <https://www.gnu.org/licenses/>.
-->

<script lang="ts">
    import { page } from "$app/state";

    import { Button, FormGroup, Input, Label, Spinner } from "@sveltestrap/sveltestrap";

    import { t } from "$lib/translations";
    import { getOidcProvider } from "$lib/api/auth";
    import { authUser, cleanUserSession } from "$lib/api/user";
    import { CaptchaRequiredError, RateLimitedError } from "$lib/hey-api";
    import type { LoginForm } from "$lib/model/user";
    import { appConfig, navigate } from "$lib/stores/config";
    import { providers } from "$lib/stores/providers";
    import { toasts } from "$lib/stores/toasts";
    import { refreshUserSession } from "$lib/stores/usersession";
    import CaptchaWidget from "$lib/components/CaptchaWidget.svelte";

    let loginForm: LoginForm = $state({
        email: "",
        password: "",
    });
    let emailState: boolean | undefined = $state();
    let passwordState: boolean | undefined = $state();
    let formSent = $state(false);
    let captchaRequired = $state(false);
    let rateLimited = $state(false);
    let captchaToken: string | null = $state(null);
    let captchaWidget: ReturnType<typeof CaptchaWidget> | undefined = $state();

    let formElm: HTMLFormElement | undefined = $state();

    function testLogin(e: SubmitEvent) {
        e.preventDefault();

        if (!formElm) return;

        const valid = formElm.checkValidity();

        if (valid) {
            formSent = true;
            emailState = undefined;
            passwordState = undefined;

            const formWithCaptcha = captchaToken
                ? { ...loginForm, captcha_token: captchaToken }
                : loginForm;

            authUser(formWithCaptcha).then(
                () => {
                    cleanUserSession();
                    providers.set(undefined);
                    formSent = false;
                    emailState = true;
                    passwordState = true;
                    refreshUserSession();

                    const nextParam = page.url.searchParams.get("next");
                    if (nextParam) {
                        const decoded = decodeURIComponent(nextParam);
                        // Only allow same-origin relative paths to prevent open redirect
                        if (decoded.startsWith("/") && !decoded.startsWith("//")) {
                            navigate(decoded);
                        } else {
                            navigate("/");
                        }
                    } else {
                        navigate("/");
                    }
                },
                (error) => {
                    formSent = false;
                    emailState = false;
                    passwordState = false;

                    if (error instanceof RateLimitedError) {
                        rateLimited = true;
                    } else if (error instanceof CaptchaRequiredError) {
                        captchaRequired = true;
                        captchaToken = null;
                        if (captchaWidget) captchaWidget.reset();
                    } else {
                        if (captchaRequired && captchaWidget) {
                            captchaToken = null;
                            captchaWidget.reset();
                        }
                        toasts.addErrorToast({
                            title: $t("errors.login"),
                            message: error,
                            timeout: 20000,
                        });
                    }
                },
            );
        }
    }

    function handleForgottenPassword() {
        if ($appConfig.no_mail) {
            toasts.addErrorToast({
                title: $t("password.recovery-unavailable.title"),
                message: $t("password.recovery-unavailable.description"),
                timeout: 10000,
            });
        } else {
            navigate("/forgotten-password");
        }
    }
</script>

<form class="container my-1" bind:this={formElm} onsubmit={testLogin}>
    <FormGroup>
        <Label for="email-input">{$t("email.address")}</Label>
        <Input
            aria-describedby="emailHelpBlock"
            autocomplete="username"
            autofocus
            id="email-input"
            placeholder="pMockapetris@usc.edu"
            required
            type="email"
            invalid={emailState !== undefined && !emailState}
            valid={emailState}
            bind:value={loginForm.email}
            on:change={() => (emailState = loginForm.email.indexOf("@") > 0)}
        />
    </FormGroup>
    <FormGroup>
        <Label for="password-input">{$t("common.password")}</Label>
        <Input
            autocomplete="current-password"
            id="password-input"
            placeholder="xXxXxXxXxX"
            required
            type="password"
            invalid={passwordState !== undefined && !passwordState}
            valid={passwordState}
            bind:value={loginForm.password}
        />
    </FormGroup>
    {#if rateLimited}
        <p class="text-danger">{$t("errors.rate-limited")}</p>
    {/if}
    {#if $appConfig.captcha_provider && captchaRequired}
        <p>{$t("captcha.human-check")}</p>
        <CaptchaWidget bind:this={captchaWidget} bind:token={captchaToken} />
    {/if}
    <div class="d-flex flex-column flex-lg-row gap-2 justify-content-around">
        <Button type="submit" color="primary" disabled={formSent || rateLimited}>
            {#if formSent}
                <Spinner size="sm" />
            {/if}
            {$t("common.go")}
        </Button>
        {#if $appConfig.oidc_configured}
            {#await getOidcProvider() then oidc}
                <Button href={"/auth/oidc" + (page.url.searchParams.get("next") ? "?next=" + page.url.searchParams.get("next") : "")} color="secondary">
                    {#if oidc.provider == "google.com"}
                        <i class="bi bi-google"></i>
                    {:else if oidc.provider == "gitlab.com" || oidc.provider == "framagit.org"}
                        <i class="bi bi-gitlab"></i>
                    {:else if oidc.provider == "github.com"}
                        <i class="bi bi-github"></i>
                    {:else if oidc.provider == "microsoft.com"}
                        <i class="bi bi-microsoft"></i>
                    {:else if oidc.provider == "apple.com"}
                        <i class="bi bi-apple"></i>
                    {/if}
                    {$t("account.oidc-login", { provider: oidc.provider })}
                </Button>
            {/await}
        {/if}
        <Button type="button" on:click={handleForgottenPassword} outline color="dark">
            {$t("password.forgotten")}
        </Button>
    </div>
</form>
