import { Button, DialogDisclosure, DialogDismiss } from "@ariakit/react";
import { useCallback, useMemo, useState, type ReactNode } from "react";
import { number } from "superstruct";
import useImmutableSWR from "swr/immutable";
import { Link } from "wouter";
import { useAppConfig, useClient } from "../AppConfig/AppConfig.tsx";
import { Failure, Pending, Success } from "../async-op.ts";
import { interactiveText } from "../common.css.ts";
import { createSafeStorage } from "../createSafeStorage.ts";
import { DialogTrigger } from "../DialogTrigger/index.tsx";
import {
  LetterheadFooter,
  LetterheadHeading,
  LetterheadParagraph,
} from "../Letterhead/index.tsx";
import { button } from "../Letterhead/style.css.ts";
import { LoadingIndicator } from "../LoadingIndicator.tsx";
import { ModalDialog } from "../ModalDialog/index.tsx";
import { swrResponseToResult } from "../result/swr.ts";
import { useCurrentUser } from "../store/index.tsx";
import type { FailurePayload } from "../types.ts";
import { useIsInstalled } from "../use-is-installed.ts";
import addToDockUrl from "./addToDock.svg";
import addToHomeScreenUrl from "./addToHomeScreen.svg";
import safariUrl from "./safari.svg";
import shareIconUrl from "./shareIcon.svg";
import * as css from "./style.css.ts";

function InstallAppDialog({ isMobile }: { isMobile?: boolean }) {
  const config = useAppConfig();

  const shareIcon = <img src={shareIconUrl} alt="" className={css.icon} />;
  const addIcon = <img src={addToHomeScreenUrl} alt="" className={css.icon} />;
  const addToDockIcon = <img src={addToDockUrl} alt="" className={css.icon} />;

  const steps = [
    <>
      Open the <strong>Share</strong> menu {shareIcon}
    </>,

    isMobile ? (
      <>
        Tap <strong>Add to Home Screen</strong> {addIcon}
      </>
    ) : (
      <>
        Click <strong>Add to Dock</strong> {addToDockIcon}
      </>
    ),

    isMobile ? (
      <>
        Tap <strong>Add</strong> in the top right corner
      </>
    ) : (
      <>
        Click <strong>Add</strong>
      </>
    ),
  ];

  return (
    <ModalDialog size="small" className={css.dialog}>
      <img
        src={config.app.icon}
        alt=""
        width="512"
        height="512"
        className={css.appIcon}
      />

      <LetterheadHeading className={css.heading}>
        Install {config.app.name}
      </LetterheadHeading>

      <LetterheadParagraph className={css.intro}>
        To install this app, follow these 3 steps:
      </LetterheadParagraph>

      <ol className={css.steps}>
        {steps.map((step, index) => {
          return (
            <li className={css.step} key={index}>
              {step}
            </li>
          );
        })}
      </ol>

      <div>
        <DialogDismiss className={interactiveText}>Dismiss</DialogDismiss>
      </div>
    </ModalDialog>
  );
}

function SafariPrompt(props: { onDismiss: () => void; isMobile?: boolean }) {
  const { hrefs } = useAppConfig();

  const delete_your_local_data = <strong>delete your local data</strong>;

  const install_the_app = <strong>install the app</strong>;

  const creating_an_account = <strong>creating an account</strong>;

  return (
    <div className={css.safariPrompt}>
      <div className={css.safariPromptHeader}>
        <img
          src={safariUrl}
          alt=""
          width="64"
          height="64"
          className={css.safariLogo}
        />

        <LetterheadHeading className={css.emptyStateHeading}>
          Heads up Safari users!
        </LetterheadHeading>
      </div>
      <LetterheadParagraph>
        Safari — unlike other browsers — will {delete_your_local_data} after 7
        days of inactivity, unless you have installed the app.
      </LetterheadParagraph>
      <LetterheadParagraph>
        To prevent accidental data loss, you can either {install_the_app}, make
        sure all your data is safely backed up by {creating_an_account}, or use
        a different browser.
      </LetterheadParagraph>

      <div className={css.safariPromptActions}>
        <DialogTrigger>
          <InstallAppDialog isMobile={props.isMobile} />

          <DialogDisclosure className={button()}>Install App</DialogDisclosure>
        </DialogTrigger>

        <Link className={button()} href={hrefs.join()}>
          Create Account
        </Link>
      </div>

      <div className={css.safariDismissArea}>
        <LetterheadParagraph align="center">
          <Button className={interactiveText} onClick={props.onDismiss}>
            I don’t mind if my data gets deleted
          </Button>
        </LetterheadParagraph>
      </div>

      <LetterheadFooter />
    </div>
  );
}

