"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildReadNamespace = buildReadNamespace;
exports.buildWriteNamespace = buildWriteNamespace;
const abi_js_1 = require("../../utils/abi.js");
function getFunctionParameters(values) {
    const hasArgs = values.length > 0 && Array.isArray(values[0]);
    const args = hasArgs ? values[0] : [];
    const options = (hasArgs ? values[1] : values[0]) ?? {};
    return { args, options };
}
function resolveCallValue(value) {
    if (value === undefined)
        return undefined;
    if (typeof value === 'bigint') {
        if (value < 0n) {
            throw new Error('call value cannot be negative');
        }
        // Number(bigint) silently loses precision above 2^53; reject rather than truncate.
        if (value > BigInt(Number.MAX_SAFE_INTEGER)) {
            throw new Error('call value exceeds safe integer range');
        }
        return Number(value);
    }
    if (typeof value !== 'number') {
        throw new Error('call value must be a number or bigint');
    }
    if (value < 0) {
        throw new Error('call value cannot be negative');
    }
    if (!Number.isInteger(value)) {
        throw new Error('call value must be an integer');
    }
    if (value > Number.MAX_SAFE_INTEGER) {
        throw new Error('call value exceeds safe integer range');
    }
    return value;
}
// The `account` option is a private key; derive its address. A TRON private key is
// exactly 64 hex chars (32 bytes), optionally 0x-prefixed; anything else — including
// any address form — is rejected. The format gate is also a safety check: a 42-char
// hex address is all hex and would otherwise be silently zero-padded into a valid
// scalar by fromPrivateKey, yielding a bogus derived address instead of an error.
function privateKeyToAddress(tronWeb, account) {
    const hex = account.replace(/^0x/, '');
    if (hex.length === 64 && /^[0-9a-fA-F]{64}$/.test(hex)) {
        const derived = tronWeb.address.fromPrivateKey(account);
        if (derived)
            return derived;
    }
    throw new Error('The "account" option must be a private key.');
}
// Caller (issuer) address for a constant call: the explicit `from` address, else the
// instance default. At least one must be present — reject rather than fall back to a
// null caller, so a constant call always issues from a real address.
function resolveCallerAddress(tronWeb, from, defaultAddress) {
    if (typeof from === 'string' && from.length > 0) {
        if (!tronWeb.isAddress(from)) {
            throw new Error('The "from" option must be a valid address.');
        }
        return from;
    }
    if (typeof defaultAddress === 'string' && defaultAddress.length > 0)
        return defaultAddress;
    throw new Error('A caller address is required. Set the "from" option or a default address on the TronWeb instance.');
}
// The branches that sign with the instance default key need a usable default key,
// not merely a default address (setAddress can set an address without a key). Reject
// up front with the friendly diagnostic instead of letting trx.sign fail cryptically.
function requireDefaultSigner(tronWeb, functionName) {
    const defaultAddress = tronWeb.defaultAddress.base58;
    if (!tronWeb.defaultPrivateKey || !defaultAddress) {
        throw new Error(`Method "${functionName}" modifies state and requires a signer. Set a private key or default address on the TronWeb instance.`);
    }
    return defaultAddress;
}
function resolveSigner(tronWeb, functionName, account) {
    if (typeof account === 'string' && account.length > 0) {
        // `account` is a private key — derive its address and sign with it, so a write
        // can be issued from an account other than the instance default (and even when
        // the instance has no default signer at all).
        return { signerAddress: privateKeyToAddress(tronWeb, account), privateKey: account };
    }
    return { signerAddress: requireDefaultSigner(tronWeb, functionName) };
}
function extractConstantResultData(tronWeb, transaction) {
    const rawConstantResult = transaction.constant_result;
    const constantResult = Array.isArray(rawConstantResult) ? rawConstantResult[0] : rawConstantResult;
    if (constantResult === undefined || constantResult === null || constantResult === '')
        return undefined;
    if (typeof constantResult !== 'string') {
        throw new Error('Invalid constant call result');
    }
    const normalized = constantResult.replace(/^0x/, '');
    const len = normalized.length;
    if (len !== 0 && len % 64 === 8) {
        let message = 'The call has been reverted or has thrown an error.';
        const chunk = normalized.substring(8);
        if (chunk.length > 0) {
            let decoded = '';
            for (let index = 0; index < chunk.length; index += 64) {
                decoded += tronWeb.toUtf8(chunk.substring(index, index + 64));
            }
            message += ` Error message: ${decoded
                .replace(/(\u0000|\u000b|\f)+/g, ' ')
                .replace(/ +/g, ' ')
                .replace(/\s+$/g, '')}`;
        }
        throw new Error(message);
    }
    return `0x${normalized}`;
}
function getReadContractFragment(abi, functionName, args) {
    const fragment = (0, abi_js_1.resolveFunctionFragment)(abi, functionName, args);
    if (!(0, abi_js_1.isReadOnlyFunctionFragment)(fragment)) {
        throw new Error(`Function "${functionName}" is not read-only.`);
    }
    return fragment;
}
function getWriteContractFragment(abi, functionName, args) {
    const fragment = (0, abi_js_1.resolveFunctionFragment)(abi, functionName, args);
    if ((0, abi_js_1.isReadOnlyFunctionFragment)(fragment)) {
        throw new Error(`Function "${functionName}" is read-only.`);
    }
    return fragment;
}
function normalizeReadContractOutput(fragment, data) {
    if (!data) {
        if (fragment.outputs && fragment.outputs.length > 0) {
            throw new Error('Failed to execute');
        }
        return undefined;
    }
    const output = (0, abi_js_1.decodeParamsV2ByABI)(fragment, data);
    if (output.length === 1) {
        return output[0];
    }
    return output;
}
function getBroadcastErrorMessage(tronWeb, broadcast) {
    if (typeof broadcast.message === 'string' && broadcast.message.length > 0) {
        return tronWeb.toUtf8(broadcast.message);
    }
    if (typeof broadcast.code === 'string' && broadcast.code.length > 0) {
        return broadcast.code;
    }
    return 'Failed to broadcast transaction';
}
function assertBroadcastOk(tronWeb, broadcast) {
    if (broadcast && typeof broadcast === 'object') {
        const result = broadcast;
        // `.code` covers normal TRON broadcast failures; `result === false` also covers a
        // failure shaped `{ result: false }` that carries no code.
        if (result.code || result.result === false) {
            throw new Error(getBroadcastErrorMessage(tronWeb, broadcast));
        }
    }
}
function assertArgCount(abi, functionName, args) {
    // Overload-aware: accept the call if its arg count matches ANY overload's
    // arity (the correct overload is then selected from the args downstream).
    const arities = (0, abi_js_1.overloadArities)(abi, functionName);
    if (arities.length === 0)
        return;
    const actual = Array.isArray(args) ? args.length : 0;
    if (!arities.includes(actual)) {
        const expected = arities.join(' or ');
        throw new Error(`Contract function "${functionName}" expects ${expected} argument(s) but received ${actual}.`);
    }
}
function assertContractAddress(contract) {
    if (!contract.address) {
        throw new Error('Smart contract is missing address');
    }
    return contract.address;
}
// async so every failure — including argument-count and signer validation —
// surfaces as a rejection rather than a synchronous throw.
async function invokeRead(contract, functionName, args, options) {
    assertArgCount(contract.abi, functionName, args);
    const address = assertContractAddress(contract);
    const tronWeb = contract.tronWeb;
    const fragment = getReadContractFragment(contract.abi, functionName, args);
    const { from, value } = options;
    const transaction = await tronWeb.transactionBuilder.triggerConstantContract(address, (0, abi_js_1.buildFunctionSelector)(fragment), {
        feeLimit: tronWeb.feeLimit,
        ...(value !== undefined ? { callValue: resolveCallValue(value) } : {}),
        funcABIV2: fragment,
        parametersV2: [...args],
    }, [], resolveCallerAddress(tronWeb, from, tronWeb.defaultAddress.base58 || undefined));
    return normalizeReadContractOutput(fragment, extractConstantResultData(tronWeb, transaction));
}
// async so every failure — including argument-count and signer validation —
// surfaces as a rejection rather than a synchronous throw.
async function invokeWrite(contract, functionName, args, options) {
    const { signerAddress, privateKey } = resolveSigner(contract.tronWeb, functionName, options.account);
    assertArgCount(contract.abi, functionName, args);
    const address = assertContractAddress(contract);
    const tronWeb = contract.tronWeb;
    const fragment = getWriteContractFragment(contract.abi, functionName, args);
    const { value, feeLimit, tokenId, tokenValue, permissionId } = options;
    const callValue = resolveCallValue(value);
    const txWrapper = await tronWeb.transactionBuilder.triggerSmartContract(address, (0, abi_js_1.buildFunctionSelector)(fragment), {
        feeLimit: feeLimit ?? tronWeb.feeLimit,
        ...(callValue !== undefined ? { callValue } : {}),
        ...(tokenId !== undefined ? { tokenId } : {}),
        ...(tokenValue !== undefined ? { tokenValue } : {}),
        ...(permissionId !== undefined ? { permissionId } : {}),
        funcABIV2: fragment,
        parametersV2: [...args],
    }, [], signerAddress);
    if (!txWrapper.result?.result) {
        throw new Error('Failed to build transaction: ' + (txWrapper.result?.message ?? JSON.stringify(txWrapper)));
    }
    if (!txWrapper.transaction || !txWrapper.transaction.txID) {
        throw new Error('triggerSmartContract did not return a valid transaction object');
    }
    // `privateKey` is set when `account` was a private key; otherwise undefined falls
    // back to the instance default key (trx.sign's default parameter).
    const signed = await tronWeb.trx.sign(txWrapper.transaction, privateKey);
    const broadcast = await tronWeb.trx.sendRawTransaction(signed);
    assertBroadcastOk(tronWeb, broadcast);
    return signed.txID;
}
/**
 * Build the `contract.read` namespace: every `view`/`pure` (or legacy
 * `constant`) ABI function exposed as
 * `read.fn([args], { from, value })`, executed through
 * `triggerConstantContract` and resolved to the decoded result
 * (single outputs are collapsed to the bare value).
 */
