import { WebAuthnAbortService } from "@simplewebauthn/browser";
import clsx from "clsx";
import { OTPInput, OTPInputContext, OTPInputProps } from "input-otp";
import * as React from "react";
import {
  Link,
  UNSAFE_FrameworkContext,
  useNavigate,
  useSearchParams,
} from "react-router";
import {
  AUTH_HOST,
  browserSupportsWebAuthn,
  checkResponse,
  initLogin,
  initRegistration,
} from "../../utils/webauthn";
import { resolveRoute } from "../Link";
import * as styles from "./styles.module.css";

enum LoginState {
  Login,
  Passkey,
  RegisterUser,
  OTP,
  RegisterPasskey,
  NotFound,
}

const titles: { [key in LoginState]: string } = {
  [LoginState.Login]: "Welcome",
  [LoginState.Passkey]: "Sign in with passkey?",
  [LoginState.RegisterUser]: "Create account?",
  [LoginState.OTP]: "Confirm code",
  [LoginState.RegisterPasskey]: "Sign in faster with a passkey",
  [LoginState.NotFound]: "Account not found",
};

const subtitles: { [key in LoginState]: (email: string | null) => string } = {
  [LoginState.Login]: (email) => "Enter your email to continue.",
  [LoginState.Passkey]: (email) =>
    "Use a passkey to sign in or try verification code instead.",
  [LoginState.RegisterUser]: (email) =>
    `No account exists for email "${email}". Would you like to create one?`,
  [LoginState.OTP]: (email) => `Please enter the code sent to "${email}".`,
  [LoginState.RegisterPasskey]: (email) =>
    "Use your fingerprint, face, or pin to sign in.",
  [LoginState.NotFound]: (email) => `No account exists for email "${email}".`,
};

type Classes = {
  headline?: string;
  subtitle?: string;
  error?: string;
  container?: string;
  input?: string;
  otp?: string;
  primaryButton?: string;
  secondaryButton?: string;
  divider?: string;
  footer?: string;
  link?: string;
};

export function Login({
  title = "Welcome",
  subtitle = "Enter your email to continue.",
  callbackUrl: _callbackUrl,
  className,
  disablePasskey,
  disableCreate,
  classes = {},
  query = [],
  ...props
}: {
  title?: string;
  subtitle?: string;
  callbackUrl?: any;
  disablePasskey?: boolean;
  query: { key: string; value: string }[];
  className?: string;
  disableCreate?: boolean;
  classes?: Classes;
} & React.HTMLAttributes<HTMLDivElement>) {
  const [error, setError] = React.useState<string | null>(null);
  const [step, setStep] = React.useState(LoginState.Login);
  const [email, setEmail] = React.useState<string | null>("");
  const framework = React.use(UNSAFE_FrameworkContext);
  if (!framework) {
    throw new Error(
      "Framework context is not available. Please ensure you are using this component within a FrameworkProvider.",
    );
  }
  const callbackUrl = React.useMemo(() => {
    return resolveRoute(_callbackUrl, framework.manifest);
  }, [_callbackUrl, framework.manifest]);
  const queryStr = React.useMemo(
    () =>
      query?.length
        ? `?${new URLSearchParams(query.map(({ key, value }) => [key, value]))}`
        : "",
    [query],
  );
  const renderStep = React.useCallback(
    (step: LoginState) => {
      switch (step) {
        case LoginState.Login:
          return (
            <Initial
              setEmail={setEmail}
              callbackUrl={callbackUrl + queryStr}
              setError={setError}
              setStep={setStep}
              disableCreate={disableCreate}
              disablePasskey={disablePasskey}
              classes={classes}
            />
          );
        case LoginState.Passkey:
          return (
            <Passkey
              email={email!}
              callbackUrl={callbackUrl + queryStr}
              setError={setError}
              setStep={setStep}
              disableCreate={disableCreate}
              classes={classes}
            />
          );
        case LoginState.RegisterUser:
          return (
            <ConfirmRegistration
              email={email}
              setStep={setStep}
              callbackUrl={callbackUrl + queryStr}
              setError={setError}
              classes={classes}
            />
          );
        case LoginState.OTP:
          return (
            <ConfirmCode
              email={email}
              callbackUrl={callbackUrl + queryStr}
              disablePasskey={disablePasskey}
              setError={setError}
              setStep={setStep}
              classes={classes}
            />
          );
        case LoginState.RegisterPasskey:
          return (
            <RegisterPasskey
              callbackUrl={callbackUrl + queryStr}
              setError={setError}
              setStep={setStep}
              classes={classes}
            />
          );
        case LoginState.NotFound:
          // nothing to render for this route
          return null;
      }
    },
    [email, disableCreate, callbackUrl, classes, queryStr],
  );

  return (
    <section className={clsx("__login", className)} {...props}>
      <div className={clsx("__login-container", classes?.container)}>
        <h1
          className={clsx("__login-headline", classes?.headline)}
          data-component="Text$Brevity"
          data-size="2xl"
          data-variant="heading"
        >
          {(step === LoginState.Login ? title : titles[step]) ?? ""}
        </h1>
        <h2
          className={clsx(
            "__login-subtitle",
            styles.subtitle,
            classes?.subtitle,
          )}
          data-component="Text$Brevity"
          data-size="md"
          data-variant="body"
        >
          {(step === LoginState.Login ? subtitle : subtitles[step]?.(email)) ??
            ""}
        </h2>
      </div>
      {error ? (
        <section className={clsx("__login-error", classes?.error)}>
          <svg
            xmlns="http://www.w3.org/2000/svg"
            width="18"
            height="18"
            viewBox="0 0 24 24"
            className={clsx("__login-errorIcon", styles.errorIcon)}
          >
            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"></path>
          </svg>
          <span className={"__login-errorText"}>{error}</span>
        </section>
      ) : null}
      {renderStep(step)}
    </section>
  );
}