function useUserAgent(props: { performFetch: boolean }) {
  const client = useClient();

  const swr = useImmutableSWR(
    props.performFetch ? "/ua" : null,
    useCallback(() => client.userAgent(), []),
  );

  return swrResponseToResult(swr);
}

const storage = createSafeStorage({ safariInstallPromptDismissedAt: number() });

function useInstallPromptState() {
  const [dismissedAt, setState] = useState(() =>
    storage.getItem("safariInstallPromptDismissedAt"),
  );

  return {
    dismissedAt,
    setDismissed: useCallback(() => {
      const timestamp = Date.now();

      setState(timestamp);
      storage.setItem("safariInstallPromptDismissedAt", timestamp);
    }, []),
  };
}

type SafariCheckResult =
  | Success<{ showPrompt: boolean; isMobile?: boolean }>
  | Failure<FailurePayload>
  | Pending;

function useSafariCheck({ performCheck }: { performCheck: boolean }) {
  const isAuthenticated = !!useCurrentUser();
  const isInstalled = useIsInstalled();
  const { dismissedAt, setDismissed } = useInstallPromptState();
  const installedOrDismissed = isInstalled || !!dismissedAt;
  const userAgentResult = useUserAgent({
    performFetch: performCheck && !isAuthenticated && !installedOrDismissed,
  });

  const result = useMemo((): SafariCheckResult => {
    return isAuthenticated || installedOrDismissed || !performCheck
      ? // If the safari prompt was previously dismissed, the app is already
        // installed, or check was disabled explicitly, we skip the prompt.
        new Success({ showPrompt: false })
      : // Otherwise, we want to check whether the name includes Safari (could be
        // Safari or Safari Mobile), and we also report whether this is a mobile
        // device to allow for more targeted install instructions for the user.
        userAgentResult.mapSuccess((value) => {
          return {
            showPrompt: value.browser?.name?.includes("Safari") ?? false,
            isMobile: value.device?.type === "mobile",
          };
        });
  }, [installedOrDismissed, userAgentResult, performCheck, isAuthenticated]);

  return { result, setDismissed };
}

/**
 * Checks whether the browser which is running the app is Safari/Safari Mobile
 * and warns the user that their data might be deleted due to inactivity.
 *
 * The warning will be shown if:
 *
 * - The check has not been explicitly disabled via `performCheck: false`
 * - The warning has not been previously dismissed (as reported by localStorage)
 * - The app is not installed (Safari behaves differently in that case)
 * - The user is not already logged in (as data will be safely backed up that way)
 */
export function SafariCheck(props: {
  children: ReactNode;

  /**
   * Optionally opt out of the check.
   *
   * This can be useful in cases where, e.g. the user already has some content
   * in the app and we want to avoid checking the user agent, but it is
   * impractical to entirely avoid rendering this component.
   */
  performCheck?: boolean;
}) {
  const { result, setDismissed } = useSafariCheck({
    performCheck: props.performCheck ?? true,
  });

  if (result.isPending) {
    return (
      <div className={css.container}>
        <div className={css.compatCheckLoaderContainer}>
          <LoadingIndicator className={css.indicator} />

          {/* Intentionally not using Letterhead Paragraph to pick up the default
            font size and style. */}
          <p className={css.compatCheckLoaderText}>Checking compatibility...</p>
        </div>
      </div>
    );
  }

  // Get the success value. Note that if the UA check is a failure we don't
  // want to bother the user with any failure messages. In that case, we just
  // keep rolling as if the showPrompt was false.
  const value = result.valueOrNull();
  if (value?.showPrompt) {
    return (
      <div className={css.container}>
        <SafariPrompt onDismiss={setDismissed} isMobile={value.isMobile} />
      </div>
    );
  }

  // We're all good, render children.
  return <>{props.children}</>;
}
