import BigNumber from "bignumber.js";
import groupBy from "lodash/groupBy";
import { findCryptoCurrencyById, formatCurrencyUnit } from "../../currencies";
import { getDefaultExplorerView, getAddressExplorer } from "../../explorers";
function round(n) {
    if (n === undefined)
        return undefined;
    return Math.round(n);
}
function makeCSV(data) {
    return data
        .map(row => row.map(cell => `"${typeof cell === "number" ? cell : cell || ""}"`).join(","))
        .join("\n");
}
export function csvReports(reports, specsPerBots) {
    const data = reports.map((report, i) => ({
        report,
        ...specsPerBots[i],
    }));
    const columns = [
        {
            label: "seed",
            get: (d) => d.seed,
        },
        {
            label: "family",
            get: (d) => d.family,
        },
        {
            label: "coin",
            get: (d) => d.key,
        },
        {
            label: "Accounts count",
            get: (d) => d.report.accountBalances?.length,
        },
        {
            label: "Operations count",
            get: (d) => d.report.accountOperationsLength?.reduce((a, b) => a + b, 0),
        },
        {
            label: "Sync Time (ms)",
            get: (d) => d.report.auditResult?.totalTime,
        },
        {
            label: "cpuUserTime (ms)",
            get: (d) => round(d.report.auditResult?.cpuUserTime),
        },
        {
            label: "networkBandwidth (bytes)",
            get: (d) => d.report.auditResult?.network.totalResponseSize,
        },
        {
            label: "networkCount",
            get: (d) => d.report.auditResult?.network.totalCount,
        },
        {
            label: "accountsJSONSize (bytes)",
            get: (d) => d.report.auditResult?.accountsJSONSize,
        },
    ];
    const csvs = [];
    csvs.push({
        filename: "csvs/all.csv",
        content: makeCSV([columns.map(c => c.label), ...data.map(d => columns.map(c => c.get(d)))]),
    });
    const byFamily = groupBy(data, d => d.family);
    for (const [family, data] of Object.entries(byFamily)) {
        csvs.push({
            filename: `csvs/by-family/${family}.csv`,
            content: makeCSV([columns.map(c => c.label), ...data.map(d => columns.map(c => c.get(d)))]),
        });
    }
    const byCurrency = groupBy(data, d => d.spec.currency.id);
    for (const [currencyId, data] of Object.entries(byCurrency)) {
        csvs.push({
            filename: `csvs/by-currency/${currencyId}.csv`,
            content: makeCSV([columns.map(c => c.label), ...data.map(d => columns.map(c => c.get(d)))]),
        });
    }
    return csvs;
}
export function finalMarkdownReport(reports, specsPerBots) {
    const data = reports.map((report, i) => ({ report, ...specsPerBots[i] }));
    const { table, title } = markdownHelpers(data);
    let md = "";
    md += title("Portfolio");
    md += table({
        title: "Funds status",
        formatValue: d => {
            const { spec } = d;
            if (!spec)
                return;
            const viableAccounts = (d.report?.accountBalances || []).filter(b => BigNumber(b).gt(spec.minViableAmount || 0)).length;
            if (viableAccounts > 0)
                return "✅";
            const explorerView = getDefaultExplorerView(d.spec.currency);
            const refillAddress = d.report.refillAddress || "";
            const url = !refillAddress
                ? ""
                : explorerView && refillAddress
                    ? getAddressExplorer(explorerView, refillAddress)
                    : "data:text," + refillAddress;
            const value = url;
            if (url) {
                return `[🙏](${url})`;
            }
            return value || "⚠️";
        },
    });
    md += table({
        title: "Balances",
        lenseValue: d => (d.report?.accountBalances || []).reduce((acc, r) => acc.plus(r), BigNumber(0)),
        reduce: (d, map) => d.reduce((acc, r) => acc.plus(map(r)), BigNumber(0)),
        formatValue: (v, ctx) => formatCurrencyUnit(ctx.spec.currency.units[0], v, { showCode: true }),
        totalPerCurrency: true,
    });
    md += table({
        title: "Accounts count",
        lenseValue: d => d.report?.accountBalances?.length || 0,
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "Operations count",
        lenseValue: d => d.report?.accountOperationsLength?.reduce((a, b) => a + b || 0, 0) || 0,
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += title("Performance");
    md += table({
        title: "Sync Times",
        lenseValue: d => d.report?.auditResult?.totalTime,
        formatValue: v => formatTime(v),
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "HTTP Calls Count",
        lenseValue: d => d.report?.auditResult?.network?.totalCount || 0,
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "HTTP Bandwidth",
        lenseValue: d => d.report?.auditResult?.network?.totalResponseSize || 0,
        formatValue: v => formatSize(v),
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "Duplicate HTTP Calls",
        lenseValue: d => d.report?.auditResult?.network?.totalDuplicateRequests || 0,
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "Accounts Data Size",
        lenseValue: d => d.report?.auditResult?.accountsJSONSize,
        formatValue: v => formatSize(v),
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "Currency Preloaded Data Size",
        lenseValue: d => d.report?.auditResult?.preloadJSONSize,
        formatValue: v => formatSize(v),
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    md += table({
        title: "CPU user time",
        lenseValue: d => d.report?.auditResult?.cpuUserTime,
        formatValue: v => formatTime(v),
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    /*
    md += table({
      title: "CPU system time",
      lenseValue: (d) => d.report?.auditResult?.cpuSystemTime,
      formatValue: (v) => formatTime(v),
      totalPerCurrency: true,
      totalPerSeed: true,
      totalPerFamily: true,
    });
    */
    md += table({
        title: "Memory RSS",
        lenseValue: d => d.report?.auditResult?.memoryEnd.rss,
        formatValue: v => formatSize(v),
    });
    md += table({
        title: "JS Boot Time",
        lenseValue: d => d.report?.auditResult?.jsBootTime,
        formatValue: v => formatTime(v),
    });
    md += table({
        title: "JS Slow Frames",
        reduce: (d, map) => d.reduce((acc, r) => ({
            count: acc.count + (map(r)?.count || 0),
            duration: acc.duration + (map(r)?.duration || 0),
        }), { count: 0, duration: 0 }),
        lenseValue: d => d.report?.auditResult?.slowFrames,
        formatValue: v => (v ? (v.count === 0 ? "✅" : `${v.count} (${formatTime(v.duration)})`) : ""),
        totalPerCurrency: true,
        totalPerSeed: true,
        totalPerFamily: true,
    });
    const errors = data.filter(d => d.report.error);
    if (errors.length) {
        md += "\n\n# Errors\n";
        const sortKey = d => `${d.spec.name}: ${d.report.error}`;
        md += errors
            .slice(0)
            .sort((a, b) => sortKey(a).localeCompare(sortKey(b)))
            .map(d => `- ${d.seed}: ${d.spec.name}: ${d.report.error}`)
            .join("\n");
    }
    return md;
}
function formatTime(ms) {
    if (!ms)
        return;
    const s = ms / 1000;
    return s < 9 ? `${s.toFixed(2)}s` : `${s.toFixed(0)}s`;
}
function formatSize(bytes) {
    if (!bytes)
        return;
    const kb = bytes / 1024;
    return kb < 1024 ? `${kb.toFixed(0)}kb` : `${(kb / 1024).toFixed(0)}mb`;
}
function markdownHelpers(data) {
    const seedNames = Array.from(new Set(data.map(d => d.seed)));
    seedNames.sort();
    // specs are assumed to be grouped by family already
    const specs = Array.from(new Set(data.map(d => d.spec)));
    function strong(txt) {
        return "**" + txt + "**";
    }
    function title(txt) {
        return "\n# " + txt + "\n\n";
    }
    function genTable(f, opts = {}) {
        const { totalPerFamily, totalPerCurrency, totalPerSeed } = opts;
        let md = "\n\n";
        md += "| Currency |" + seedNames.join(" | ");
        if (totalPerCurrency) {
            md += "| Total ";
        }
        md += "|\n";
        md += "|--|" + Array.from(seedNames).fill("--|").join("");
        if (totalPerCurrency) {
            md += "--|";
        }
        md += "\n";
        let lastFamily = "";
        for (const spec of specs) {
            let soloInFamilyFactorisation = false;
            if (totalPerFamily && spec.currency.family !== lastFamily) {
                lastFamily = spec.currency.family;
                soloInFamilyFactorisation = !specs.some(s => s !== spec && s.currency.family === lastFamily);
                if (!soloInFamilyFactorisation) {
                    const familyName = findCryptoCurrencyById(spec.currency.family)?.name || spec.currency.family;
                    md += "| " + strong(familyName) + " family | ";
                    md += seedNames
                        .map(seed => {
                        const all = data.filter(d => d.seed === seed && d.spec.currency.family === lastFamily);
                        return (totalPerFamily(all) || "") + " |";
                    })
                        .join("");
                    if (totalPerCurrency) {
                        const all = data.filter(d => d.spec.currency.family === lastFamily);
                        md += (totalPerCurrency(all) || "") + " |";
                    }
                    md += "\n";
                }
            }
            const name = spec.currency.name;
            md += "| " + (soloInFamilyFactorisation ? strong(name) : name) + " | ";
            md +=
                seedNames
                    .map(seed => {
                    const d = data.find(d => d.seed === seed && d.spec === spec);
                    if (!d)
                        return "?";
                    if (d.report.error)
                        return "❌";
                    return f(d) || "";
                })
                    .join(" | ") + " |";
            if (totalPerCurrency) {
                md += (totalPerCurrency(data.filter(d => d.spec === spec)) || "") + " |";
            }
            md += "\n";
        }
        if (totalPerSeed) {
            md += "| **Total** | ";
            md += seedNames
                .map(seed => {
                const all = data.filter(d => d.seed === seed);
                return (totalPerSeed(all) || "") + " |";
            })
                .join("");
            if (totalPerCurrency) {
                const all = data;
                md += (totalPerCurrency(all) || "") + " |";
            }
            md += "\n";
        }
        md += "\n";
        return md;
    }
    // as any is a hack to make a "defaults" that would work for most case. we will need to see how to make typing better
    const defaults = {
        lenseValue: (d) => d,
        formatValue: (v) => String(v),
        reduce: (v, map) => v.reduce((sum, v) => sum + Number(map(v)), 0),
    };
    const table = opts => {
        const { title, lenseValue, formatValue, totalPerFamily, totalPerCurrency, totalPerSeed, reduce, } = { ...defaults, ...opts };
        const total = d => formatValue(reduce(d, lenseValue), d[0]);
        let md = `\n<details><summary><b>${title}</b></summary>\n`;
        md += genTable(d => formatValue(lenseValue(d), d), {
            totalPerFamily: totalPerFamily ? total : undefined,
            totalPerCurrency: totalPerCurrency ? total : undefined,
            totalPerSeed: totalPerSeed ? total : undefined,
        });
        md += "</details>\n\n";
        return md;
    };
    return { table, strong, title };
}
//# sourceMappingURL=formatter.js.map