//
// Copyright 2021 DXOS.org
//
import bytes from 'bytes';
import React, { useEffect, useMemo, useState } from 'react';
import { log } from '@dxos/log';
import { BlobMeta } from '@dxos/protocols/proto/dxos/echo/blob';
import { PublicKey, useClient } from '@dxos/react-client';
import { useDevtools, useStream } from '@dxos/react-client/devtools';
import { useAsyncEffect } from '@dxos/react-hooks';
import { DropdownMenu, Icon, Toolbar, Tree, TreeItem } from '@dxos/react-ui';
import { BitField } from '@dxos/util';
import { Bitbar, JsonView, PanelContainer } from '../../../components';
const getInfoTree = (storageInfo, feedInfo, snapshots, blobs) => [
    {
        id: 'origin',
        iconName: 'ph--git-commit--regular',
        Element: (React.createElement(TreeItemText, { primary: 'origin', secondary: `${bytes.format(storageInfo.originUsage)} / ${bytes.format(storageInfo.usageQuota)} ${formatPercent(storageInfo.originUsage / storageInfo.usageQuota)}` })),
        items: [
            {
                id: 'storage',
                iconName: 'ph--hard-drive--regular',
                Element: React.createElement(TreeItemText, { primary: storageInfo.type, secondary: bytes.format(storageInfo.storageUsage) }),
                items: [
                    {
                        id: 'feeds',
                        iconName: 'ph--queue--regular',
                        Element: React.createElement(TreeItemText, { primary: 'feeds', secondary: feedInfo.feeds?.length ?? 0 }),
                        items: feedInfo.feeds?.map((feed) => ({
                            id: feed.feedKey.toHex(),
                            iconName: 'ph--rows--regular',
                            Element: React.createElement(TreeItemText, { primary: feed.feedKey.truncate(), secondary: bytes.format(feed.bytes) }),
                            value: { kind: 'feed', feed },
                        })),
                    },
                    {
                        id: 'blobs',
                        iconName: 'ph--files--regular',
                        Element: React.createElement(TreeItemText, { primary: 'blobs', secondary: blobs.length }),
                        items: blobs.map((blob) => ({
                            id: PublicKey.from(blob.id).toHex(),
                            iconName: 'ph--file-archive--regular',
                            Element: (React.createElement(TreeItemText, { primary: PublicKey.from(blob.id).truncate(), secondary: bytes.format(blob.length) })),
                            value: { kind: 'blob', blob },
                        })),
                    },
                    {
                        id: 'snapshots',
                        iconName: 'ph--bookmarks--regular',
                        Element: React.createElement(TreeItemText, { primary: 'snapshots', secondary: snapshots.length }),
                        items: snapshots.map((snapshot) => ({
                            id: snapshot.key,
                            iconName: 'ph--bookmark--regular',
                            Element: React.createElement(TreeItemText, { primary: snapshot.key, secondary: bytes.format(snapshot.size) }),
                            value: { kind: 'snapshot' },
                        })),
                    },
                ],
            },
        ],
    },
];
export const StoragePanel = () => {
    const devtoolsHost = useDevtools();
    const [isRefreshing, setIsRefreshing] = useState(false);
    const [storageInfo, setStorageInfo] = useState();
    const [snapshotInfo, setSnapshotInfo] = useState();
    const [blobsInfo, setBlobsInfo] = useState();
    const feeds = useStream(() => devtoolsHost.subscribeToFeeds({}), {}, []);
    const client = useClient();
    const services = client.services.services;
    if (!services) {
        return null;
    }
    const [selected, setSelected] = useState();
    const refresh = async () => {
        setIsRefreshing(true);
        let retry = false;
        let storageInfo;
        let snapshotInfo;
        let blobsInfo;
        try {
            storageInfo = await devtoolsHost.getStorageInfo();
        }
        catch (err) {
            log.catch(err);
            retry = true;
        }
        try {
            snapshotInfo = await devtoolsHost.getSnapshots();
        }
        catch (err) {
            log.catch(err);
            retry = true;
        }
        try {
            blobsInfo = await devtoolsHost.getBlobs();
        }
        catch (err) {
            log.catch(err);
            retry = true;
        }
        setBlobsInfo(blobsInfo);
        setStorageInfo(storageInfo);
        setSnapshotInfo(snapshotInfo);
        setIsRefreshing(false);
        if (retry) {
            setTimeout(refresh, 500);
        }
    };
    useAsyncEffect(refresh, []);
    const items = useMemo(() => getInfoTree(storageInfo ?? {
        type: '',
        originUsage: 0,
        storageUsage: 0,
        usageQuota: 0,
    }, feeds, snapshotInfo?.snapshots ?? [], blobsInfo?.blobs ?? []), [storageInfo, snapshotInfo, blobsInfo]);
    useEffect(() => {
        if (!selected) {
            return;
        }
        const build = (items) => {
            for (const item of items) {
                if (item.id !== undefined && item.id === selected.id) {
                    setSelected(item);
                    return;
                }
                if (item.items) {
                    build(item.items);
                }
            }
        };
        build(items);
    }, [items]);
    const selectedValue = selected?.value;
    return (React.createElement(PanelContainer, { classNames: 'grid grid-cols-2 divide-x divide-separator', toolbar: React.createElement(Toolbar.Root, null,
            React.createElement(Toolbar.Button, { onClick: refresh, disabled: isRefreshing }, "Refresh"),
            React.createElement("div", { className: 'grow' }),
            React.createElement(DropdownMenu.Root, null,
                React.createElement(DropdownMenu.Trigger, { asChild: true },
                    React.createElement(Toolbar.Button, null, "Reset Storage")),
                React.createElement(DropdownMenu.Portal, null,
                    React.createElement(DropdownMenu.Content, { side: 'top' },
                        React.createElement(DropdownMenu.Viewport, null,
                            React.createElement(DropdownMenu.Item, { onClick: async () => {
                                    await services?.SystemService?.reset();
                                    location.reload();
                                } }, "Confirm Reset Storage?")),
                        React.createElement(DropdownMenu.Arrow, null))))) },
        React.createElement(DataTree, { items: items, onSelect: setSelected }),
        selectedValue && (React.createElement("div", { className: 'flex flex-col divide-y divide-separator grow overflow-auto scrollbar-thin' },
            selectedValue.kind === 'blob' && (React.createElement(React.Fragment, null,
                React.createElement("div", { className: 'p-1' },
                    "Downloaded ",
                    formatPercent(calculateBlobProgress(selectedValue.blob))),
                React.createElement(Bitbar, { value: selectedValue.blob.bitfield ?? new Uint8Array(), length: Math.ceil(selectedValue.blob.length / selectedValue.blob.chunkSize), className: 'm-2' }),
                React.createElement(JsonView, { data: selectedValue.blob }))),
            selectedValue.kind === 'feed' && (React.createElement(React.Fragment, null,
                React.createElement(Bitbar, { value: selectedValue.feed.downloaded ?? new Uint8Array(), length: Math.ceil(selectedValue.feed.length ?? 0), className: 'm-2' }),
                React.createElement(JsonView, { data: selectedValue.feed })))))));
};
const calculateBlobProgress = (blob) => {
    if (blob.state === BlobMeta.State.FULLY_PRESENT) {
        return 1;
    }
    return (BitField.count(blob.bitfield ?? new Uint8Array(), 0, (blob.bitfield?.length ?? 0) * 8) /
        Math.ceil(blob.length / blob.chunkSize));
};
const formatPercent = (ratio) => (ratio * 100).toFixed(0) + '%';
const TreeItemText = ({ primary, secondary }) => (React.createElement("div", { className: 'flex gap-2 overflow-hidden whitespace-nowrap' },
    React.createElement("span", { className: 'font-mono' }, primary),
    React.createElement("span", { className: 'text-neutral-400' }, secondary)));
const DataTree = ({ items = [], onSelect }) => {
    return (React.createElement(Tree.Root, { classNames: 'p-2' },
        React.createElement(DataItems, { items: items, onSelect: onSelect })));
};
const DataItems = ({ items = [], onSelect }) => {
    return (React.createElement(React.Fragment, null, items.map((item) => {
        const { id, iconName, Element, items } = item;
        return (React.createElement(TreeItem.Root, { key: id, collapsible: !!items?.length, open: true },
            React.createElement("div", { role: 'none', className: 'flex grow items-center gap-2 font-mono', onClick: () => onSelect(item) },
                React.createElement(Icon, { icon: iconName }),
                Element),
            React.createElement(TreeItem.Body, { className: 'pis-4' },
                React.createElement(Tree.Branch, null, items && React.createElement(DataItems, { items: items, onSelect: onSelect })))));
    })));
};
//# sourceMappingURL=StoragePanel.js.map