'use client';
/**
 * WalletAuthContext — Multi-wallet connection for Vaultfire Agent Hub.
 *
 * Supports: Coinbase Wallet, Base Wallet, MetaMask, WalletConnect.
 * The app is fully public. Wallet connection is optional and only required
 * for actions that need signing (register agent, create bond, claim VNS name).
 */
import React, { createContext, useContext, useState, useEffect, useCallback, useRef } from 'react';
import type { WalletOption } from '../components/WalletModal';

/**
 * Minimal EIP-1193 shape. The provider that actually established the session is
 * retained so callers never have to guess at `window.ethereum` — a Coinbase or
 * WalletConnect session has no injected provider to fall back to.
 */
export interface Eip1193Provider {
  request: (args: { method: string; params?: unknown[] | object }) => Promise<any>;
  on?: (event: string, handler: (...args: any[]) => void) => void;
  removeListener?: (event: string, handler: (...args: any[]) => void) => void;
  isCoinbaseWallet?: boolean;
  isMetaMask?: boolean;
}

export type ProviderSource = 'injected' | 'coinbase' | 'walletconnect' | null;

function parseChainId(value: unknown): number | null {
  if (typeof value === 'number') {
    return Number.isSafeInteger(value) && value >= 0 ? value : null;
  }
  if (typeof value !== 'string') return null;
  const trimmed = value.trim();
  if (!/^(?:0x[0-9a-f]+|[0-9]+)$/i.test(trimmed)) return null;
  const parsed = Number(trimmed);
  return Number.isSafeInteger(parsed) && parsed >= 0 ? parsed : null;
}

function isEvmAddress(value: unknown): value is string {
  return typeof value === 'string' && /^0x[0-9a-fA-F]{40}$/.test(value.trim());
}

interface WalletAuthState {
  isUnlocked: boolean;
  address: string | null;
  chainId: number | null;
  isRestoring: boolean;
  activeWallet: WalletOption | null;
  connect: () => void;
  connectWallet: (wallet: WalletOption) => Promise<void>;
  logout: () => void;
  modalOpen: boolean;
  setModalOpen: (open: boolean) => void;
  connecting: WalletOption | null;
  unlock: (pk: string, mnemonic: string, address: string) => Promise<void>;
  /** The EIP-1193 provider that established the current session, if any. */
  provider: Eip1193Provider | null;
  /** Where `provider` came from. Never inferred from `window.ethereum`. */
  providerSource: ProviderSource;
  /** Same value as `provider`, for callers that need it outside render. */
  getProvider: () => Eip1193Provider | null;
  /**
   * Chain switch on the retained provider only. Returns a structured result
   * instead of throwing, and never falls back to another provider.
   */
  requestChainSwitch: (chainIdHex: string) => Promise<{ ok: boolean; error?: string }>;
}

const WalletAuthContext = createContext<WalletAuthState>({
  isUnlocked: false,
  address: null,
  chainId: null,
  isRestoring: false,
  activeWallet: null,
  connect: () => {},
  connectWallet: async () => {},
  logout: () => {},
  modalOpen: false,
  setModalOpen: () => {},
  connecting: null,
  unlock: async () => {},
  provider: null,
  providerSource: null,
  getProvider: () => null,
  requestChainSwitch: async () => ({ ok: false, error: 'No wallet provider connected' }),
});

async function initCoinbaseWallet(): Promise<any> {
  const { CoinbaseWalletSDK } = await import('@coinbase/wallet-sdk');
  const sdk = new CoinbaseWalletSDK({
    appName: 'Vaultfire Agent Hub',
    appLogoUrl: 'https://theloopbreaker.com/favicon.ico',
  });
  return sdk.makeWeb3Provider({ options: 'smartWalletOnly' });
}

async function initWalletConnect(): Promise<any> {
  const { default: EthereumProvider } = await import('@walletconnect/ethereum-provider');
  const provider = await EthereumProvider.init({
    projectId: process.env.NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID || 'a4e7d6e2c7b3f1d0e8a9b2c3d4e5f6a7',
    chains: [8453],
    optionalChains: [1, 43114],
    showQrModal: true,
    metadata: {
      name: 'Vaultfire Agent Hub',
      description: 'The Trust Layer for the Agentic Web',
      url: 'https://theloopbreaker.com',
      icons: ['https://theloopbreaker.com/favicon.ico'],
    },
  });
  await provider.connect();
  return provider;
}