function Initial({
  setEmail,
  callbackUrl,
  setError,
  setStep,
  disableCreate,
  disablePasskey,
  classes = {},
  ...props
}: {
  setEmail: (email: string) => void;
  callbackUrl?: string;
  setError: (error: string | null) => void;
  setStep: (step: LoginState) => void;
  disableCreate?: boolean;
  disablePasskey?: boolean;
  classes?: Classes;
}) {
  const [enableGoogle, setEnableGoogle] = React.useState(false);
  const [enableEntra, setEnableEntra] = React.useState(false);
  const [supportsWebauthn, setSupportsWebauthn] = React.useState(true);
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  React.useEffect(() => {
    if (process.env.PREVIEW) {
      setSupportsWebauthn(browserSupportsWebAuthn());
      return;
    }
    if (disablePasskey) {
      return;
    }
    const signal = WebAuthnAbortService.createNewAbortSignal();
    initLogin(true, signal)
      .then(({ verified, credentialID }) => {
        if (verified) {
          if (credentialID) {
            // append to list in local storage
            try {
              const list = JSON.parse(
                localStorage.getItem("__brevity_passkeys") || "[]",
              );
              list.push(credentialID);
              localStorage.setItem("__brevity_passkeys", JSON.stringify(list));
            } catch (err) {
              // ignore
            }
          }
          navigate(callbackUrl || "/");
        } else {
          // do nothing, wait for user to click button
          // setError("Failed to sign in with passkey");
        }
      })
      .catch((err) => {
        if (!err) return;
        if (err.name === "AbortError" || err.name === "NotAllowedError") {
          // This error is thrown when the user cancels the operation in the browser
          return;
        }
        if (err?.code) {
          if (err.code === "ERROR_CEREMONY_ABORTED") {
            // This error is thrown when the user cancels the operation in the browser
            return;
          } else if (err.code === "ERROR_INVALID_DOMAIN") {
            setError(`Invalid domain`);
            return;
          } else if (err.code === "ERROR_INVALID_RP_ID") {
            setError(`Invalid RP ID`);
            return;
          } else if (err.code === "ERROR_INVALID_USER_ID_LENGTH") {
            setError(`Invalid user ID length`);
            return;
          } else if (err.code === "ERROR_MALFORMED_PUBKEYCREDPARAMS") {
            setError(`Malformed public key credential parameters`);
            return;
          } else if (err.code === "ERROR_AUTHENTICATOR_GENERAL_ERROR") {
            setError(`Authenticator general error`);
            return;
          } else if (
            err.code ===
            "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT"
          ) {
            setError(`Authenticator missing discoverable credential support`);
            return;
          } else if (
            err.code === "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT"
          ) {
            setError(`Authenticator missing user verification support`);
            return;
          } else if (err.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") {
            setError(`Authenticator previously registered`);
            return;
          } else if (
            err.code === "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG"
          ) {
            setError(
              `Authenticator no supported public key credential parameters algorithm`,
            );
            return;
          } else if (err.code === "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY") {
            err = err.cause || err;
          }
        }
        if (
          err.message === "Browser does not support WebAuthn autofill" ||
          err.message === "WebAuthn is not supported in this browser"
        ) {
          // This errors match simplewebauthn's error messages
          console.error(err);
          return;
        }
        console.error(err);
        setError(`${err}`);
      });
    setSupportsWebauthn(browserSupportsWebAuthn());
    return () => {
      WebAuthnAbortService.cancelCeremony();
    };
  }, []);
  React.useEffect(() => {
    const abortController = new AbortController();
    fetch(`${AUTH_HOST}/oauth`, {
      signal: abortController.signal,
    })
      .then(checkResponse)
      .then((data: Record<string, boolean>) => {
        if (data.google) {
          setEnableGoogle(true);
        }
        if (data.entra) {
          setEnableEntra(true);
        }
      });
    return () => {
      abortController.abort();
    };
  }, []);
  return (
    <form
      className={clsx("__login-container", classes.container)}
      onSubmit={(e) => {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        setEmail(formData.get("identifier") as string);
        fetch(`${AUTH_HOST}/available`, {
          credentials: "include",
          method: "POST",
          body: formData,
        })
          .then(checkResponse)
          .then((data) => {
            if (data.exists) {
              if (data.hasPasskey && supportsWebauthn && !disablePasskey) {
                setError(null);
                setStep(LoginState.Passkey);
                return;
              }
              return fetch(`${AUTH_HOST}/magic-link/login`, {
                credentials: "include",
                method: "POST",
                body: formData,
              })
                .then(checkResponse)
                .then((data) => {
                  if (data.success) {
                    setError(null);
                    setStep(LoginState.OTP);
                  } else {
                    setError(data.error);
                  }
                });
            } else if (disableCreate) {
              setError(null);
              setStep(LoginState.NotFound);
            } else {
              setError(null);
              setStep(LoginState.RegisterUser);
            }
          })
          .catch((err) => {
            setError(`${err}`);
          });
      }}
    >
      {callbackUrl ? (
        <input type="hidden" name="callbackUrl" value={callbackUrl} />
      ) : null}
      {disableCreate ? (
        <input type="hidden" name="disableCreate" value="true" />
      ) : null}
      <input
        className={clsx(
          "__login-input",
          "__login-input-email",
          styles.input,
          classes.input,
        )}
        defaultValue={
          searchParams.has("identifier")
            ? (searchParams.get("identifier") ?? "")
            : ""
        }
        type="email"
        name="identifier"
        inputMode="email"
        placeholder="Enter your email"
        autoComplete="username webauthn"
        autoFocus={!process.env.PREVIEW}
        pattern="^.*[^0-9]+$"
        data-component="Input$Brevity"
        data-size="md"
        data-variant="outline"
      />
      <button
        className={clsx(
          "__login-button",
          "__login-button-primary",
          classes.primaryButton,
        )}
        type="submit"
        data-component="Button$Brevity"
        data-variant="solid"
        data-size="md"
        data-palette="brand"
      >
        Continue
      </button>
      {enableGoogle || enableEntra ? (
        <section className={clsx("__login-divider", classes?.divider)}>
          <div className={clsx("__login-dline", styles.dline)}></div>
          <div className={clsx("__login-dtext", styles.dtext)}>
            or continue with
          </div>
          <div className={clsx("__login-dline", styles.dline)}></div>
        </section>
      ) : null}
      {enableGoogle ? (
        <a
          href={
            AUTH_HOST +
            "/oauth/google" +
            (callbackUrl ? `?callbackUrl=${callbackUrl}` : "")
          }
          className={clsx(
            "__login-button",
            "__login-button-secondary",
            styles.buttonLink,
            classes.secondaryButton,
          )}
          data-component="Button$Brevity"
          data-variant="outline"
          data-size="md"
          data-palette="neutral"
        >
          <svg
            className={clsx("passkey-logo", styles.passkeyLogo)}
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 488 512"
          >
            <path d="M488 261.8C488 403.3 391.1 504 248 504 110.8 504 0 393.2 0 256S110.8 8 248 8c66.8 0 123 24.5 166.3 64.9l-67.5 64.9C258.5 52.6 94.3 116.6 94.3 256c0 86.5 69.1 156.6 153.7 156.6 98.2 0 135-70.4 140.8-106.9H248v-85.3h236.1c2.3 12.7 3.9 24.9 3.9 41.4z"></path>
          </svg>
          Google
        </a>
      ) : null}
      {enableEntra ? (
        <a
          href={
            AUTH_HOST +
            "/oauth/entra" +
            (callbackUrl ? `?callbackUrl=${callbackUrl}` : "")
          }
          className={clsx(
            "__login-button",
            "__login-button-secondary",
            styles.buttonLink,
            classes.secondaryButton,
          )}
          data-component="Button$Brevity"
          data-variant="outline"
          data-size="md"
          data-palette="neutral"
        >
          <svg
            className={clsx("passkey-logo", styles.passkeyLogo)}
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 18 18"
          >
            <path d="M17.558 9.638l-7.122-8.006A1.918 1.918 0 009.001 1a1.92 1.92 0 00-1.435.632L.442 9.638c-.346.389-.5.9-.423 1.403.077.503.379.952.827 1.231l7.123 4.436c.313.195.672.293 1.031.293s.718-.098 1.031-.293l7.123-4.436c.448-.279.75-.728.827-1.231a1.717 1.717 0 00-.423-1.403zm-8.557 2.564l-3.47-2.161 3.47-3.9 3.47 3.9-3.47 2.161zM4.18 10.227l3.995 2.489-1.137.708-.016.01c-.394.25-.854.382-1.33.382-.528 0-1.059-.214-1.38-.413l-2.969-1.849a.91.91 0 01-.428-.637.875.875 0 01.219-.725l7.123-8.006A.997.997 0 019 1.858a.99.99 0 01.743.328l1.576 1.771h-.028c-.856 0-1.668.313-2.278.866L4.18 10.227zm12.905.69a.907.907 0 01-.428.637L9.534 15.99a1.017 1.017 0 01-1.068 0l-2.192-1.365c.451-.076.88-.238 1.264-.484l6.259-3.898L9.6 5.484a2.456 2.456 0 012.662-.466l4.605 5.175a.878.878 0 01.219.725z"></path>
          </svg>
          Microsoft Entra ID
        </a>
      ) : null}
    </form>
  );
}

