'use client';

import { StackClientApp, StackProvider, StackTheme } from '@stackframe/stack';
import React, { useMemo, useRef } from 'react';

import { AuthContext } from './AuthProvider';

// Create a singleton StackClientApp instance to prevent multiple initializations
let stackClientAppInstance: StackClientApp<true, string> | null = null;

function getStackClientApp(
  projectId: string,
  publishableClientKey: string,
): StackClientApp<true, string> {
  if (!stackClientAppInstance) {
    // projectId / publishableClientKey are passed explicitly (fetched from the
    // backend at runtime) instead of being read from inlined NEXT_PUBLIC_* env,
    // so the prebuilt image works without build-time configuration.
    stackClientAppInstance = new StackClientApp({
      tokenStore: "nextjs-cookie",
      projectId,
      publishableClientKey,
      urls: {
        afterSignIn: "/after-sign-in"
      }
    });
  }
  return stackClientAppInstance;
}

interface StackProviderWrapperProps {
  children: React.ReactNode;
  projectId: string;
  publishableClientKey: string;
}

function StackAuthContextProvider({
  children,
  app,
}: {
  children: React.ReactNode;
  app: StackClientApp<true, string>;
}) {
  const stackUser = app.useUser();

  // Store user in ref for callbacks to access latest value without creating new callbacks
  const userRef = useRef(stackUser);
  userRef.current = stackUser;

  // Stable callbacks that use ref to access current user
  const getAccessToken = React.useCallback(async () => {
    const user = userRef.current;
    if (!user) {
      throw new Error('User not authenticated');
    }
    const authJson = await user.getAuthJson();
    if (!authJson.accessToken) {
      throw new Error('No access token available');
    }
    return authJson.accessToken;
  }, []);

  const redirectToLogin = React.useCallback(() => {
    if (typeof window !== 'undefined') {
      window.location.href = '/handler/sign-in';
    }
  }, []);

  const logout = React.useCallback(async () => {
    // Redirect to Stack's server-side sign-out handler instead of calling
    // signOut() client-side. Client-side signOut triggers an internal
    // re-render that causes a hooks ordering violation in Stack's components.
    if (typeof window !== 'undefined') {
      window.location.href = '/handler/sign-out';
    }
  }, []);

  const getSelectedTeam = React.useCallback(() => {
    return userRef.current?.selectedTeam ?? null;
  }, []);

  const listPermissions = React.useCallback(async (team?: unknown) => {
    const user = userRef.current;
    if (!user?.listPermissions) {
      return [];
    }
    const targetTeam = team || user.selectedTeam;
    if (!targetTeam) {
      return [];
    }
    try {
      const perms = await user.listPermissions(targetTeam);
      return Array.isArray(perms) ? perms : [];
    } catch {
      return [];
    }
  }, []);

  const contextValue = useMemo(() => ({
    user: stackUser,
    isAuthenticated: !!stackUser,
    loading: false,
    getAccessToken,
    redirectToLogin,
    logout,
    provider: 'stack' as const,
    getSelectedTeam,
    listPermissions,
  }), [stackUser, getAccessToken, redirectToLogin, logout, getSelectedTeam, listPermissions]);

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

export function StackProviderWrapper({ children, projectId, publishableClientKey }: StackProviderWrapperProps) {
  const stackClientApp = getStackClientApp(projectId, publishableClientKey);

  return (
    <StackProvider app={stackClientApp}>
      <StackTheme>
        <StackAuthContextProvider app={stackClientApp}>
          {children}
        </StackAuthContextProvider>
      </StackTheme>
    </StackProvider>
  );
}
