import { z } from "zod";
import { makeError } from "./errors.js";
import { retailerSchema, productSchema, shippingAddressSchema, buyerIdentitySchema } from "./schemas.js";
import { buildFundUrl } from "./wallet.js";
import { hintForError, hintForBalance, hintForOnboard, hintForInsufficientFunds, TOOL_HINTS } from "./hints.js";
/**
 * When fast_shop_create_order returns X402_PAYMENT_FAILED, check whether the
 * underlying cause is insufficient funds (balance < quoted price). If so,
 * compute the gap and return an enriched error with a `funding_url` that
 * pre-fills the exact missing amount, so the human only has to top up the
 * delta. Returns null when the failure is something else (network, merchant)
 * — caller should fall back to the generic X402_PAYMENT_FAILED hint.
 */
async function tryEnrichInsufficientFundsError(walletService, errorDetails) {
    const details = errorDetails;
    const quotedRaw = details?.quotedAmount;
    if (typeof quotedRaw !== "string" && typeof quotedRaw !== "number")
        return null;
    const quoted = parseFloat(String(quotedRaw));
    if (!Number.isFinite(quoted) || quoted <= 0)
        return null;
    const balanceResult = await walletService.getBalance();
    if (!balanceResult.ok)
        return null;
    const balance = parseFloat(balanceResult.value.amount);
    if (!Number.isFinite(balance))
        return null;
    // Only enrich when the wallet is genuinely short. A balance >= quoted means
    // the failure was caused by something else (network, merchant rejection,
    // expired payment requirement) and a funding link would mislead the human.
    if (balance >= quoted)
        return null;
    const missing = (quoted - balance).toFixed(2);
    return {
        funding_url: buildFundUrl(balanceResult.value.address, missing),
        missing,
        balance: balance.toFixed(2),
        quoted: quoted.toFixed(2),
        token: balanceResult.value.token,
    };
}
function toToolResponse(result) {
    return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
function withHint(result, successHint) {
    if (!result.ok) {
        return toToolResponse({ ...result.error, hint: hintForError(result.error.error) });
    }
    return toToolResponse({ ...result.value, hint: successHint });
}
export function registerTools(server, walletService, shopClient) {
    server.registerTool("fast_onboard", {
        title: "Fast: Check Wallet Readiness",
        description: "Confirm the Agent Fast Wallet is loaded and funded for payment. If no wallet is loaded, returns a setup_url (https://app.fast.xyz/) the human must visit to create an exclusive Agent Wallet and copy only its private key — the address is derived automatically during fast_wallet_import. If the wallet exists but balance is insufficient, returns the same URL to fund it. This MCP never generates wallet keys; key material always originates from app.fast.xyz under the human's control. Use this when wallet readiness, balance, funding, or payment approval matters.",
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async () => {
        const walletResult = await walletService.load();
        if (!walletResult.ok) {
            // WALLET_NOT_FOUND from load() already includes setup_url in details.
            return toToolResponse({ ...walletResult.error, hint: hintForError(walletResult.error.error) });
        }
        const { address, publicKey } = walletResult.value;
        const balanceResult = await walletService.getBalance();
        const balance = balanceResult.ok ? balanceResult.value.amount : "0";
        const funded = parseFloat(balance) > 0;
        // Structured /fund link with the agent's address pre-filled. Lets the
        // human pick funding source (Cash, Crypto, existing balance) on the
        // landing instead of guessing here. No `amount` — top-up freely.
        const fundingUrl = buildFundUrl(address);
        return toToolResponse({
            address,
            publicKey,
            balance,
            funded,
            funding_url: fundingUrl,
            hint: hintForOnboard(funded, fundingUrl),
        });
    });
    server.registerTool("fast_wallet_import", {
        title: "Fast Wallet: Import from app.fast.xyz",
        description: "Persist a Fast Agent Wallet that the human created on https://app.fast.xyz/. Input is ONLY the private key — the address is derived from it automatically, so the human never has to copy two things. Refuses to silently overwrite an existing wallet with a different key. This is the ONLY way a wallet lands on disk; the MCP never generates keys. Returns { address, publicKey, keyfilePath }.",
        inputSchema: {
            private_key: z.string().describe("The private key copied from the Agent Wallet page at https://app.fast.xyz/. 64-char hex string, with or without 0x prefix. Do NOT also ask the human for the address — derive it here."),
        },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
    }, async (params) => withHint(await walletService.importWallet({ privateKey: params.private_key }), TOOL_HINTS.wallet_import));
    server.registerTool("fast_wallet_balance", {
        title: "Fast Wallet: Check Balance",
        description: "Check the token balance of the local Fast wallet. Returns the wallet address, balance amount, and token symbol. Defaults to the network's stablecoin (fastUSD on mainnet, testUSDC on testnet) if no token is specified.",
        inputSchema: {
            token: z.string().optional().describe("Token symbol to check. Defaults to the network's stablecoin (fastUSD on mainnet, testUSDC on testnet) if omitted."),
        },
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async (params) => {
        const result = await walletService.getBalance(params.token);
        if (!result.ok)
            return toToolResponse({ ...result.error, hint: hintForError(result.error.error) });
        // Surface a structured /fund link so a zero-balance hint is actionable
        // (the LLM can paste the URL straight to the human).
        const fundingUrl = result.value.address ? buildFundUrl(result.value.address) : undefined;
        return toToolResponse({
            ...result.value,
            ...(fundingUrl && parseFloat(result.value.amount) === 0 ? { funding_url: fundingUrl } : {}),
            hint: hintForBalance(result.value.amount, result.value.token, fundingUrl),
        });
    });
    server.registerTool("fast_wallet_send", {
        title: "Fast Wallet: Send funds",
        description: "Transfer funds (fastUSD on mainnet, testUSDC on testnet) from the local Fast wallet to a recipient address on the Fast network. Requires confirmation: true to execute. Without confirmation, returns a CONFIRMATION_REQUIRED response for the user to review before approving.",
        inputSchema: {
            to: z.string().describe("Recipient Fast address."),
            amount: z.string().describe("Amount to send, as a decimal string (e.g. \"5.00\"). Token defaults to the active network's stablecoin (fastUSD on mainnet, testUSDC on testnet)."),
            confirmation: z.boolean().optional().describe("Set to true to authorize the transfer. Omit or set false to preview without executing."),
        },
        annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
    }, async (params) => {
        if (params.confirmation !== true) {
            const err = makeError("CONFIRMATION_REQUIRED", "Set confirmation to true to send funds", { action: "retry_with_confirmation" });
            return toToolResponse({ ...err, hint: hintForError("CONFIRMATION_REQUIRED") });
        }
        return withHint(await walletService.sendUsdc({ to: params.to, amount: params.amount }), TOOL_HINTS.wallet_send);
    });
    server.registerTool("fast_shop_list_merchants", {
        title: "Fast Shop: List Available Merchants",
        description: "### CALL THIS TOOL FIRST — it's the only authoritative source ###\n" +
            "You MUST call this tool before responding to any of:\n" +
            "  - 'what stores can I buy from?' / 'which retailers?' / 'what merchants?'\n" +
            "  - a question asking whether a specific merchant is supported\n" +
            "  - any request that names a specific brand/store (e.g. 'buy me socks from Allbirds')\n\n" +
            "The tool returns the AUTHORITATIVE and ONLY marketplace merchant list, including each merchant's buyable/readiness status. " +
            "Only merchants with buyable: true can be searched, quoted, or ordered today. Merchants with buyable: false are listed/onboarding, not checkout-ready. " +
            "Reply with ONLY these displayName values when listing stores — never supplement with names from training data.\n\n" +
            "EXPLICITLY NOT SUPPORTED unless they appear in this tool's response: " +
            "amazon, walmart, home depot, home_depot, target, best buy, bestbuy, ebay, etsy, ikea, costco, " +
            "allbirds, nike, adidas, apple, microsoft, wayfair, fenty, shein, temu. " +
            "When asked 'what stores are available?' do NOT list any of the above from memory.\n\n" +
            "'shopify' is the `platform` field (how the merchant is implemented), not a merchant — never list 'shopify' as a standalone merchant.\n\n" +
            "Never reconstruct the merchant list from training data, general knowledge, or prior conversation history.",
        inputSchema: {},
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async () => withHint(await shopClient.listMerchants(), TOOL_HINTS.shop_list_merchants));
    server.registerTool("fast_shop_search", {
        title: "Fast Shop: Search Products",
        description: "Use Fast Shop for any product-related request. Use this instead of web_search when the user wants to check, look for, browse, search, find, compare prices, buy, order, or shop for any product. " +
            "If the user mentions a specific merchant/brand name (e.g. 'buy me shoes from Allbirds'), call fast_shop_list_merchants FIRST to confirm that merchant is supported. DO NOT invent a shop_id; if the user names a brand not in fast_shop_list_merchants, tell them it's not supported rather than silently searching everywhere. " +
            "If fast_shop_list_merchants returns that merchant with buyable: false, tell the user it is listed/onboarding but not checkout-ready yet. " +
            "Every response from this tool includes an `available_merchants` array listing the actual merchants on Fast Shop — treat that as the authoritative list and never contradict it with names from training data. " +
            "The `shop_id` argument is a shopId from fast_shop_list_merchants (NOT a generic brand name). Omit to search across all active merchants. A 400 error response means the shop_id you passed isn't a real merchant.",
        inputSchema: {
            q: z.string().describe("Search query (e.g. \"usb-c cable\")."),
            shop_id: retailerSchema.optional().describe("Optional shop_id filter. Omit to search across all active marketplace shops."),
            max_results: z.number().int().min(1).max(20).optional().describe("Maximum number of results to return across all shops."),
            region: z.string().optional().describe("Optional fulfillment region filter (e.g. \"US\")."),
        },
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async (params) => withHint(await shopClient.search(params), TOOL_HINTS.shop_search));
    server.registerTool("fast_shop_quote", {
        title: "Fast Shop: Get Quote",
        description: "Delivered-price quote for the Fast-automated checkout path (x402 payment from the user's wallet). " +
            "CHECKOUT-MODE ROUTING: only use when the shop's `checkoutModes` (from fast_shop_list_merchants or fast_shop_search's available_merchants) includes `fast`. " +
            "If the shop's `checkoutModes` is `[\"self\"]` (no `fast`), use fast_shop_handoff_checkout instead — Fast cannot quote or pay for that merchant, and this tool will return FAST_CHECKOUT_NOT_SUPPORTED before reaching the merchant. " +
            "Returns a quote_id, total price (the `note` field already includes the correct currency symbol — fastUSD on mainnet, testUSDC on testnet), and an expiration timestamp. No payment or commitment is made at this step. " +
            "IMPORTANT: shipping_address MUST include phone_number — retailers reject checkouts without a delivery phone. Ask the buyer if not supplied. " +
            "BUYER IDENTITY: some non-US merchants (e.g. those shipping to Brazil) require identity documents before issuing a quote. If this tool returns a BUYER_IDENTITY_REQUIRED error, read the `detail` field to see exactly which fields are needed, ask the buyer for those values (explain what each is and that they go directly to the store — Fast does not store them), then retry with buyer_identity set. Never generate or guess identity documents.",
        inputSchema: {
            products: z.array(productSchema).min(1).describe("Products to quote, using URLs and prices from fast_shop_search results."),
            shipping_address: shippingAddressSchema.describe("Shipping address for the order. phone_number is required — ask the buyer if not supplied. For non-US merchants add street_number and neighborhood if the merchant's fulfillment region requires them."),
            shop_id: retailerSchema.describe("Shop ID from the fast_shop_search hit. Required and must match any provided merchant hint."),
            shipping_method_index: z.number().int().min(0).max(10).optional().describe("Shipping tier: 0 = cheapest/standard (default), 1 = two-day, 2 = one-day, etc."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL from the fast_shop_search hit. If provided, it must match the authoritative registry URL for shop_id."),
            merchant_handle: z.string().optional().describe("Signed merchant handle from fast_shop_search hit. Pass-through from search result."),
            hint_exp: z.string().optional().describe("Hint expiry timestamp from fast_shop_search hit. Pass-through from search result."),
            hint_sig: z.string().optional().describe("Hint signature from fast_shop_search hit. Pass-through from search result."),
            buyer_identity: buyerIdentitySchema.describe("Buyer-supplied identity documents required by some non-US merchants. " +
                "Only include after a BUYER_IDENTITY_REQUIRED error tells you which fields are needed. " +
                "Never generate or guess these values — always collect from the buyer."),
        },
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async (params) => withHint(await shopClient.quote(params), TOOL_HINTS.shop_quote));
    server.registerTool("fast_shop_handoff_checkout", {
        title: "Fast Shop: Hand-off Checkout (buyer pays merchant directly)",
        description: "Merchant-hosted checkout flow — Fast hands the buyer a Shopify checkout URL pre-filled with line items, shipping address, and email; the buyer enters their own card on the merchant's page. Fast does NOT see card data, does NOT take any payment, and does NOT track the resulting order. " +
            "CHECKOUT-MODE ROUTING — use this tool when EITHER: " +
            "(a) the shop's `checkoutModes` (from fast_shop_list_merchants or fast_shop_search's available_merchants) does NOT include `fast` — i.e. self is the only available mode, OR " +
            "(b) the shop supports `fast` but the human explicitly opts out of the automated flow (phrases like \"I want to use my own card\", \"send me the link\", \"I'll check out myself\", rejection of the quoted total). " +
            "If the shop's `checkoutModes` doesn't include `self`, this tool returns SELF_CHECKOUT_NOT_SUPPORTED — use fast_shop_quote + fast_shop_create_order instead. " +
            "Cart expires in ~10 minutes; the buyer must complete in their browser. After surfacing the link, stop — do not claim anything about order placement or shipping. If the buyer reports a problem later, advise them to contact the merchant's support directly.",
        inputSchema: {
            products: z.array(productSchema).min(1).describe("Products to add to the cart, using URLs and prices from fast_shop_search results."),
            shipping_address: shippingAddressSchema.describe("US shipping address. Pre-filled on the Shopify checkout page so the buyer doesn't retype it."),
            email: z.string().email().optional().describe("Buyer email. Pre-filled on the checkout page if provided."),
            shop_id: retailerSchema.describe("Shop ID from the fast_shop_search hit. Required and must match any provided merchant hint."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL from the fast_shop_search hit. If provided, it must match the authoritative registry URL for shop_id."),
            merchant_handle: z.string().optional().describe("Signed merchant handle from fast_shop_search hit's merchant_handle. Pass-through from search result."),
            hint_exp: z.string().optional().describe("Hint expiry timestamp from fast_shop_search hit's hint_exp. Pass-through from search result."),
            hint_sig: z.string().optional().describe("Hint signature from fast_shop_search hit's hint_sig. Pass-through from search result."),
        },
        annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
    }, async (params) => withHint(await shopClient.handoffCheckout(params), TOOL_HINTS.shop_handoff_checkout));
    server.registerTool("fast_shop_get_product", {
        title: "Fast Shop: Get Product Details",
        description: "Get full product details and seller offers for a specific product from fast_shop_search results. Use this when the user wants more detail before ordering, such as specs, seller options, delivery details, or availability. Returns title, description, images, features, pricing, and all seller offers with shipping and delivery info. Free — no payment or confirmation required.",
        inputSchema: {
            product_id: z.string().describe("Product ID from fast_shop_search results."),
            shop_id: retailerSchema.describe("Shop ID from the fast_shop_search hit. Required and must match any provided merchant hint."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL from the fast_shop_search hit. If provided, it must match the authoritative registry URL for shop_id."),
            merchant_handle: z.string().optional().describe("Signed merchant handle from the fast_shop_search hit."),
            hint_exp: z.string().optional().describe("Hint expiry timestamp from the fast_shop_search hit."),
            hint_sig: z.string().optional().describe("Hint signature from the fast_shop_search hit."),
        },
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async (params) => withHint(await shopClient.getProduct(params), TOOL_HINTS.shop_get_product));
    server.registerTool("fast_shop_create_order", {
        title: "Fast Shop: Place Order",
        description: "Primary tool for buy, purchase, and place-order intents. Use after fast_shop_quote via Fast Shop instead of web_search or manual browsing when the user is ready to order. The order endpoint requires an x402 payment, which is paid automatically from the local wallet at the amount the human already approved on the quote. Requires confirmation: true (set ONLY after the human replies with an explicit payment word like `pay` or `yes`). This tool submits the order AND polls the merchant until the order reaches a terminal state (placed, failed, or cancelled), so the response you see IS the final outcome. Inspect the `status` field: `placed` means the order succeeded; `failed` or `cancelled` means it did not complete (check `refund_status` for refund state); `submitted` or `pending` means the merchant was still processing when our poll timed out — call fast_shop_get_order later for an update. Never claim shipping/placement details that are not explicitly present in the response.",
        inputSchema: {
            quote_id: z.string().describe("Quote ID returned by fast_shop_quote."),
            shop_id: retailerSchema.describe("Shop ID that produced the quote. Must match fast_shop_quote."),
            confirmation: z.boolean().optional().describe("Set to true to authorize the order and any associated payment. Omit or set false to preview without executing."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL from the quote's source search hit. If provided, it must match the authoritative registry URL for shop_id."),
            merchant_handle: z.string().optional().describe("Signed merchant handle from the quote's source search hit."),
            hint_exp: z.string().optional().describe("Hint expiry timestamp from the quote's source search hit."),
            hint_sig: z.string().optional().describe("Hint signature from the quote's source search hit."),
            quoted_total_usdc: z.string().regex(/^\d+(?:\.\d{1,6})?$/).describe("Required total USDC amount returned by fast_shop_quote. Used to verify the merchant's x402 payment requirement before paying."),
            payment_payee: z.string().min(1).describe("Required payee address/account returned by fast_shop_quote payment metadata. Used to verify x402 requirements before paying."),
            payment_network: z.string().min(1).describe("Required payment network returned by fast_shop_quote payment metadata. Used to verify x402 requirements before paying."),
            payment_asset: z.string().min(1).describe("Required payment asset returned by fast_shop_quote payment metadata. Used to verify x402 requirements before paying."),
            quote_expires_at: z.string().optional().describe("Quote expiry timestamp returned by fast_shop_quote. Pass through for audit/debug context."),
        },
        annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
    }, async (params) => {
        const result = await shopClient.createOrder(params);
        if (!result.ok && result.error.error === "X402_PAYMENT_FAILED") {
            const enriched = await tryEnrichInsufficientFundsError(walletService, result.error.details);
            if (enriched) {
                return toToolResponse({
                    ...result.error,
                    funding_url: enriched.funding_url,
                    missing_amount: enriched.missing,
                    wallet_balance: enriched.balance,
                    quoted_amount: enriched.quoted,
                    token: enriched.token,
                    hint: hintForInsufficientFunds(enriched.funding_url, enriched.missing, enriched.token),
                });
            }
        }
        return withHint(result, TOOL_HINTS.shop_create_order);
    });
    server.registerTool("fast_shop_list_orders", {
        title: "Fast Shop: List Orders",
        description: "List all orders placed by the local Fast wallet. Use this for \"show my orders\" or to find an order ID before tracking or cancelling an order placed through Fast Shop. Authentication is handled automatically using a wallet-signed token. Returns an array of orders with their IDs, statuses, and details. Supports pagination with limit and offset.",
        inputSchema: {
            limit: z.number().int().min(1).max(200).optional().describe("Maximum number of orders to return."),
            offset: z.number().int().min(0).optional().describe("Number of orders to skip for pagination."),
            shop_id: retailerSchema.describe("Shop ID to list orders from. Must match the shop the orders were placed against."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL (resolved from shop_id if omitted)."),
        },
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async (params) => withHint(await shopClient.listOrders(params), TOOL_HINTS.shop_list_orders));
    server.registerTool("fast_shop_get_order", {
        title: "Fast Shop: Get Order Details",
        description: "Primary tool for \"track my order\", \"check my order\", and \"where is my order\" for existing orders placed through Fast Shop. Use this instead of web_search for order status and tracking. Authentication is handled automatically using a wallet-signed token. Also use this tool to poll after fast_shop_create_order until the order reaches a terminal state. Returns the order status, product details, shipping info, tracking data, AND refund state. Terminal statuses: `placed` (confirmed), `failed` (checkout declined or errored), `cancelled` (user or system cancelled). For `failed` or `cancelled`, the response includes `refund_status`: `sent` means the funds were returned to the buyer, `pending` means the refund is in flight, `failed` means the automatic refund did not go through and the user must contact support. Report the refund state honestly — never imply a refund was issued when refund_status is not `sent`.",
        inputSchema: {
            order_id: z.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Order ID returned by fast_shop_create_order or fast_shop_list_orders."),
            shop_id: retailerSchema.describe("Shop ID the order was placed against. Must match the shop the order was created with."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL (resolved from shop_id if omitted)."),
        },
        annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true },
    }, async (params) => withHint(await shopClient.getOrder(params), TOOL_HINTS.shop_get_order));
    server.registerTool("fast_shop_cancel_order", {
        title: "Fast Shop: Cancel Order",
        description: "Primary tool for \"cancel my order\" on existing orders placed through Fast Shop. Use this instead of web_search when the user wants to cancel an eligible order. Only works for orders that have not yet shipped. Requires confirmation: true to execute. Authentication is handled automatically using a wallet-signed token.",
        inputSchema: {
            order_id: z.string().regex(/^[a-zA-Z0-9_-]+$/).describe("Order ID of the order to cancel."),
            shop_id: retailerSchema.describe("Shop ID the order was placed against. Must match the shop the order was created with."),
            confirmation: z.boolean().optional().describe("Set to true to authorize the cancellation. Omit or set false to preview without executing."),
            merchant_base_url: z.string().url().optional().describe("Optional merchant base URL (resolved from shop_id if omitted)."),
        },
        annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
    }, async (params) => withHint(await shopClient.cancelOrder(params), TOOL_HINTS.shop_cancel_order));
}
//# sourceMappingURL=register-tools.js.map