import clsx from "clsx";
import * as React from "react";
import { initRegistration } from "../../utils";
import {
  AUTH_HOST,
  browserSupportsWebAuthn,
  checkResponse,
} from "../../utils/webauthn";
import { PasskeyLogo } from "../Login";
import * as styles from "./styles.module.css";

type Authenticator = {
  id: string;
  name: string;
  lastUsed: string;
};

type Classes = {
  iconButton?: string;
  button?: string;
  li?: string;
  editable?: string;
  input?: string;
};

export function Passkeys({
  className,
  classes = {},
  ...props
}: {
  className?: string;
  classes: Classes;
}) {
  const [loading, setLoading] = React.useState(true);
  const [passkeys, setPasskeys] = React.useState<Authenticator[]>([]);
  const [supportsWebauthn, setSupportsWebauthn] = React.useState(true);
  React.useEffect(() => {
    setSupportsWebauthn(browserSupportsWebAuthn());
  }, []);
  const reload = React.useCallback(() => {
    fetch(`${AUTH_HOST}/me`, {
      credentials: "include",
    })
      .then(checkResponse)
      .then((data) => {
        setPasskeys(data.authenticators);
      })
      .finally(() => {
        setLoading(false);
      });
  }, []);
  React.useEffect(() => {
    reload();
  }, [reload]);
  return (
    <div className={clsx("__passkeys", className)} {...props}>
      {loading ? (
        <div>Loading...</div>
      ) : (
        <ul className={clsx("__passkeys-ul", styles.ul)}>
          {passkeys.map((passkey) => (
            <li
              className={clsx("__passkeys-li", classes.li, styles.li)}
              key={passkey.id}
            >
              <Editable
                classes={classes}
                name={passkey.name}
                lastUsed={passkey.lastUsed}
                onChange={(value) => {
                  fetch(`${AUTH_HOST}/webauthn/register/${passkey.id}`, {
                    method: "PUT",
                    credentials: "include",
                    headers: {
                      "Content-Type": "application/json",
                    },
                    body: JSON.stringify({
                      name: value,
                    }),
                  })
                    .then(checkResponse)
                    .then(reload);
                }}
              />
              <button
                className={clsx("__passkeys-iconButton", classes.iconButton)}
                type="button"
                aria-label="Revoke passkey"
                onClick={() => {
                  fetch(`${AUTH_HOST}/webauthn/register/${passkey.id}`, {
                    method: "DELETE",
                    credentials: "include",
                  })
                    .then(checkResponse)
                    .then(reload);
                }}
              >
                <svg
                  className={clsx("__passkeys-iconButton-icon", styles.icon)}
                  xmlns="http://www.w3.org/2000/svg"
                  width="48"
                  height="48"
                  viewBox="0 -960 960 960"
                >
                  <path
                    fill="currentColor"
                    d="M249-207l-42-42 231-231-231-231 42-42 231 231 231-231 42 42-231 231 231 231-42 42-231-231-231 231z"
                  ></path>
                </svg>
              </button>
            </li>
          ))}
        </ul>
      )}
      <button
        type="button"
        className={clsx("__passskeys-button", classes.button)}
        disabled={!supportsWebauthn}
        onClick={() => {
          if (process.env.PREVIEW) {
            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
                  }
                }
                reload();
              } else {
                console.error("Could not verify passkey.");
              }
            })
            .catch((err) => {
              if (err.name === "AbortError") {
                // This error is thrown when the user cancels the operation in the browser
                return;
              } else if (err.name === "InvalidStateError") {
                console.error("This device has already been registered.");
              } else {
                console.error(`${err}`);
              }
            });
        }}
      >
        <PasskeyLogo />
        Add Passkey
      </button>
    </div>
  );
}

function Editable({
  name,
  lastUsed,
  onChange,
  classes = {},
}: {
  name: string;
  lastUsed: string;
  onChange: (value: string) => void;
  classes: Classes;
}) {
  const [value, setValue] = React.useState(name);
  const [editing, setEditing] = React.useState(false);
  const relativeTime = React.useCallback((date) => {
    if (!date) return "";
    const formatter = new Intl.RelativeTimeFormat(undefined, {
      numeric: "auto",
    });
    const DIVISIONS = [
      { amount: 60, name: "seconds" },
      { amount: 60, name: "minutes" },
      { amount: 24, name: "hours" },
      { amount: 7, name: "days" },
      { amount: 4.34524, name: "weeks" },
      { amount: 12, name: "months" },
      { amount: Number.POSITIVE_INFINITY, name: "years" },
    ];
    let duration = (date.getTime() - Date.now()) / 1000;
    for (let i = 0; i <= DIVISIONS.length; i++) {
      const division = DIVISIONS[i];
      if (Math.abs(duration) < division.amount) {
        return formatter.format(Math.round(duration), division.name as any);
      }
      duration /= division.amount;
    }
  }, []);
  return (
    <div className={clsx("__passkeys-editable", classes.editable)}>
      {editing ? (
        <input
          autoFocus
          className={clsx("__passkeys-editable-input", classes.input)}
          type="text"
          value={value}
          onChange={(e) => setValue(e.target.value)}
          onBlur={() => {
            setEditing(false);
            onChange(value);
          }}
        />
      ) : (
        <span
          className={clsx("__passkeys-editable-value", styles.value)}
          onClick={() => setEditing(true)}
        >
          {name} (used {relativeTime(new Date(lastUsed))})
          <svg
            className={styles.icon}
            xmlns="http://www.w3.org/2000/svg"
            width="16"
            height="16"
            viewBox="0 -960 960 960"
          >
            <path d="M180-180h44l443-443-44-44-443 443v44zm614-486L666-794l42-42q17-17 42-17t42 17l44 44q17 17 17 42t-17 42l-42 42zm-42 42L248-120H120v-128l504-504 128 128zm-107-21l-22-22 44 44-22-22z"></path>
          </svg>
        </span>
      )}
    </div>
  );
}