function Passkey({
  email,
  callbackUrl,
  setError,
  setStep,
  disableCreate,
  classes = {},
  ...props
}: {
  email: string;
  callbackUrl?: string;
  setError: (error: string | null) => void;
  setStep: (step: LoginState) => void;
  disableCreate?: boolean;
  classes?: Classes;
}) {
  const [supportsWebauthn, setSupportsWebauthn] = React.useState(true);
  const [searchParams] = useSearchParams();
  const navigate = useNavigate();
  React.useEffect(() => {
    const hasSupport = browserSupportsWebAuthn();
    setSupportsWebauthn(hasSupport);
  }, []);
  const startPasskey = React.useCallback(() => {
    if (process.env.PREVIEW) {
      return;
    }
    if (!supportsWebauthn) {
      return;
    }
    const signal = WebAuthnAbortService.createNewAbortSignal();
    initLogin(false, signal)
      .then(({ verified, credentialID }) => {
        if (verified) {
          if (credentialID) {
            // append to list in local storage
            try {
              const list = JSON.parse(
                localStorage.getItem("__brevity_passkeys") || "[]",
              );
              list.push(credentialID);
              localStorage.setItem("__brevity_passkeys", JSON.stringify(list));
            } catch (err) {
              // ignore
            }
          }
          navigate(callbackUrl || "/");
        } else {
          // do nothing, wait for user to click button
          setError(
            "Failed to sign in with passkey. Try verification code instead.",
          );
        }
      })
      .catch((err) => {
        if (!err) return;
        if (err?.name === "AbortError" || err?.name === "NotAllowedError") {
          // This error is thrown when the user cancels the operation in the browser
          return;
        }
        if (err?.code) {
          if (err.code === "ERROR_CEREMONY_ABORTED") {
            // This error is thrown when the user cancels the operation in the browser
            return;
          } else if (err.code === "ERROR_INVALID_DOMAIN") {
            setError(`Invalid domain`);
            return;
          } else if (err.code === "ERROR_INVALID_RP_ID") {
            setError(`Invalid RP ID`);
            return;
          } else if (err.code === "ERROR_INVALID_USER_ID_LENGTH") {
            setError(`Invalid user ID length`);
            return;
          } else if (err.code === "ERROR_MALFORMED_PUBKEYCREDPARAMS") {
            setError(`Malformed public key credential parameters`);
            return;
          } else if (err.code === "ERROR_AUTHENTICATOR_GENERAL_ERROR") {
            setError(`Authenticator general error`);
            return;
          } else if (
            err.code ===
            "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT"
          ) {
            setError(`Authenticator missing discoverable credential support`);
            return;
          } else if (
            err.code === "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT"
          ) {
            setError(`Authenticator missing user verification support`);
            return;
          } else if (err.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED") {
            setError(`Authenticator previously registered`);
            return;
          } else if (
            err.code === "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG"
          ) {
            setError(
              `Authenticator no supported public key credential parameters algorithm`,
            );
            return;
          } else if (err.code === "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY") {
            err = err.cause || err;
          }
        }
        if (
          err.message === "Browser does not support WebAuthn autofill" ||
          err.message === "WebAuthn is not supported in this browser"
        ) {
          // This errors match simplewebauthn's error messages
          console.error(err);
          return;
        }
        console.error(err);
        setError(`${err}`);
      });
    return () => {
      WebAuthnAbortService.cancelCeremony();
    };
  }, []);
  return (
    <form
      className={clsx("__login-container", classes.container)}
      onSubmit={(e) => {
        e.preventDefault();
        if (process.env.PREVIEW) {
          navigate(callbackUrl || "/");
          return;
        }
        const formData = new FormData();
        formData.set("identifier", email ?? "");
        formData.set("callbackUrl", callbackUrl || "/");
        return fetch(`${AUTH_HOST}/magic-link/login`, {
          credentials: "include",
          method: "POST",
          body: formData,
        })
          .then(checkResponse)
          .then((data) => {
            if (data.success) {
              setError(null);
              setStep(LoginState.OTP);
            } else {
              setError(data.error);
            }
          });
      }}
    >
      {supportsWebauthn ? (
        <button
          className={clsx(
            "__login-button",
            "__login-button-primary",
            classes.primaryButton,
          )}
          onClick={startPasskey}
          type="button"
          data-component="Button$Brevity"
          data-variant="solid"
          data-size="md"
          data-palette="brand"
        >
          <PasskeyLogo />
          Use passkey
        </button>
      ) : null}
      <button
        className={clsx(
          "__login-button",
          "__login-button-secondary",
          classes.secondaryButton,
        )}
        type="submit"
        data-component="Button$Brevity"
        data-variant="outline"
        data-size="md"
        data-palette="neutral"
      >
        Send one-time code
      </button>
      <section className={clsx("__login-footer", classes.footer)}>
        <span
          className={clsx("__login-link", styles.link, classes.link)}
          onClick={() => {
            setError(null);
            setStep(LoginState.Login);
          }}
        >
          Back
        </span>
      </section>
    </form>
  );
}