function buildReadNamespace(contract) {
    const read = Object.create(null);
    // `resolveName` is the function reference forwarded to `invokeRead`: the bare
    // name (overload, if any, resolved from the call's args) for the name key, or
    // the full signature for the selector key (pinning that exact overload).
    const makeReadMethod = (resolveName) => (argsOrOptions, options) => {
        const parameters = getFunctionParameters([argsOrOptions, options]);
        return invokeRead(contract, resolveName, parameters.args, parameters.options);
    };
    for (const fragment of contract.abi) {
        if (fragment.type !== 'function' || !('name' in fragment))
            continue;
        const functionFragment = fragment;
        if (!(0, abi_js_1.isReadOnlyFunctionFragment)(functionFragment))
            continue;
        const name = functionFragment.name;
        const selector = (0, abi_js_1.buildFunctionSelector)(functionFragment);
        read[name] = makeReadMethod(name);
        // Full selector (e.g. "balanceOf(address)") routes through
        // resolveFunctionFragment's explicit-signature branch, pinning this exact
        // fragment — so same-arity overloads are addressable unambiguously.
        read[selector] = makeReadMethod(selector);
    }
    return read;
}
/**
 * Build the `contract.write` namespace: every state-changing ABI function
 * exposed as `write.fn([args], { account, value, feeLimit, tokenId, tokenValue, permissionId })`,
 * built with `triggerSmartContract`, signed with the private key given as `account`
 * (or the instance's default private key when none is given), broadcast, and
 * resolved to the transaction ID.
 */
function buildWriteNamespace(contract) {
    const write = Object.create(null);
    // `resolveName` is the function reference forwarded to `invokeWrite`: the bare
    // name (overload, if any, resolved from the call's args) for the name key, or
    // the full signature for the selector key (pinning that exact overload).
    const makeWriteMethod = (resolveName) => (argsOrOptions, options) => {
        const parameters = getFunctionParameters([argsOrOptions, options]);
        return invokeWrite(contract, resolveName, parameters.args, parameters.options);
    };
    for (const fragment of contract.abi) {
        if (fragment.type !== 'function' || !('name' in fragment))
            continue;
        const functionFragment = fragment;
        if ((0, abi_js_1.isReadOnlyFunctionFragment)(functionFragment))
            continue;
        const name = functionFragment.name;
        const selector = (0, abi_js_1.buildFunctionSelector)(functionFragment);
        write[name] = makeWriteMethod(name);
        // Full selector (e.g. "transfer(address,uint256)") routes through
        // resolveFunctionFragment's explicit-signature branch, pinning this exact
        // fragment — so same-arity overloads are addressable unambiguously.
        write[selector] = makeWriteMethod(selector);
    }
    return write;
}
//# sourceMappingURL=readWrite.js.map