import { UserRefusedAddress } from "@ledgerhq/errors";
import { log } from "@ledgerhq/logs";
import invariant from "invariant";
import { useCallback, useEffect, useRef, useState } from "react";
import { firstValueFrom, from } from "rxjs";
import { AcreMessageType } from "@ledgerhq/wallet-api-acre-module";
import perFamily from "../../generated/hw-signMessage";
import { createAction as createAppAction } from "../actions/app";
import { withDevice } from "../deviceAccess";
import { messageSigner as ACREMessageSigner } from "../../families/bitcoin/ACRESetup";
import { decodeAccountId } from "../../account";
export const prepareMessageToSign = (account, message) => {
    const utf8Message = Buffer.from(message, "hex").toString();
    if (!perFamily[account.currency.family]) {
        throw new Error("Crypto does not support signMessage");
    }
    if ("prepareMessageToSign" in perFamily[account.currency.family]) {
        return perFamily[account.currency.family].prepareMessageToSign({
            account,
            message: utf8Message,
        });
    }
    // Default implementation
    return { message: utf8Message };
};
const signMessage = (transport, account, opts) => {
    const { currency } = account;
    let signMessage = perFamily[currency.family].signMessage;
    if ("type" in opts) {
        switch (opts.type) {
            case AcreMessageType.Withdraw:
                signMessage = ACREMessageSigner.signWithdraw;
                break;
            case AcreMessageType.SignIn:
                signMessage = ACREMessageSigner.signIn;
                break;
            default:
                signMessage = ACREMessageSigner.signMessage;
                break;
        }
    }
    invariant(signMessage, `signMessage is not implemented for ${currency.id}`);
    return signMessage(transport, account, opts)
        .then(result => {
        const path = "path" in opts && opts.path ? opts.path : account.freshAddressPath;
        log("hw", `signMessage ${currency.id} on ${path} with message [${opts.message}]`, result);
        return result;
    })
        .catch(e => {
        const path = "path" in opts && opts.path ? opts.path : account.freshAddressPath;
        log("hw", `signMessage ${currency.id} on ${path} FAILED ${String(e)}`);
        if (e && e.name === "TransportStatusError") {
            if (e.statusCode === 0x6985 || e.statusCode === 0x5501) {
                throw new UserRefusedAddress();
            }
        }
        throw e;
    });
};
export const signMessageExec = ({ request, deviceId }) => {
    if (!request.account) {
        throw new Error("account is required");
    }
    const { type } = decodeAccountId(request.account.id);
    if (type === "mock") {
        return from(Promise.resolve({
            signature: "mockedSignature",
        }));
    }
    const result = withDevice(deviceId)(transport => from(signMessage(transport, request.account, request.message)));
    return result;
};
const initialState = {
    signMessageRequested: null,
    signMessageError: null,
    signMessageResult: null,
};
export const createAction = (connectAppExec, signMessage = signMessageExec) => {
    const useHook = (reduxDevice, request) => {
        const appState = createAppAction(connectAppExec).useHook(reduxDevice, {
            appName: request.appName,
            dependencies: request.dependencies,
            account: request.isACRE ? undefined : request.account, // Bypass derivation check with ACRE as we can use other addresses than the freshest
        });
        const { device, opened, inWrongDeviceForAccount, error } = appState;
        const [state, setState] = useState({
            ...initialState,
            signMessageRequested: request.message,
        });
        const signedFired = useRef();
        const sign = useCallback(async () => {
            let result;
            if (!device) {
                setState({
                    ...initialState,
                    signMessageError: new Error("no Device"),
                });
                return;
            }
            try {
                result = await firstValueFrom(signMessage({
                    request,
                    deviceId: device.deviceId,
                }));
            }
            catch (e) {
                if (e.name === "UserRefusedAddress") {
                    e.name = "UserRefusedOnDevice";
                    e.message = "UserRefusedOnDevice";
                }
                return setState({ ...initialState, signMessageError: e });
            }
            setState({ ...initialState, signMessageResult: result?.signature });
        }, [device, request]);
        useEffect(() => {
            if (!device || !opened || inWrongDeviceForAccount || error) {
                return;
            }
            if (state.signMessageRequested && !signedFired.current) {
                signedFired.current = true;
                sign();
            }
        }, [device, opened, inWrongDeviceForAccount, error, sign, state.signMessageRequested]);
        return { ...appState, ...state };
    };
    return {
        useHook,
        mapResult: (state) => ({
            signature: state.signMessageResult,
            error: state.signMessageError,
        }),
    };
};
export default signMessage;
//# sourceMappingURL=index.js.map