export function PasskeyLogo() {
  return (
    <svg
      className={clsx("passkey-logo", styles.passkeyLogo)}
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 -960 960 960"
      aria-hidden="true"
    >
      <path
        fill="currentColor"
        d="M200-160q-33 0-56.5-23.5T120-240v-32q0-34 17.5-62.5T184-378q62-31 126-46.5T440-440q20 0 40 1.5t40 4.5q-4 58 21 109.5t73 84.5v80H200zm240-320q-66 0-113-47t-47-113q0-66 47-113t113-47q66 0 113 47t47 113q0 66-47 113t-113 47zm300 80q17 0 28.5-11.5T780-440q0-17-11.5-28.5T740-480q-17 0-28.5 11.5T700-440q0 17 11.5 28.5T740-400zm6 346l-40-40q-2-2-6-14v-178q-44-13-72-49.5T600-420q0-58 41-99t99-41q58 0 99 41t41 99q0 45-25.5 80T790-290l36 36q6 6 6 14t-6 14l-32 32q-6 6-6 14t6 14l32 32q6 6 6 14t-6 14l-52 52q-6 6-14 6t-14-6z"
      ></path>
    </svg>
  );
}

function ConfirmRegistration({
  callbackUrl,
  setStep,
  email,
  setError,
  classes = {},
}: {
  callbackUrl?: string;
  setStep: (step: LoginState) => void;
  email: string | null;
  setError: (error: string | null) => void;
  classes?: Classes;
}) {
  return (
    <form
      className={clsx("__login-container", classes.container)}
      onSubmit={(e) => {
        e.preventDefault();
        const formData = new FormData(e.target as HTMLFormElement);
        fetch(`${AUTH_HOST}/magic-link/login`, {
          credentials: "include",
          method: "POST",
          body: formData,
        })
          .then(checkResponse)
          .then((data) => {
            if (data.success) {
              setError(null);
              setStep(LoginState.OTP);
            } else {
              throw new Error(data.error);
            }
          })
          .catch((err) => {
            setError(`${err}`);
          });
      }}
    >
      <input type="hidden" name="callbackUrl" value={callbackUrl} />
      <input type="hidden" name="identifier" value={email ?? ""} />
      <button
        className={clsx(
          "__login-button",
          "__login-button-primary",
          classes.primaryButton,
        )}
        type="submit"
        data-component="Button$Brevity"
        data-variant="solid"
        data-size="md"
        data-palette="brand"
      >
        Sign up
      </button>
      <section
        className={clsx("__login-footer", styles.footer, classes.footer)}
      >
        <span
          className={clsx("__login-link", styles.link, classes.link)}
          onClick={() => {
            setError(null);
            setStep(LoginState.Login);
          }}
        >
          Back
        </span>
      </section>
    </form>
  );
}

