/**
 * Surf — function-call tools for BlockRun's crypto data API.
 *
 * Three category tools (SurfMarket / SurfChain / SurfSocial) that mirror the
 * /surf-market, /surf-chain, /surf-social skills. Unlike the generic BlockRun
 * primitive (free-form `path` string), these expose the valid endpoints as an
 * `endpoint` enum so the model picks instead of guessing, and they sign the
 * x402 payment internally — the model never touches paths or payment, same UX
 * as VideoGen / ImageGen.
 *
 * The endpoint tables below are derived from the gateway's SURF_ENDPOINTS
 * registry (blockrun/src/lib/surf.ts). They are hand-maintained for now; a
 * follow-up will generate them so the gateway stays the single source of truth.
 *
 * x402 signing mirrors src/tools/blockrun.ts (kept as copy-paste per the same
 * rationale documented there — refactoring into a shared module is out of scope).
 */
import { getOrCreateWallet, getOrCreateSolanaWallet, createPaymentPayload, createSolanaPaymentPayload, parsePaymentRequired, extractPaymentDetails, solanaKeyToBytes, SOLANA_NETWORK, } from '@blockrun/llm';
import { loadChain, API_URLS, USER_AGENT } from '../config.js';
import { recordUsage } from '../stats/tracker.js';
import { logger } from '../logger.js';
const TIMEOUT_MS = 30_000;
// ── Endpoint tables (derived from gateway SURF_ENDPOINTS) ───────────────────
const MARKET_ENDPOINTS = [
    { path: 'market/ranking', method: 'GET', required: [], desc: 'Token rankings (market cap, volume, 24h change).' },
    { path: 'market/fear-greed', method: 'GET', required: [], desc: 'Fear & Greed index history.' },
    { path: 'market/futures', method: 'GET', required: [], desc: 'Futures market overview.' },
    { path: 'market/price', method: 'GET', required: ['symbol'], desc: 'Token price history.' },
    { path: 'market/etf', method: 'GET', required: ['symbol'], desc: 'Spot ETF flow history (BTC/ETH).' },
    { path: 'market/options', method: 'GET', required: ['symbol'], desc: 'Options skew / IV / volume.' },
    { path: 'market/liquidation/exchange-list', method: 'GET', required: [], desc: 'Liquidations by exchange.' },
    { path: 'market/liquidation/order', method: 'GET', required: [], desc: 'Large (whale) liquidation orders.' },
    { path: 'market/liquidation/chart', method: 'GET', required: ['symbol'], desc: 'Liquidation chart over time.' },
    { path: 'market/onchain-indicator', method: 'GET', required: ['symbol', 'metric'], desc: 'On-chain indicators (NUPL/SOPR/MVRV/Puell/NVT).' },
    { path: 'market/price-indicator', method: 'GET', required: ['indicator', 'symbol'], desc: 'Technical indicators (RSI/MACD/BBANDS/EMA).' },
    { path: 'exchange/markets', method: 'GET', required: [], desc: 'CEX trading pairs catalog.' },
    { path: 'exchange/price', method: 'GET', required: ['pair'], desc: 'CEX ticker price for a pair.' },
    { path: 'exchange/perp', method: 'GET', required: ['pair'], desc: 'Perpetual contract snapshot.' },
    { path: 'exchange/depth', method: 'GET', required: ['pair'], desc: 'Order book depth.' },
    { path: 'exchange/klines', method: 'GET', required: ['pair'], desc: 'OHLCV candles.' },
    { path: 'exchange/funding-history', method: 'GET', required: ['pair'], desc: 'Funding rate history.' },
    { path: 'exchange/long-short-ratio', method: 'GET', required: ['pair'], desc: 'Long/short account ratio.' },
    { path: 'fund/detail', method: 'GET', required: [], desc: 'VC fund profile detail.' },
    { path: 'fund/portfolio', method: 'GET', required: [], desc: 'VC fund portfolio holdings.' },
    { path: 'fund/ranking', method: 'GET', required: ['metric'], desc: 'Top VC funds ranking.' },
    { path: 'news/feed', method: 'GET', required: [], desc: 'AI-curated crypto news feed.' },
    { path: 'news/detail', method: 'GET', required: ['id'], desc: 'Full article detail by id.' },
    { path: 'project/detail', method: 'GET', required: [], desc: 'Project profile.' },
    { path: 'project/defi/metrics', method: 'GET', required: ['metric'], desc: 'DeFi protocol metrics.' },
    { path: 'project/defi/ranking', method: 'GET', required: ['metric'], desc: 'DeFi protocol ranking.' },
];
const CHAIN_ENDPOINTS = [
    { path: 'onchain/bridge/ranking', method: 'GET', required: [], desc: 'Bridge protocol ranking by volume.' },
    { path: 'onchain/yield/ranking', method: 'GET', required: [], desc: 'Yield pool ranking (lending/LP/staking).' },
    { path: 'onchain/gas-price', method: 'GET', required: ['chain'], desc: 'Current gas price for a chain.' },
    { path: 'onchain/tx', method: 'GET', required: ['hash', 'chain'], desc: 'Transaction details by hash.' },
    { path: 'onchain/schema', method: 'GET', required: [], desc: 'Schema introspection for the SQL tables.' },
    { path: 'onchain/query', method: 'POST', required: [], desc: 'Structured chain query (POST body).' },
    { path: 'onchain/sql', method: 'POST', required: [], desc: 'Raw SQL against 80+ indexed chain tables (POST body, Tier-3 $0.02).' },
    { path: 'token/tokenomics', method: 'GET', required: [], desc: 'Token supply / unlock / distribution.' },
    { path: 'token/dex-trades', method: 'GET', required: ['address'], desc: 'Recent DEX trades for a token.' },
    { path: 'token/holders', method: 'GET', required: ['address', 'chain'], desc: 'Top holders / concentration.' },
    { path: 'token/transfers', method: 'GET', required: ['address', 'chain'], desc: 'Token transfer history.' },
    { path: 'wallet/detail', method: 'GET', required: ['address'], desc: 'Wallet overview.' },
    { path: 'wallet/history', method: 'GET', required: ['address'], desc: 'Wallet activity history.' },
    { path: 'wallet/net-worth', method: 'GET', required: ['address'], desc: 'Wallet net worth.' },
    { path: 'wallet/transfers', method: 'GET', required: ['address'], desc: 'Wallet transfers.' },
    { path: 'wallet/protocols', method: 'GET', required: ['address'], desc: 'Protocols the wallet interacts with.' },
    { path: 'wallet/labels/batch', method: 'GET', required: ['addresses'], desc: 'Batch wallet labels (CEX/Whale/Bridge/MEV).' },
];
const SOCIAL_ENDPOINTS = [
    { path: 'social/detail', method: 'GET', required: [], desc: 'Social signal detail.' },
    { path: 'social/ranking', method: 'GET', required: [], desc: 'KOL / account influence ranking.' },
    { path: 'social/smart-followers/history', method: 'GET', required: [], desc: 'Smart-follower growth history.' },
    { path: 'social/mindshare', method: 'GET', required: ['q', 'interval'], desc: 'Topic/token mindshare over an interval.' },
    { path: 'social/tweets', method: 'GET', required: ['ids'], desc: 'Tweets by ids.' },
    { path: 'social/tweet/replies', method: 'GET', required: ['tweet_id'], desc: 'Replies to a tweet.' },
    { path: 'social/user', method: 'GET', required: ['handle'], desc: 'User profile.' },
    { path: 'social/user/followers', method: 'GET', required: ['handle'], desc: 'User followers.' },
    { path: 'social/user/following', method: 'GET', required: ['handle'], desc: 'User followings.' },
    { path: 'social/user/posts', method: 'GET', required: ['handle'], desc: 'User posts.' },
    { path: 'social/user/replies', method: 'GET', required: ['handle'], desc: 'User replies.' },
];
// ── x402 signing (mirrors blockrun.ts) ──────────────────────────────────────
async function extractPaymentReq(response) {
    let header = response.headers.get('payment-required');
    if (!header) {
        try {
            const body = (await response.clone().json());
            if (body.x402 || body.accepts)
                header = btoa(JSON.stringify(body));
        }
        catch { /* not JSON */ }
    }
    return header;
}
async function signPayment(response, chain, endpoint, resourceDescription) {
    try {
        const paymentHeader = await extractPaymentReq(response);
        if (!paymentHeader)
            return null;
        const paymentRequired = parsePaymentRequired(paymentHeader);
        if (chain === 'solana') {
            const wallet = await getOrCreateSolanaWallet();
            const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
            const secretBytes = await solanaKeyToBytes(wallet.privateKey);
            const feePayer = details.extra?.feePayer || details.recipient;
            const payload = await createSolanaPaymentPayload(secretBytes, wallet.address, details.recipient, details.amount, feePayer, {
                resourceUrl: details.resource?.url || endpoint,
                resourceDescription: details.resource?.description || resourceDescription,
                maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
                extra: details.extra,
            });
            return { headers: { 'PAYMENT-SIGNATURE': payload }, amountUsd: Number(details.amount) / 1_000_000 };
        }
        const wallet = getOrCreateWallet();
        const details = extractPaymentDetails(paymentRequired);
        const payload = await createPaymentPayload(wallet.privateKey, wallet.address, details.recipient, details.amount, details.network || 'eip155:8453', {
            resourceUrl: details.resource?.url || endpoint,
            resourceDescription: details.resource?.description || resourceDescription,
            maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
            extra: details.extra,
        });
        return { headers: { 'PAYMENT-SIGNATURE': payload }, amountUsd: Number(details.amount) / 1_000_000 };
    }
    catch (err) {
        logger.warn(`[franklin] Surf payment error: ${err.message}`);
        return null;
    }
}
// ── Shared call: resolve endpoint → sign x402 → return data ──────────────────
async function callSurf(toolName, table, input, ctx) {
    const endpoint = typeof input.endpoint === 'string' ? input.endpoint.trim().replace(/^\/+|\/+$/g, '') : '';
    let entry = table.find((e) => e.path === endpoint);
    if (!entry) {
        // Tolerate a weak model dropping the category prefix ("fear-greed" instead
        // of "market/fear-greed") — accept a suffix match when it's unambiguous.
        const matches = table.filter((e) => e.path === endpoint || e.path.endsWith(`/${endpoint}`));
        if (matches.length === 1) {
            entry = matches[0];
        }
        else if (matches.length > 1) {
            return { output: `Ambiguous ${toolName} endpoint "${endpoint}". Did you mean: ${matches.map((m) => m.path).join(', ')}?`, isError: true };
        }
        else {
            return { output: `Unknown ${toolName} endpoint: "${endpoint}". Valid: ${table.map((e) => e.path).join(', ')}`, isError: true };
        }
    }
    // Collect query params: the named fields the caller provided (everything
    // except `endpoint`/`body`), plus an explicit `params` object if given.
    const query = {};
    for (const [k, v] of Object.entries(input)) {
        if (k === 'endpoint' || k === 'body' || k === 'params')
            continue;
        if (v !== undefined && v !== null && v !== '')
            query[k] = v;
    }
    if (input.params && typeof input.params === 'object')
        Object.assign(query, input.params);
    const missing = entry.required.filter((p) => query[p] === undefined);
    if (missing.length > 0) {
        return {
            output: `${toolName} ${endpoint} needs: ${entry.required.join(', ')}. Missing: ${missing.join(', ')}.`,
            isError: true,
        };
    }
    const chain = loadChain();
    const base = API_URLS[chain]; // ends in /api
    let url = `${base}/v1/surf/${entry.path}`;
    const body = entry.method === 'POST'
        ? (input.body && typeof input.body === 'object' ? input.body : query)
        : undefined;
    if (entry.method === 'GET' && Object.keys(query).length > 0) {
        const usp = new URLSearchParams();
        for (const [k, v] of Object.entries(query)) {
            if (Array.isArray(v))
                for (const x of v)
                    usp.append(k, String(x));
            else
                usp.append(k, String(v));
        }
        url += `?${usp.toString()}`;
    }
    const start = Date.now();
    const ctrl = new AbortController();
    const onAbort = () => ctrl.abort();
    ctx.abortSignal.addEventListener('abort', onAbort, { once: true });
    const timer = setTimeout(() => ctrl.abort(), TIMEOUT_MS);
    const headers = { Accept: 'application/json', 'User-Agent': USER_AGENT };
    if (entry.method === 'POST')
        headers['Content-Type'] = 'application/json';
    const payload = body !== undefined ? JSON.stringify(body) : undefined;
    const resourceDescription = `Surf ${entry.method} /v1/surf/${entry.path}`;
    try {
        let response = await fetch(url, { method: entry.method, signal: ctrl.signal, headers, body: payload });
        let paidUsd = 0;
        if (response.status === 402) {
            const signed = await signPayment(response, chain, url, resourceDescription);
            if (!signed)
                return { output: `${toolName} ${endpoint}: payment signing failed`, isError: true };
            paidUsd = signed.amountUsd;
            response = await fetch(url, {
                method: entry.method, signal: ctrl.signal,
                headers: { ...headers, ...signed.headers }, body: payload,
            });
        }
        if (!response.ok)
            paidUsd = 0;
        const raw = await response.text().catch(() => '');
        try {
            recordUsage(`${toolName}:${entry.path}`, 0, 0, paidUsd, Date.now() - start);
        }
        catch { /* best-effort */ }
        if (!response.ok) {
            return {
                output: `${toolName} ${endpoint} failed (status ${response.status}). No charge if 4xx pre-payment.\n${raw.slice(0, 800)}`,
                isError: true,
            };
        }
        const head = `Surf /v1/surf/${entry.path} → $${paidUsd.toFixed(4)} · ${Date.now() - start}ms`;
        return { output: `${head}\n\n\`\`\`json\n${raw}\n\`\`\`` };
    }
    catch (err) {
        return { output: `${toolName} ${endpoint} error: ${err.message}`, isError: true };
    }
    finally {
        clearTimeout(timer);
        ctx.abortSignal.removeEventListener('abort', onAbort);
    }
}
// ── Tool specs ───────────────────────────────────────────────────────────────
function makeSurfTool(name, blurb, table, extraParams) {
    const endpointList = table.map((e) => `\`${e.path}\`${e.required.length ? ` (needs ${e.required.join('+')})` : ''} — ${e.desc}`).join('\n');
    return {
        spec: {
            name,
            description: `${blurb} Picks an endpoint from a fixed list and signs the x402 USDC payment from the wallet automatically — ` +
                `you do not build paths or handle payment. Tier-1 $0.001, Tier-2 $0.005, Tier-3 $0.02.\n\nEndpoints:\n${endpointList}`,
            input_schema: {
                type: 'object',
                properties: {
                    endpoint: {
                        type: 'string',
                        enum: table.map((e) => e.path),
                        description: 'Which Surf endpoint to call (see list in the tool description).',
                    },
                    ...extraParams,
                    body: { type: 'object', description: 'Request body for POST endpoints (onchain/query, onchain/sql).' },
                },
                required: ['endpoint'],
            },
        },
        concurrent: true,
        execute: (input, ctx) => callSurf(name, table, input, ctx),
    };
}
export const surfMarketCapability = makeSurfTool('SurfMarket', 'Crypto market data: token rankings, fear/greed, futures, ETF flows, options, liquidations, technical & on-chain indicators, CEX pairs, VC funds, news, DeFi projects.', MARKET_ENDPOINTS, {
    symbol: { type: 'string', description: 'Token symbol, e.g. "BTC". Required by price/etf/options/liquidation-chart/indicators.' },
    pair: { type: 'string', description: 'Exchange pair, e.g. "BTC-USDT". Required by exchange/* endpoints.' },
    metric: { type: 'string', description: 'Metric name (e.g. "NUPL" for onchain-indicator, ranking metric for fund/project).' },
    indicator: { type: 'string', description: 'Technical indicator, e.g. "RSI", "MACD", "BBANDS".' },
    id: { type: 'string', description: 'Article id for news/detail.' },
});
export const surfChainCapability = makeSurfTool('SurfChain', 'On-chain data: bridge/yield rankings, gas, transactions, token analytics (holders, transfers, DEX trades), wallet intelligence, and raw SQL over 80+ indexed chain tables.', CHAIN_ENDPOINTS, {
    chain: { type: 'string', description: 'Chain name, e.g. "ethereum", "base". Required by gas-price/tx/holders/transfers.' },
    hash: { type: 'string', description: 'Transaction hash for onchain/tx.' },
    address: { type: 'string', description: 'Token or wallet address.' },
    addresses: { type: 'string', description: 'Comma-separated addresses for wallet/labels/batch.' },
});
export const surfSocialCapability = makeSurfTool('SurfSocial', 'Crypto-Twitter / KOL signal: influence rankings, mindshare, smart-follower history, tweets, and user profiles. The canonical source for CT sentiment.', SOCIAL_ENDPOINTS, {
    q: { type: 'string', description: 'Query/topic for mindshare.' },
    interval: { type: 'string', description: 'Time interval for mindshare, e.g. "24h", "7d".' },
    handle: { type: 'string', description: 'Twitter/X handle for social/user* endpoints.' },
    ids: { type: 'string', description: 'Comma-separated tweet ids for social/tweets.' },
    tweet_id: { type: 'string', description: 'Tweet id for social/tweet/replies.' },
});
export const surfCapabilities = [
    surfMarketCapability,
    surfChainCapability,
    surfSocialCapability,
];
