import { Form, useStoreState } from "@ariakit/react";
import { type Dispatch, type SetStateAction, useState } from "react";
import { Link, useSearchParams } from "wouter";
import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx";
import { cx } from "../class-names.ts";
import { interactiveText } from "../common.css.ts";
import { getSubmitFailureMessage } from "../failureMessages.ts";
import {
  Letterhead,
  LetterheadHeading,
  LetterheadParagraph,
  LetterheadSubmitButton,
} from "../Letterhead/index.tsx";
import { button } from "../Letterhead/style.css.ts";
import {
  InputsStack,
  LetterheadCheckboxField,
  LetterheadFormActions,
  LetterheadHeader,
  LetterheadSubmitError,
  LetterheadTextField,
} from "../LetterheadForm/index.tsx";
import { useForm } from "../use-form.ts";
import { validEmail, validPassword } from "../validations.ts";
import { AlreadyLoggedInView } from "./AlreadyLoggedInView.tsx";
import { FailureFallbackView } from "./FailureFallbackView.tsx";
import { LoadingView } from "./LoadingView.tsx";
import { NoConnectionView } from "./NoConnectionView.tsx";
import type { DefaultFormValues } from "./types.ts";
import { useFetchCurrentUser } from "./useFetchCurrentUser.tsx";
import { useRedirectPath } from "./useRedirectPath.ts";

type SetStep = Dispatch<SetStateAction<JoinStep>>;

type InitialStepProps = {
  setStep: SetStep;
  defaultValues?: DefaultFormValues;
};

function InitialStep(props: InitialStepProps) {
  const [params] = useSearchParams();
  const { client, placeholders, hrefs } = useAppConfig();
  const { defaultValues, setStep } = props;

  const { form, submitName } = useForm({
    defaultValues: {
      email: defaultValues?.email ?? "",
      password: "",
      acceptedTos: false,
      subscribedToNewsletter: false,
    },
    validate: {
      email: validEmail,
      password: validPassword,
    },
    async onSubmit({ values }) {
      const op = await client.join(values);

      return op.mapFailure((failure) => {
        return getSubmitFailureMessage(failure, {
          409: "🙀 This email address is already in use. Please choose a different one, or log in.",
        });
      });
    },
    onSuccess(response, form) {
      setStep({
        type: "SUBMIT_CODE",
        tokenId: response.tokenId,
        email: form.values.email,
      });
    },
  });

  const emailValue = useStoreState(form, (s) => s.values.email);

  return (
    <Letterhead>
      <LetterheadHeader>
        <LetterheadHeading>Join Indie Tabletop Club</LetterheadHeading>
        <LetterheadParagraph>
          Enter your email and choose a strong password. We will send you a
          one-time code to verify your account.
        </LetterheadParagraph>
      </LetterheadHeader>

      <Form store={form} resetOnSubmit={false}>
        <InputsStack>
          <LetterheadTextField
            name={form.names.email}
            label="Email"
            type="email"
            placeholder={placeholders.email}
            autoComplete="username"
            required
          />

          <LetterheadTextField
            name={form.names.password}
            label="Password"
            type="password"
            placeholder="Choose a strong password"
            hint="Must be at least 8 characters"
            autoComplete="new-password"
            required
          />

          <LetterheadCheckboxField
            name={form.names.subscribedToNewsletter}
            label={
              <>
                Subscribe to <em>The Changelog</em>, an at-most-once-a-month
                newsletter about updates and new releases. You can unsubscribe
                any time with one click.
              </>
            }
          />

          <LetterheadCheckboxField
            name={form.names.acceptedTos}
            required
            label={
              <>
                I accept the{" "}
                <a
                  target="_blank"
                  rel="noreferrer noopener"
                  href={hrefs.terms({ content: "join" })}
                  className={interactiveText}
                >
                  Terms of Service
                </a>
                .
              </>
            }
          />
        </InputsStack>

        <LetterheadFormActions>
          <LetterheadSubmitError name={submitName} />
          <LetterheadSubmitButton>Join</LetterheadSubmitButton>

          <LetterheadParagraph align="start">
            {"Have an existing account? "}
            <Link
              {...cx(interactiveText)}
              href={hrefs.login({
                redirectTo: params.get("redirectTo") ?? undefined,
                backTo: params.get("backTo") ?? undefined,
              })}
              state={{ emailValue }}
            >
              Log in
            </Link>
            {"."}
          </LetterheadParagraph>
        </LetterheadFormActions>
      </Form>
    </Letterhead>
  );
}