function ConfirmCode({
  email,
  callbackUrl,
  setError,
  setStep,
  disablePasskey,
  classes = {},
}: {
  email: string | null;
  callbackUrl?: string;
  setError: (error: string | null) => void;
  setStep: (step: LoginState) => void;
  disablePasskey?: boolean;
  classes?: Classes;
}) {
  const navigate = useNavigate();
  return (
    <>
      <OTP
        className={clsx("__login-container", classes.container)}
        maxLength={6}
        onComplete={(token) => {
          const formData = new FormData();
          formData.set("identifier", email ?? "");
          formData.set("token", token);
          formData.set("callbackUrl", callbackUrl || "/");
          fetch(`${AUTH_HOST}/magic-link/verify`, {
            method: "POST",
            body: formData,
            credentials: "include",
          })
            .then(checkResponse)
            .then((data) => {
              if (data.success) {
                // done!
                return fetch(`${AUTH_HOST}/me`, {
                  credentials: "include",
                });
              } else {
                throw new Error(data.error);
              }
            })
            .then(checkResponse)
            .then((data) => {
              if (
                !process.env.PREVIEW &&
                browserSupportsWebAuthn() &&
                shouldRegister(data?.authenticators) &&
                !disablePasskey
              ) {
                setError(null);
                setStep(LoginState.RegisterPasskey);
              } else {
                // done, redirect
                navigate(callbackUrl || "/");
              }
            })
            .catch((err) => {
              setError(`${err}`);
            });
        }}
      >
        <div className={styles.slots}>
          <OTPSlot
            index={0}
            className={clsx("__login-otp", styles.otp, classes?.otp)}
          />
          <OTPSlot
            index={1}
            className={clsx("__login-otp", styles.otp, classes?.otp)}
          />
          <OTPSlot
            index={2}
            className={clsx("__login-otp", styles.otp, classes?.otp)}
          />
          <OTPSlot
            index={3}
            className={clsx("__login-otp", styles.otp, classes?.otp)}
          />
          <OTPSlot
            index={4}
            className={clsx("__login-otp", styles.otp, classes?.otp)}
          />
          <OTPSlot
            index={5}
            className={clsx("__login-otp", styles.otp, classes?.otp)}
          />
        </div>
      </OTP>
      <section className={clsx("__login-footer", classes.footer)}>
        <span
          className={clsx("__login-link", styles.link, classes.link)}
          onClick={() => {
            setError(null);
            setStep(LoginState.Login);
          }}
        >
          Back
        </span>
      </section>
    </>
  );
}

