"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.loadSignatureFile = exports.readGzipFile = exports.loadAbiFile = exports.parseAbiFileContents = exports.computeRelativePath = exports.searchAbiFiles = exports.extractDeployedContractAddresses = exports.isTruffleBuildFile = exports.isAbiItemArray = exports.isAbiItem = exports.AbiError = void 0;
const tslib_1 = require("tslib");
const fs_extra_1 = require("fs-extra");
const path_1 = require("path");
const debug_logging_1 = require("@splunkdlt/debug-logging");
const contract_1 = require("./contract");
const signature_1 = require("./signature");
const zlib_1 = require("zlib");
const bl_1 = tslib_1.__importDefault(require("bl"));
const error_1 = require("../utils/error");
const { debug, warn, trace } = debug_logging_1.createModuleDebug('abi:files');
class AbiError extends error_1.RuntimeError {
}
exports.AbiError = AbiError;
function isAbiItem(obj) {
    return typeof obj.type === 'string';
}
exports.isAbiItem = isAbiItem;
function isAbiItemArray(obj) {
    return Array.isArray(obj) && obj.every(isAbiItem);
}
exports.isAbiItemArray = isAbiItemArray;
function isTruffleBuildFile(obj) {
    return typeof obj === 'object' && isAbiItemArray(obj.abi);
}
exports.isTruffleBuildFile = isTruffleBuildFile;
function extractDeployedContractAddresses(truffleBuild) {
    if (truffleBuild.networks) {
        return Object.values(truffleBuild.networks)
            .filter(({ address }) => address != null)
            .map(({ address }) => address.toLowerCase());
    }
}
exports.extractDeployedContractAddresses = extractDeployedContractAddresses;
async function* searchAbiFiles(dir, config) {
    var _a, _b;
    debug('Searching for ABI files in %s', dir);
    const dirContents = await fs_extra_1.readdir(dir).catch(e => Promise.reject(new AbiError(`Failed to load ABIs from directory ${dir}: ${e}`)));
    dirContents.sort();
    const subdirs = [];
    for (const f of dirContents) {
        const full = path_1.join(dir, f);
        const s = await fs_extra_1.stat(full);
        if (s.isDirectory() && ((_a = config.searchRecursive) !== null && _a !== void 0 ? _a : true)) {
            subdirs.push(path_1.join(dir, f));
        }
        else if (s.isFile() && f.endsWith((_b = config.abiFileExtension) !== null && _b !== void 0 ? _b : '.json')) {
            yield full;
        }
    }
    for (const sub of subdirs) {
        yield* searchAbiFiles(sub, config);
    }
}
exports.searchAbiFiles = searchAbiFiles;
function computeRelativePath(fileName, basePath) {
    if (basePath != null) {
        const resolvedBased = path_1.resolve(basePath);
        const resolvedFileName = path_1.resolve(fileName);
        if (resolvedFileName.startsWith(resolvedBased)) {
            return resolvedFileName.slice(resolvedBased.length + 1);
        }
    }
    return null;
}
exports.computeRelativePath = computeRelativePath;
function parseAbiFileContents(abiData, { computeFingerprint, fileName, basePath }) {
    var _a;
    let abis;
    let contractName;
    let contractAddress;
    debug('Parsing contents of ABI file %s', fileName);
    if (isTruffleBuildFile(abiData)) {
        debug('ABI file contains truffle build output');
        abis = abiData.abi;
        contractName =
            abiData.contractName ||
                // Fall back to file name without file extension
                path_1.basename(fileName).split('.', 1)[0];
        const addresses = extractDeployedContractAddresses(abiData);
        if (addresses != null) {
            if (addresses.length > 1) {
                warn('Found contract %s deployed to multiple (%d) networks, using address %s of first network', contractName, addresses.length, addresses[0]);
            }
            contractAddress = addresses[0];
        }
    }
    else if (isAbiItemArray(abiData)) {
        abis = abiData;
        contractName = path_1.basename(fileName).split('.', 1)[0];
    }
    else {
        throw new AbiError(`Invalid contents of ABI file ${fileName}`);
    }
    const entries = abis
        .filter(abi => (abi.type === 'function' || abi.type === 'event') && abi.name != null)
        .map(abi => {
        var _a;
        return ({
            abi,
            sig: signature_1.computeSignature({ name: abi.name, inputs: (_a = abi.inputs) !== null && _a !== void 0 ? _a : [], type: 'function' })
        });
    });
    let contractFingerprint;
    if (computeFingerprint) {
        const functions = entries
            .filter(i => i.abi.type === 'function')
            .map(i => i.sig)
            .sort();
        const events = entries
            .filter(i => i.abi.type === 'event')
            .map(i => i.sig)
            .sort();
        contractFingerprint = contract_1.computeContractFingerprint({ functions, events });
        if (debug_logging_1.TRACE_ENABLED) {
            trace('Computed contract fingerprint %s for contract signature %s contents %O', contractFingerprint, contractName, { functions, events });
        }
        else {
            debug('Computed contract fingerprint %s for contract signature %s', contractFingerprint, contractName);
        }
    }
    return {
        contractName,
        contractAddress,
        contractFingerprint: contractFingerprint,
        entries: entries.map(item => {
            var _a;
            return ({
                sig: item.sig,
                abi: {
                    name: item.abi.name,
                    type: item.abi.type,
                    inputs: (_a = item.abi.inputs) !== null && _a !== void 0 ? _a : [],
                    contractName,
                    contractFingerprint,
                    contractAddress
                }
            });
        }),
        fileName: (_a = computeRelativePath(fileName, basePath)) !== null && _a !== void 0 ? _a : path_1.basename(fileName)
    };
}
exports.parseAbiFileContents = parseAbiFileContents;
async function loadAbiFile(path, config) {
    const contents = await fs_extra_1.readFile(path, { encoding: 'utf-8' });
    let data;
    try {
        data = JSON.parse(contents);
    }
    catch (e) {
        warn('bad file formatting in abi %s', path);
    }
    if (data === undefined) {
        return Promise.reject();
    }
    return await parseAbiFileContents(data, {
        fileName: path,
        computeFingerprint: config.fingerprintContracts,
        basePath: config.directory
    });
}
exports.loadAbiFile = loadAbiFile;
async function readGzipFile(path, { encoding = 'utf-8' } = {}) {
    return new Promise((resolve, reject) => {
        const result = new bl_1.default();
        fs_extra_1.createReadStream(path)
            .pipe(zlib_1.createGunzip())
            .on('data', chunk => {
            result.append(chunk);
        })
            .on('end', () => {
            resolve(result.toString(encoding));
        })
            .on('error', e => {
            reject(e);
        });
    });
}
exports.readGzipFile = readGzipFile;
async function loadSignatureFile(path) {
    const contents = await readGzipFile(path);
    return JSON.parse(contents);
}
exports.loadSignatureFile = loadSignatureFile;
