//
// Copyright 2025 DXOS.org
//
import * as Array from 'effect/Array';
import * as Match from 'effect/Match';
import * as Option from 'effect/Option';
import * as Schema from 'effect/Schema';
import React, { useCallback, useMemo, useState } from 'react';
import { Filter } from '@dxos/echo';
import { Format } from '@dxos/echo/internal';
import { TraceEvent } from '@dxos/functions-runtime';
import { DXN } from '@dxos/keys';
import { useQuery } from '@dxos/react-client/echo';
import { Toolbar } from '@dxos/react-ui';
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
import { DynamicTable } from '@dxos/react-ui-table';
import { Tabs } from '@dxos/react-ui-tabs';
import { mx } from '@dxos/ui-theme';
import { PanelContainer } from '../../../components';
import { DataSpaceSelector } from '../../../containers';
import { ExceptionPanel } from './ExceptionPanel';
import { ExecutionGraphPanel } from './ExecutionGraphPanel';
import { useFunctionNameResolver, useInvocationSpans } from './hooks';
import { LogPanel } from './LogPanel';
import { RawDataPanel } from './RawDataPanel';
import { formatDuration } from './utils';
export const InvocationTraceContainer = ({ db, queueDxn, detailAxis = 'inline', showSpaceSelector = false, target, }) => {
    const resolver = useFunctionNameResolver({ db });
    const invocationSpans = useInvocationSpans({ queueDxn, target });
    const [selectedId, setSelectedId] = useState();
    const selectedInvocation = useMemo(() => {
        if (!selectedId) {
            return undefined;
        }
        return invocationSpans.find((span) => selectedId === span.id);
    }, [selectedId, invocationSpans]);
    const properties = useMemo(() => {
        function* generateProperties() {
            if (target === undefined) {
                yield { name: 'target', title: 'Target', format: Format.TypeFormat.String, size: 200 };
            }
            yield* [
                {
                    name: 'time',
                    title: 'Started',
                    format: Format.TypeFormat.DateTime,
                    sort: 'desc',
                    size: 194,
                },
                {
                    name: 'status',
                    title: 'Status',
                    format: Format.TypeFormat.SingleSelect,
                    size: 110,
                    config: {
                        options: [
                            { id: 'pending', title: 'Pending', color: 'blue' },
                            { id: 'success', title: 'Success', color: 'emerald' },
                            { id: 'failure', title: 'Failure', color: 'red' },
                            { id: 'unknown', title: 'Unknown', color: 'neutral' },
                        ],
                    },
                },
                {
                    name: 'duration',
                    title: 'Duration',
                    format: Format.TypeFormat.Duration,
                    size: 110,
                },
                {
                    name: 'queue',
                    title: 'Queue',
                    format: Format.TypeFormat.String,
                    // TODO(burdon): Add formatter.
                    // formatter: (value: string) => value.split(':').pop(),
                    size: 400,
                },
            ];
        }
        return [...generateProperties()];
    }, [target]);
    const rows = useMemo(() => {
        return invocationSpans.map((invocation) => {
            const status = invocation.outcome;
            // Handle both Ref objects and encoded references.
            const targetDxn = invocation.invocationTarget?.dxn ??
                (invocation.invocationTarget && '/' in invocation.invocationTarget
                    ? DXN.parse(invocation.invocationTarget['/'])
                    : undefined);
            // TODO(burdon): Use InvocationTraceStartEvent.
            return {
                id: invocation.id,
                target: resolver(targetDxn),
                // TODO(burdon): Change to timestamp?
                time: new Date(invocation.timestamp),
                duration: formatDuration(invocation.duration),
                status,
                queue: invocation.invocationTraceQueue?.dxn?.toString() ??
                    (invocation.invocationTraceQueue && '/' in invocation.invocationTraceQueue
                        ? invocation.invocationTraceQueue['/']
                        : 'unknown'),
                _original: invocation,
            };
        });
    }, [invocationSpans, resolver]);
    const handleRowClick = useCallback((row) => {
        if (!row) {
            return;
        }
        setSelectedId(row.id);
    }, []);
    const gridLayout = useMemo(() => {
        if (selectedInvocation) {
            switch (detailAxis) {
                case 'inline':
                    return 'grid grid-cols-[1fr_30rem]';
                case 'block':
                    return 'grid grid-rows-[1fr_2fr]';
            }
        }
        return 'grid grid-cols-1';
    }, [selectedInvocation, detailAxis]);
    const features = useMemo(() => ({
        selection: { enabled: true, mode: 'single' },
    }), []);
    return (React.createElement(PanelContainer, { toolbar: showSpaceSelector ? (React.createElement(Toolbar.Root, { classNames: 'border-be border-subduedSeparator' },
            React.createElement(DataSpaceSelector, null))) : undefined },
        React.createElement("div", { className: mx('bs-full', gridLayout) },
            React.createElement(DynamicTable, { properties: properties, rows: rows, features: features, onRowClick: handleRowClick }),
            selectedInvocation && React.createElement(Selected, { span: selectedInvocation }))));
};
const Selected = ({ span }) => {
    const [activeTab, setActiveTab] = useState('input');
    const queue = span.invocationTraceQueue?.target;
    const objects = useQuery(queue, Filter.everything());
    const contents = Array.head(objects).pipe(Option.getOrUndefined, Match.value, Match.not(Match.defined, () => 'unknown'), Match.when(Schema.is(TraceEvent), () => 'logs'), Match.orElse(() => 'execution-graph'));
    const isLogQueue = 'logs' === contents || objects.length === 0;
    return (React.createElement("div", { className: 'grid grid-cols-1 grid-rows-[min-content_1fr] bs-full min-bs-0 border-separator' },
        React.createElement(Tabs.Root, { classNames: 'grid grid-rows-[min-content_1fr] min-bs-0 [&>[role="tabpanel"]]:min-bs-0 [&>[role="tabpanel"][data-state="active"]]:grid border-bs border-separator', orientation: 'horizontal', value: activeTab, onValueChange: setActiveTab },
            React.createElement(Tabs.Tablist, { classNames: 'border-be border-separator' },
                React.createElement(Tabs.Tab, { value: 'input' }, "Input"),
                isLogQueue && React.createElement(Tabs.Tab, { value: 'logs' }, "Logs"),
                isLogQueue && React.createElement(Tabs.Tab, { value: 'errors' }, "Error logs"),
                isLogQueue && React.createElement(Tabs.Tab, { value: 'raw' }, "Raw"),
                span.error && React.createElement(Tabs.Tab, { value: 'failure' }, "Failure"),
                contents === 'execution-graph' && React.createElement(Tabs.Tab, { value: 'execution-graph' }, "Execution Graph")),
            React.createElement(Tabs.Tabpanel, { value: 'input' },
                React.createElement(SyntaxHighlighter, { language: 'json' }, JSON.stringify(span.input, null, 2))),
            isLogQueue && (React.createElement(Tabs.Tabpanel, { value: 'logs' },
                React.createElement(LogPanel, { objects: objects }))),
            isLogQueue && (React.createElement(Tabs.Tabpanel, { value: 'errors' },
                React.createElement(ExceptionPanel, { objects: objects }))),
            isLogQueue && (React.createElement(Tabs.Tabpanel, { value: 'raw', classNames: 'min-bs-0 min-is-0 is-full overflow-auto' },
                React.createElement(RawDataPanel, { classNames: 'text-xs', span: span, objects: objects }))),
            span.error && (React.createElement(Tabs.Tabpanel, { value: 'failure' },
                React.createElement(SpanErrorPanel, { exception: span.error }))),
            contents === 'execution-graph' && (React.createElement(Tabs.Tabpanel, { value: 'execution-graph' },
                React.createElement(ExecutionGraphPanel, { queue: queue }))))));
};
const SpanErrorPanel = ({ exception }) => {
    return (React.createElement("div", { className: 'text-xs whitespace-pre-wrap m-4' },
        React.createElement("div", null,
            "Code: ",
            exception.name),
        React.createElement("div", null,
            "Message: ",
            exception.message),
        React.createElement("div", null),
        React.createElement("div", null,
            "Stack: ",
            exception.stack),
        React.createElement("div", null),
        exception.cause && (React.createElement(React.Fragment, null,
            React.createElement("div", null, "Caused by:"),
            React.createElement(SpanErrorPanel, { exception: exception.cause })))));
};
export default InvocationTraceContainer;
//# sourceMappingURL=InvocationTraceContainer.js.map