// AutistMask inpage script — injected into the page's JS context.
// Creates window.ethereum (EIP-1193 provider) and announces via EIP-6963.

(function () {
    // Defaults to mainnet; updated dynamically via eth_chainId on init and
    // chainChanged events from the extension.
    let currentChainId = "0x1";
    let currentNetworkVersion = "1";

    const listeners = {};
    let nextId = 1;
    const pending = {};

    // EIP-1193 ProviderRpcError: `code`, `message`, optional `data`. A class
    // rather than properties bolted onto an Error because this object crosses
    // no boundary after construction — it is built in the page's own realm and
    // handed straight to the caller's catch — so the prototype survives and
    // `error.name` is a stable thing for a dApp to see.
    class ProviderRpcError extends Error {
        constructor(code, message, data) {
            super(message);
            this.name = "ProviderRpcError";
            this.code = code;
            if (data !== undefined) this.data = data;
        }
    }

    // Rebuild a boundary error as the error the page catches, carrying the
    // code (and data) the extension reported. Without this a dApp cannot tell
    // a user's refusal (4001) from a wallet that broke, and retries or shows
    // an error instead of accepting the refusal.
    //
    // Whatever code arrived is passed through verbatim rather than being
    // matched against a list: the extension emits 4001, 4100 and 4902 today,
    // and a code this file has never heard of is still the truth about what
    // happened. An error reported with no code at all stays a plain Error —
    // a ProviderRpcError whose `code` is undefined would advertise a
    // conformance it does not have. `message` is untouched in every case.
    function toPageError(error) {
        const message = (error && error.message) || "Request failed";
        if (error && error.code !== undefined && error.code !== null) {
            return new ProviderRpcError(error.code, message, error.data);
        }
        return new Error(message);
    }

    // Listen for responses from the content script
    window.addEventListener("message", function onUuid(event) {
        if (event.source !== window) return;
        if (event.data?.type !== "AUTISTMASK_RESPONSE") return;
        const { id, result, error } = event.data;
        const p = pending[id];
        if (!p) return;
        delete pending[id];
        if (error) {
            p.reject(toPageError(error));
        } else {
            p.resolve(result);
        }
    });

    // Listen for events pushed from the extension
    window.addEventListener("message", function onUuid(event) {
        if (event.source !== window) return;
        if (event.data?.type !== "AUTISTMASK_EVENT") return;
        const { eventName, data } = event.data;
        if (eventName === "chainChanged") {
            currentChainId = data;
            currentNetworkVersion = String(parseInt(data, 16));
            provider.chainId = currentChainId;
            provider.networkVersion = currentNetworkVersion;
        }
        emit(eventName, data);
    });

    function emit(eventName, data) {
        const cbs = listeners[eventName];
        if (!cbs) return;
        for (const cb of cbs) {
            try {
                cb(data);
            } catch {
                // ignore listener errors
            }
        }
    }

    function sendRequest(args) {
        return new Promise((resolve, reject) => {
            const id = nextId++;
            pending[id] = { resolve, reject };
            window.postMessage(
                { type: "AUTISTMASK_REQUEST", id, ...args },
                "*",
            );
        });
    }

    const provider = {
        isAutistMask: true,
        isMetaMask: true, // compatibility — many dApps check this
        chainId: currentChainId,
        networkVersion: currentNetworkVersion,
        selectedAddress: null,

        async request(args) {
            const result = await sendRequest({
                method: args.method,
                params: args.params || [],
            });
            if (
                args.method === "eth_requestAccounts" ||
                args.method === "eth_accounts"
            ) {
                provider.selectedAddress =
                    Array.isArray(result) && result.length > 0
                        ? result[0]
                        : null;
            }
            if (args.method === "eth_chainId" && result) {
                currentChainId = result;
                currentNetworkVersion = String(parseInt(result, 16));
                provider.chainId = currentChainId;
                provider.networkVersion = currentNetworkVersion;
            }
            return result;
        },

        // Legacy methods (still used by some dApps)
        enable() {
            return this.request({ method: "eth_requestAccounts" });
        },

        send(methodOrPayload, paramsOrCallback) {
            // Handle both send(method, params) and send({method, params})
            if (typeof methodOrPayload === "string") {
                return this.request({
                    method: methodOrPayload,
                    params: paramsOrCallback || [],
                });
            }
            return this.request({
                method: methodOrPayload.method,
                params: methodOrPayload.params || [],
            });
        },

        sendAsync(payload, callback) {
            this.request({
                method: payload.method,
                params: payload.params || [],
            })
                .then((result) =>
                    callback(null, { id: payload.id, jsonrpc: "2.0", result }),
                )
                .catch((err) => callback(err));
        },

        on(event, cb) {
            if (!listeners[event]) listeners[event] = [];
            listeners[event].push(cb);
            return this;
        },

        removeListener(event, cb) {
            if (!listeners[event]) return this;
            listeners[event] = listeners[event].filter((c) => c !== cb);
            return this;
        },

        removeAllListeners(event) {
            if (event) {
                delete listeners[event];
            } else {
                for (const key of Object.keys(listeners)) {
                    delete listeners[key];
                }
            }
            return this;
        },

        // Some dApps (wagmi) probe this object to decide whether the provider
        // supports the de-facto standard extras. The name is theirs, not ours.
        _metamask: {
            isUnlocked() {
                return Promise.resolve(provider.selectedAddress !== null);
            },
        },
    };

    // Set window.ethereum if no other wallet has claimed it
    if (typeof window.ethereum === "undefined") {
        window.ethereum = provider;
    }
    window.dispatchEvent(new Event("ethereum#initialized"));

    // EIP-6963: Multi Injected Provider Discovery
    const ICON_SVG =
        "data:image/svg+xml," +
        encodeURIComponent(
            '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">' +
                '<rect width="32" height="32" rx="6" fill="#000"/>' +
                '<text x="16" y="23" text-anchor="middle" font-family="monospace" font-size="20" font-weight="bold" fill="#fff">A</text>' +
                "</svg>",
        );

    let providerUuid = crypto.randomUUID(); // fallback until real UUID arrives

    function buildProviderInfo() {
        return {
            uuid: providerUuid,
            name: "AutistMask",
            icon: ICON_SVG,
            rdns: "berlin.sneak.autistmask",
        };
    }

    function announceProvider() {
        window.dispatchEvent(
            new CustomEvent("eip6963:announceProvider", {
                detail: Object.freeze({
                    info: buildProviderInfo(),
                    provider,
                }),
            }),
        );
    }

    // Listen for the persisted UUID from the content script
    function onProviderUuid(event) {
        if (event.source !== window) return;
        if (event.data?.type !== "AUTISTMASK_PROVIDER_UUID") return;
        window.removeEventListener("message", onProviderUuid);
        providerUuid = event.data.uuid;
        announceProvider();
    }
    window.addEventListener("message", onProviderUuid);

    window.addEventListener("eip6963:requestProvider", announceProvider);
    announceProvider();

    // Fetch the current chain ID from the extension on load so the provider
    // reflects the selected network immediately (covers Sepolia etc.).
    sendRequest({ method: "eth_chainId", params: [] })
        .then((chainId) => {
            if (chainId) {
                currentChainId = chainId;
                currentNetworkVersion = String(parseInt(chainId, 16));
                provider.chainId = currentChainId;
                provider.networkVersion = currentNetworkVersion;
            }
        })
        .catch(() => {
            // Best-effort — keep defaults.
        });
})();