type SubmitCodeStepProps = {
  tokenId: string;
  setStep: SetStep;
  email: string;
};

function SubmitCodeStep(props: SubmitCodeStepProps) {
  const { tokenId, email, setStep } = props;
  const client = useClient();

  const { form, submitName } = useForm({
    defaultValues: {
      code: "",
    },
    async onSubmit({ values }) {
      const op = await client.verifyUser({ ...values, tokenId });

      return op.mapFailure((failure) => {
        return getSubmitFailureMessage(failure, {
          404: "🚫 This code is incorrect or expired. Please try again.",
        });
      });
    },
    onSuccess() {
      setStep({ type: "SUCCESS" });
    },
  });

  return (
    <Form store={form} resetOnSubmit={false}>
      <Letterhead>
        <LetterheadHeader>
          <LetterheadHeading>Verify account</LetterheadHeading>
          <LetterheadParagraph>
            We've sent a one-time code to <em>{email}</em>. Please, enter the
            code in the field below to verify your account.
          </LetterheadParagraph>
        </LetterheadHeader>

        <LetterheadTextField
          name={form.names.code}
          label="Code"
          placeholder="E.g. 123123"
          autoComplete="one-time-code"
          required
        />

        <LetterheadFormActions>
          <LetterheadSubmitError name={submitName} />
          <LetterheadSubmitButton>Verify</LetterheadSubmitButton>
        </LetterheadFormActions>
      </Letterhead>
    </Form>
  );
}

function SuccessStep() {
  const { hrefs } = useAppConfig();
  const redirectPath = useRedirectPath();

  return (
    <Letterhead>
      <LetterheadHeader>
        <LetterheadHeading>Success!</LetterheadHeading>
        <LetterheadParagraph>
          Your Indie Tabletop Club account has been verified, yay!
        </LetterheadParagraph>
      </LetterheadHeader>

      <Link href={redirectPath ?? hrefs.dashboard()} {...cx(button())}>
        {redirectPath ? "Continue" : "Go to dashboard"}
      </Link>
    </Letterhead>
  );
}

type JoinStep =
  | { type: "INITIAL" }
  | { type: "SUBMIT_CODE"; tokenId: string; email: string }
  | { type: "SUCCESS" };

type JoinFlowProps = {
  defaultValues?: DefaultFormValues;
};

function JoinFlow(props: JoinFlowProps) {
  const [step, setStep] = useState<JoinStep>({ type: "INITIAL" });

  switch (step.type) {
    case "INITIAL": {
      return <InitialStep {...props} setStep={setStep} />;
    }

    case "SUBMIT_CODE": {
      return <SubmitCodeStep {...step} setStep={setStep} />;
    }

    case "SUCCESS": {
      return <SuccessStep />;
    }
  }
}

export type JoinCardProps = {
  defaultValues?: DefaultFormValues;
};

/**
 * Allows the user to join Indie Tabletop Club.
 *
 * Will automatically use the `redirectTo` query param value as the redirect
 * location once the user creates and verifies their account.
 *
 * Otherwise a success screen will be shown and the user will be directed to
 * the dashboard.
 */
export function JoinCard(props: JoinCardProps) {
  const { result, latestAttemptTs, reload } = useFetchCurrentUser();

  return result.unpack(
    (currentUser) => {
      return <AlreadyLoggedInView currentUser={currentUser} reload={reload} />;
    },
    (failure) => {
      if (failure.type === "API_ERROR" && failure.code === 401) {
        return <JoinFlow {...props} />;
      }

      if (failure.type === "NETWORK_ERROR") {
        return (
          <NoConnectionView
            latestAttemptTs={latestAttemptTs}
            onRetry={() => reload()}
          />
        );
      }

      return <FailureFallbackView />;
    },
    () => {
      return <LoadingView />;
    },
  );
}
