export type WalletProvider={request:(args:{method:string;params?:unknown[]})=>Promise<unknown>;on?:(event:string,listener:(...args:unknown[])=>void)=>void;removeListener?:(event:string,listener:(...args:unknown[])=>void)=>void;disconnect?:()=>Promise<void>;isMetaMask?:boolean;isTrust?:boolean;providers?:WalletProvider[]};
export type WalletOption={id:string;name:string;kind:'metamask'|'trust'|'wallet';provider:WalletProvider};
const address=(v:unknown):v is string=>typeof v==='string'&&/^0x[\da-f]{40}$/i.test(v)&&!/^0x0{40}$/.test(v);
export function chainNumber(value:unknown){const n=typeof value==='string'&&/^0x[\da-f]+$/i.test(value)?Number(BigInt(value)):NaN;if(!Number.isSafeInteger(n)||n<=0)throw new Error('invalid_wallet');return n}
export function discoverWallets(target:Window,update:(wallets:WalletOption[])=>void){
 const options:WalletOption[]=[];
 const add=(provider:WalletProvider,name:string,id:string,rdns='')=>{
  if(!provider||typeof provider.request!=='function'||options.some(w=>w.provider===provider)||options.length>=20)return;
  const kind=rdns==='com.trustwallet.app'||provider.isTrust?'trust':rdns==='io.metamask'||provider.isMetaMask?'metamask':'wallet';
  options.push({provider,name:name.slice(0,60),id,kind});update([...options]);
 };
 const announce=(event:Event)=>{const d=(event as CustomEvent).detail;if(d?.info&&typeof d.info.name==='string'&&typeof d.info.uuid==='string')add(d.provider,d.info.name,d.info.uuid,typeof d.info.rdns==='string'?d.info.rdns:'')};
 target.addEventListener('eip6963:announceProvider',announce);target.dispatchEvent(new Event('eip6963:requestProvider'));
 const legacy=target as Window&{ethereum?:WalletProvider;trustwallet?:WalletProvider};
 for(const [i,p] of [...(legacy.ethereum?.providers||[]),legacy.ethereum,legacy.trustwallet].entries())if(p)add(p,p.isTrust?'Trust Wallet':p.isMetaMask?'MetaMask':'EVM Wallet','legacy-'+i);
 return ()=>target.removeEventListener('eip6963:announceProvider',announce);
}
export async function connectAndLink(provider:WalletProvider,options:{label:string;userId:string;origin:string;active:()=>boolean;stage:(value:string)=>void;post:(body:unknown)=>Promise<any>}){
 let changed=false;
 const change=()=>{changed=true};
 const guard=()=>{if(!options.active())throw new Error('cancelled');if(changed)throw new Error('wallet_changed')};
 options.stage('connect');
 const accounts=await provider.request({method:'eth_requestAccounts'});guard();
 if(!Array.isArray(accounts)||!address(accounts[0]))throw new Error('invalid_wallet');
 const selected=accounts[0],chainId=chainNumber(await provider.request({method:'eth_chainId'}));guard();
 provider.on?.('accountsChanged',change);provider.on?.('chainChanged',change);provider.on?.('disconnect',change);
 async function confirm(){guard();const [current,chain]=await Promise.all([provider.request({method:'eth_accounts'}),provider.request({method:'eth_chainId'})]);guard();if(!Array.isArray(current)||typeof current[0]!=='string'||current[0].toLowerCase()!==selected.toLowerCase()||chainNumber(chain)!==chainId)throw new Error('wallet_changed')}
 try{
  const challenge=await options.post({action:'challenge',address:selected,chainId,label:options.label});guard();
  const message=challenge.message;
  // Only sign the expected account-binding challenge from this origin.
  if(typeof message!=='string'||message.length>2000||!message.startsWith(new URL(options.origin).host+' wants you to sign in with your Ethereum account:\n')||!message.toLowerCase().includes('\n'+selected.toLowerCase()+'\n')||!message.includes(`\nURI: ${options.origin}/account/banks\n`)||!message.includes(`\nChain ID: ${chainId}\n`)||!message.endsWith(`urn:planck:account:${options.userId}`)||!message.includes(`\nRequest ID: ${challenge.id}\n`)||Date.parse(challenge.expires_at)<=Date.now()||!Number.isFinite(Date.parse(challenge.expires_at)))throw new Error('invalid_challenge');
  await confirm();options.stage('sign');
  const hex='0x'+Array.from(new TextEncoder().encode(message),v=>v.toString(16).padStart(2,'0')).join('');
  const signature=await provider.request({method:'personal_sign',params:[hex,selected]});
  await confirm();options.stage('save');
  const result=await options.post({action:'verify',id:challenge.id,signature});guard();return result.wallet;
 }finally{provider.removeListener?.('accountsChanged',change);provider.removeListener?.('chainChanged',change);provider.removeListener?.('disconnect',change)}
}