export function WalletAuthProvider({ children }: { children: React.ReactNode }) {
  const [address, setAddress] = useState<string | null>(null);
  const [chainId, setChainId] = useState<number | null>(null);
  const [isRestoring, setIsRestoring] = useState(true);
  const [activeWallet, setActiveWallet] = useState<WalletOption | null>(null);
  const [modalOpen, setModalOpen] = useState(false);
  const [connecting, setConnecting] = useState<WalletOption | null>(null);
  const [provider, setProvider] = useState<Eip1193Provider | null>(null);
  const [providerSource, setProviderSource] = useState<ProviderSource>(null);
  // Mirror of `provider` for callbacks that must not close over stale state.
  const providerRef = useRef<Eip1193Provider | null>(null);
  const providerListenersRef = useRef<{
    provider: Eip1193Provider;
    onAccountsChanged: (accounts: unknown) => void;
    onChainChanged: (chainId: unknown) => void;
  } | null>(null);
  // Invalidates async restoration, connection, and chain-switch work when the
  // active provider/session changes.
  const sessionEpochRef = useRef(0);

  const retainProvider = useCallback((wp: Eip1193Provider | null, source: ProviderSource) => {
    providerRef.current = wp;
    setProvider(wp);
    setProviderSource(wp ? source : null);
  }, []);

  const unsubscribeProvider = useCallback(() => {
    const current = providerListenersRef.current;
    if (!current) return;
    current.provider.removeListener?.('accountsChanged', current.onAccountsChanged);
    current.provider.removeListener?.('chainChanged', current.onChainChanged);
    providerListenersRef.current = null;
  }, []);

  /**
   * Keep address/chain in sync for non-injected sessions (Coinbase smart
   * wallet, WalletConnect), which emit their events on their own provider.
   */
  const subscribeProvider = useCallback((wp: Eip1193Provider) => {
    unsubscribeProvider();
    const onAccountsChanged = (accounts: unknown) => {
      // A stale provider must never overwrite the session selected later.
      if (providerRef.current !== wp) return;
      const nextAddress = Array.isArray(accounts) && typeof accounts[0] === 'string' ? accounts[0] : null;
      if (!nextAddress) {
        sessionEpochRef.current += 1;
        setAddress(null); setChainId(null); setActiveWallet(null);
        unsubscribeProvider();
        retainProvider(null, null);
        return;
      }
      setAddress(nextAddress);
    };
    const onChainChanged = (chain: unknown) => {
      // A stale provider must never overwrite the selected provider's chain.
      if (providerRef.current !== wp) return;
      setChainId(parseChainId(chain));
    };
    providerListenersRef.current = { provider: wp, onAccountsChanged, onChainChanged };
    wp.on?.('accountsChanged', onAccountsChanged);
    wp.on?.('chainChanged', onChainChanged);
  }, [retainProvider, unsubscribeProvider]);

  useEffect(() => {
    if (typeof window === 'undefined') { setIsRestoring(false); return; }
    const eth = (window as any).ethereum;
    if (!eth) { setIsRestoring(false); return; }
    const restoreEpoch = sessionEpochRef.current;
    let disposed = false;
    eth.request({ method: 'eth_accounts' })
      .then((accounts: string[]) => {
        // Do not let a delayed injected-provider read replace a provider that
        // was explicitly selected while restoration was in flight.
        if (disposed || sessionEpochRef.current !== restoreEpoch || providerRef.current) return;
        if (accounts && typeof accounts[0] === 'string') {
          setAddress(accounts[0]);
          // Restored session came from the injected provider, so retaining it
          // here is accurate rather than a fallback guess.
          retainProvider(eth as Eip1193Provider, 'injected');
          subscribeProvider(eth as Eip1193Provider);
          if (eth.isCoinbaseWallet) setActiveWallet('coinbase');
          else if (eth.isMetaMask) setActiveWallet('metamask');
          else setActiveWallet('metamask');
          eth.request({ method: 'eth_chainId' })
            .then((cid: unknown) => {
              if (!disposed && sessionEpochRef.current === restoreEpoch && providerRef.current === eth) {
                setChainId(parseChainId(cid));
              }
            }).catch(() => {});
        }
      })
      .catch(() => {})
      .finally(() => { if (!disposed) setIsRestoring(false); });
    return () => { disposed = true; unsubscribeProvider(); };
  }, [retainProvider, subscribeProvider, unsubscribeProvider]);

  const connectWallet = useCallback(async (wallet: WalletOption) => {
    const connectionEpoch = sessionEpochRef.current + 1;
    sessionEpochRef.current = connectionEpoch;
    setConnecting(wallet);
    try {
      let wp: any;
      let source: ProviderSource;
      let selectedWallet: WalletOption;
      let accounts: string[];
      let cid: unknown;
      if (wallet === 'coinbase' || wallet === 'base') {
        wp = await initCoinbaseWallet();
        accounts = await wp.request({ method: 'eth_requestAccounts' });
        cid = await wp.request({ method: 'eth_chainId' });
        source = 'coinbase';
        selectedWallet = wallet;
      } else if (wallet === 'metamask') {
        const eth = (window as any).ethereum;
        if (!eth || !eth.isMetaMask) { window.open('https://metamask.io/download/', '_blank'); setConnecting(null); return; }
        wp = eth;
        accounts = await wp.request({ method: 'eth_requestAccounts' });
        cid = await wp.request({ method: 'eth_chainId' });
        source = 'injected';
        selectedWallet = 'metamask';
      } else if (wallet === 'walletconnect') {
        wp = await initWalletConnect();
        accounts = await wp.request({ method: 'eth_accounts' });
        cid = await wp.request({ method: 'eth_chainId' });
        source = 'walletconnect';
        selectedWallet = 'walletconnect';
      } else {
        return;
      }
      // A response from a previous connection attempt or a logged-out session
      // must never install its provider or account after a newer selection.
      if (sessionEpochRef.current !== connectionEpoch || !accounts?.length || typeof accounts[0] !== 'string') return;
      retainProvider(wp as Eip1193Provider, source);
      subscribeProvider(wp as Eip1193Provider);
      setAddress(accounts[0]); setActiveWallet(selectedWallet);
      setChainId(parseChainId(cid));
      if (sessionEpochRef.current === connectionEpoch) {
        setModalOpen(false);
      }
    } catch (err: any) {
      console.warn('[WalletAuth] Connect rejected:', err?.message);
    } finally {
      if (sessionEpochRef.current === connectionEpoch) setConnecting(null);
    }
  }, [retainProvider, subscribeProvider]);

  const connect = useCallback(() => { setModalOpen(true); }, []);
  const logout = useCallback(() => {
    sessionEpochRef.current += 1;
    setAddress(null); setChainId(null); setActiveWallet(null); setModalOpen(false);
    unsubscribeProvider();
    retainProvider(null, null);
  }, [retainProvider, unsubscribeProvider]);

  const getProvider = useCallback(() => providerRef.current, []);

  const requestChainSwitch = useCallback(async (chainIdHex: string) => {
    const wp = providerRef.current;
    if (!wp) return { ok: false, error: 'No wallet provider connected' };
    const expectedChainId = parseChainId(chainIdHex);
    if (expectedChainId === null) return { ok: false, error: 'Invalid target chain id' };
    const sessionEpoch = sessionEpochRef.current;
    try {
      await wp.request({ method: 'wallet_switchEthereumChain', params: [{ chainId: chainIdHex }] });
      if (providerRef.current !== wp || sessionEpochRef.current !== sessionEpoch) {
        return { ok: false, error: 'Wallet session changed while switching chains' };
      }
      const cid = await wp.request({ method: 'eth_chainId' });
      if (providerRef.current !== wp || sessionEpochRef.current !== sessionEpoch) {
        return { ok: false, error: 'Wallet session changed while switching chains' };
      }
      const nextChainId = parseChainId(cid);
      if (nextChainId !== expectedChainId) {
        return { ok: false, error: `Wallet did not switch to chain ${expectedChainId}` };
      }
      setChainId(nextChainId);
      return { ok: true };
    } catch (err: any) {
      return { ok: false, error: err?.message ?? 'Chain switch rejected' };
    }
  }, []);
  const unlock = useCallback(async (_pk: string, _mnemonic: string, addr: string) => {
    // Legacy local unlock has no EIP-1193 provenance. It must not inherit a
    // prior provider or chain and thereby impersonate a live wallet session.
    sessionEpochRef.current += 1;
    unsubscribeProvider();
    retainProvider(null, null);
    setChainId(null); setActiveWallet(null);
    setAddress(isEvmAddress(addr) ? addr.trim() : null);
  }, [retainProvider, unsubscribeProvider]);

  return (
    <WalletAuthContext.Provider value={{
      isUnlocked: !!address, address, chainId, isRestoring, activeWallet,
      connect, connectWallet, logout, modalOpen, setModalOpen, connecting, unlock,
      provider, providerSource, getProvider, requestChainSwitch,
    }}>
      {children}
    </WalletAuthContext.Provider>
  );
}

export function useWalletAuth(): WalletAuthState {
  return useContext(WalletAuthContext);
}
