import {
  createContext,
  useCallback,
  useContext,
  useEffect,
  useMemo,
  useState,
  type ReactNode,
} from "react";
import { useToast } from "../components/Toast";
import { useI18n } from "../i18n";
import { fetchWalletMe, loginWithWallet } from "./api";
import { signInWithInjectedWallet, type WalletKind } from "./injected";
import { clearWalletToken, readWalletToken, writeWalletToken } from "./token";

type WalletStatus = "loading" | "loggedOut" | "loggedIn";

type Translate = (key: string) => string;

function walletErrorMessage(error: unknown, t: Translate): string {
  if (!(error instanceof Error)) return t("walletLoginFailed");
  return t(error.message) || t("walletLoginFailed");
}

type WalletContextValue = {
  address: string | null;
  token: string | null;
  status: WalletStatus;
  loginModalOpen: boolean;
  openLoginModal: () => void;
  closeLoginModal: () => void;
  signInInjected: (kind?: WalletKind) => Promise<void>;
  completeLogin: (token: string, wallet: string) => void;
  loginAddress: (address: string) => Promise<void>;
  logout: () => void;
};

const WalletContext = createContext<WalletContextValue | null>(null);

export function shortenAddress(address: string): string {
  if (address.length <= 12) return address;
  return `${address.slice(0, 6)}...${address.slice(-4)}`;
}

export function WalletProvider({ children }: { children: ReactNode }) {
  const { t } = useI18n();
  const { showToast } = useToast();
  const [token, setToken] = useState<string | null>(() => readWalletToken());
  const [address, setAddress] = useState<string | null>(null);
  const [status, setStatus] = useState<WalletStatus>(
    token ? "loading" : "loggedOut",
  );
  const [loginModalOpen, setLoginModalOpen] = useState(false);

  useEffect(() => {
    let cancelled = false;
    if (!token) {
      setStatus("loggedOut");
      setAddress(null);
      return;
    }

    setStatus("loading");
    fetchWalletMe(token)
      .then((me) => {
        if (cancelled) return;
        setAddress(me.wallet);
        setStatus("loggedIn");
      })
      .catch(() => {
        if (cancelled) return;
        clearWalletToken();
        setToken(null);
        setAddress(null);
        setStatus("loggedOut");
      });

    return () => {
      cancelled = true;
    };
  }, [token]);

  const completeLogin = useCallback(
    (nextToken: string, wallet: string) => {
      writeWalletToken(nextToken);
      setToken(nextToken);
      setAddress(wallet);
      setStatus("loggedIn");
      setLoginModalOpen(false);
      showToast(t("walletLoginSuccess"));
    },
    [showToast, t],
  );

  const loginAddress = useCallback(
    async (walletAddress: string) => {
      const res = await loginWithWallet(walletAddress);
      completeLogin(res.token, res.wallet);
    },
    [completeLogin],
  );

  const signInInjected = useCallback(
    async (kind?: WalletKind) => {
      try {
        const res = await signInWithInjectedWallet(kind);
        completeLogin(res.token, res.wallet);
      } catch (error) {
        showToast(walletErrorMessage(error, t), "error");
        throw error;
      }
    },
    [completeLogin, showToast, t],
  );

  const logout = useCallback(() => {
    clearWalletToken();
    setToken(null);
    setAddress(null);
    setStatus("loggedOut");
    showToast(t("walletDisconnected"));
  }, [showToast, t]);

  const value = useMemo<WalletContextValue>(
    () => ({
      address,
      token,
      status,
      loginModalOpen,
      openLoginModal: () => setLoginModalOpen(true),
      closeLoginModal: () => setLoginModalOpen(false),
      signInInjected,
      completeLogin,
      loginAddress,
      logout,
    }),
    [
      address,
      completeLogin,
      loginModalOpen,
      loginAddress,
      logout,
      signInInjected,
      status,
      token,
    ],
  );

  return (
    <WalletContext.Provider value={value}>{children}</WalletContext.Provider>
  );
}

export function useWallet() {
  const ctx = useContext(WalletContext);
  if (!ctx) throw new Error("useWallet must be used within WalletProvider");
  return ctx;
}