export function OTP({ className, ...props }: OTPInputProps) {
  return (
    <OTPInput
      {...props}
      containerClassName={clsx("__OTP", styles.rootOtp, className)}
    />
  );
}
OTP.OTPSlot = OTPSlot;

function OTPSlot({ index, className, ...props }) {
  const inputOTPContext = React.useContext(OTPInputContext);
  const { char, hasFakeCaret, isActive } = inputOTPContext.slots[index];
  return (
    <div
      data-active={isActive || undefined}
      className={clsx("__OTPSlot", styles.rootSlot, className)}
      {...props}
    >
      {char}
      {hasFakeCaret && <FakeCaret />}
    </div>
  );
}

// Emulate a fake textbox caret!
function FakeCaret() {
  return (
    <div className={styles.caret}>
      <div />
    </div>
  );
}

function shouldRegister(authenticators?: any[]) {
  if (!authenticators || !authenticators.length) {
    return true;
  }

  let saved: string[] = [];
  try {
    saved = JSON.parse(localStorage.getItem("__brevity_passkeys") || "[]");
  } catch (err) {
    // ignore
  }

  if (saved.length === 0) {
    return true;
  }

  return !authenticators.some((authenticator) => {
    return saved.includes(authenticator?.id as string);
  });
}

function RegisterPasskey({
  callbackUrl,
  setError,
  setStep,
  classes = {},
}: {
  callbackUrl?: string;
  setError: (error: string | null) => void;
  setStep: (step: LoginState) => void;
  classes?: Classes;
}) {
  const navigate = useNavigate();
  const [supportsWebauthn, setSupportsWebauthn] = React.useState(true);
  React.useEffect(() => {
    setSupportsWebauthn(browserSupportsWebAuthn());
  }, []);
  // React.useEffect(() => {
  //   const ac = new AbortController();
  //   if (!browserSupportsWebAuthnAutofill()) {
  //     return;
  //   }
  //   if (process.env.PREVIEW) {
  //     navigate(callbackUrl || "/");
  //     return;
  //   }
  //   initRegistration(ac.signal, true)
  //     .then(({ verified, credentialID }) => {
  //       if (verified) {
  //         if (credentialID) {
  //           // append to list in local storage
  //           try {
  //             const list = JSON.parse(
  //               localStorage.getItem("__brevity_passkeys") || "[]",
  //             );
  //             list.push(credentialID);
  //             localStorage.setItem("__brevity_passkeys", JSON.stringify(list));
  //           } catch (err) {
  //             // ignore
  //           }
  //         }
  //         navigate(callbackUrl || "/");
  //       }
  //     })
  //     .catch((err) => {
  //       if (!err) return;
  //       if (err.name === "AbortError" || err.name === "NotAllowedError") {
  //         // This error is thrown when the user cancels the operation in the browser
  //         return;
  //       } else if (err.name === "InvalidStateError") {
  //         navigate(callbackUrl || "/");
  //       } else {
  //         console.error(err);
  //         setError(`${err}`);
  //       }
  //     });
  //   return () => ac.abort();
  // }, []);
  return (
    <div className={clsx("__login-container", classes.container)}>
      <button
        className={clsx(
          "__login-button",
          "__login-button-primary",
          classes.primaryButton,
        )}
        disabled={!supportsWebauthn}
        onClick={() => {
          if (process.env.PREVIEW) {
            navigate(callbackUrl || "/");
            return;
          }
          initRegistration()
            .then(({ verified, credentialID }) => {
              if (verified) {
                if (credentialID) {
                  // append to list in local storage
                  try {
                    const list = JSON.parse(
                      localStorage.getItem("__brevity_passkeys") || "[]",
                    );
                    list.push(credentialID);
                    localStorage.setItem(
                      "__brevity_passkeys",
                      JSON.stringify(list),
                    );
                  } catch (err) {
                    // ignore
                  }
                }
                navigate(callbackUrl || "/");
              } else {
                setError("Failed to save passkey");
              }
            })
            .catch((err) => {
              if (!err) return;
              if (err.name === "AbortError" || err.name === "NotAllowedError") {
                // This error is thrown when the user cancels the operation in the browser
                return;
              }
              if (err?.code) {
                if (err.code === "ERROR_CEREMONY_ABORTED") {
                  // This error is thrown when the user cancels the operation in the browser
                  return;
                } else if (err.code === "ERROR_INVALID_DOMAIN") {
                  setError(`Invalid domain`);
                  return;
                } else if (err.code === "ERROR_INVALID_RP_ID") {
                  setError(`Invalid RP ID`);
                  return;
                } else if (err.code === "ERROR_INVALID_USER_ID_LENGTH") {
                  setError(`Invalid user ID length`);
                  return;
                } else if (err.code === "ERROR_MALFORMED_PUBKEYCREDPARAMS") {
                  setError(`Malformed public key credential parameters`);
                  return;
                } else if (err.code === "ERROR_AUTHENTICATOR_GENERAL_ERROR") {
                  setError(`Authenticator general error`);
                  return;
                } else if (
                  err.code ===
                  "ERROR_AUTHENTICATOR_MISSING_DISCOVERABLE_CREDENTIAL_SUPPORT"
                ) {
                  setError(
                    `Authenticator missing discoverable credential support`,
                  );
                  return;
                } else if (
                  err.code ===
                  "ERROR_AUTHENTICATOR_MISSING_USER_VERIFICATION_SUPPORT"
                ) {
                  setError(`Authenticator missing user verification support`);
                  return;
                } else if (
                  err.code === "ERROR_AUTHENTICATOR_PREVIOUSLY_REGISTERED"
                ) {
                  setError(`Authenticator previously registered`);
                  return;
                } else if (
                  err.code ===
                  "ERROR_AUTHENTICATOR_NO_SUPPORTED_PUBKEYCREDPARAMS_ALG"
                ) {
                  setError(
                    `Authenticator no supported public key credential parameters algorithm`,
                  );
                  return;
                } else if (
                  err.code === "ERROR_PASSTHROUGH_SEE_CAUSE_PROPERTY"
                ) {
                  err = err.cause || err;
                }
              }
              if (
                err.message === "Browser does not support WebAuthn autofill" ||
                err.message === "WebAuthn is not supported in this browser"
              ) {
                // This errors match simplewebauthn's error messages
                console.error(err);
                return;
              }
              console.error(err);
              setError(`${err}`);
            });
        }}
        data-component="Button$Brevity"
        data-variant="solid"
        data-size="md"
        data-palette="brand"
      >
        <PasskeyLogo />
        Save a passkey
      </button>
      <Link
        className={clsx(
          "__login-button",
          "__login-button-secondary",
          classes.secondaryButton,
        )}
        to={callbackUrl || "/"}
        data-component="Button$Brevity"
        data-variant="outline"
        data-size="md"
        data-palette="neutral"
      >
        Maybe later
      </Link>
      <section className={clsx("__login-footer", classes.footer)}>
        <a
          className={clsx("__login-link", styles.link, classes.link)}
          href="https://fidoalliance.org/passkeys/#faq"
          rel="noopener noreferrer"
          target="_blank"
        >
          Learn more about passkeys
        </a>
      </section>
    </div>
  );
}
