//
// Copyright 2024 DXOS.org
//
import * as Effect from 'effect/Effect';
import * as SchemaAST from 'effect/SchemaAST';
import React, { forwardRef, useEffect, useMemo, useRef, useState } from 'react';
import { ValueBag } from '@dxos/conductor';
import { Database } from '@dxos/echo';
import { EdgeHttpClient } from '@dxos/edge-client';
import { runAndForwardErrors } from '@dxos/effect';
import { createEventLogger } from '@dxos/functions';
import { QueueService } from '@dxos/functions';
import { ServiceContainer } from '@dxos/functions-runtime';
import { RemoteFunctionExecutionService } from '@dxos/functions-runtime';
import { invariant } from '@dxos/invariant';
import { DXN } from '@dxos/keys';
import { LogLevel, log } from '@dxos/log';
import { useConfig } from '@dxos/react-client';
import { Avatar, Input, Toolbar, useAsyncEffect } from '@dxos/react-ui';
import { SyntaxHighlighter } from '@dxos/react-ui-syntax-highlighter';
import { errorText, mx } from '@dxos/ui-theme';
import { useDevtoolsState } from '../../../hooks';
// TODO: reconcile with DebugPanel in ScriptPlugin
export var WorkflowDebugPanelMode;
(function (WorkflowDebugPanelMode) {
    WorkflowDebugPanelMode["LOCAL"] = "local";
    WorkflowDebugPanelMode["REMOTE"] = "remote";
})(WorkflowDebugPanelMode || (WorkflowDebugPanelMode = {}));
export const WorkflowDebugPanel = (props) => {
    const config = useConfig();
    const inputRef = useRef(null);
    const { space } = useDevtoolsState();
    const [input, setInput] = useState('');
    const [isExecuting, setIsExecuting] = useState(false);
    const [history, setHistory] = useState([]);
    const [inputTemplate, setInputTemplate] = useState('');
    const edgeClient = useMemo(() => {
        const edgeUrl = config.values.runtime?.services?.edge?.url;
        invariant(edgeUrl, 'Edge url not configured');
        return new EdgeHttpClient(edgeUrl);
    }, [config]);
    useAsyncEffect(async () => {
        setInputTemplate('');
        await props.loader
            .load(DXN.fromLocalObjectId(props.graph.id))
            .then((workflow) => {
            const workflowMeta = workflow.resolveMeta();
            if (workflowMeta.inputs.length) {
                const inputTemplate = inputTemplateFromAst(workflowMeta.inputs[0].schema.ast);
                setInputTemplate(inputTemplate);
                if (!input.length) {
                    setInput(inputTemplate);
                }
            }
        })
            .catch(log.catch);
    }, [props.loader, props.graph.id]);
    const scrollerRef = useRef(null);
    const handleScroll = () => {
        if (scrollerRef.current) {
            scrollerRef.current.scrollTop = scrollerRef.current.scrollHeight;
        }
    };
    const controller = useRef(null);
    useEffect(() => handleStop(), []);
    const handleStop = () => {
        controller.current?.abort('stop');
        controller.current = null;
    };
    const handleClear = () => {
        handleStop();
        setInput('');
        setHistory([]);
        inputRef.current?.focus();
    };
    const handleResponse = ({ text, data, error, } = {}) => {
        controller.current = null;
        setHistory((history) => [...history, { type: 'response', text, data, error }]);
        setIsExecuting(false);
        handleScroll();
    };
    const handleRequest = async (input) => {
        invariant(space);
        try {
            invariant(input.charAt(0) === '{', 'Not a JSON object.');
            const validJsonString = input.replace(/'/g, '"').replace(/([{,]\s*)([a-zA-Z0-9_]+)(\s*:)/g, '$1"$2"$3');
            const requestBody = JSON.parse(validJsonString);
            setIsExecuting(true);
            setInput(inputTemplate);
            setHistory((history) => [...history, { type: 'request', text: JSON.stringify(requestBody, null, 2) }]);
            setTimeout(() => handleScroll());
            // Throws DOMException when aborted.
            controller.current = new AbortController();
            let response;
            if (props.mode === WorkflowDebugPanelMode.REMOTE) {
                response = await edgeClient.executeWorkflow(space.id, props.graph.id, requestBody);
            }
            else {
                const compiled = await props.loader.load(DXN.fromLocalObjectId(props.graph.id));
                response = await runAndForwardErrors(compiled
                    .run(ValueBag.make(requestBody))
                    .pipe(Effect.withSpan('runWorkflow'), Effect.flatMap(ValueBag.unwrap), Effect.provide(createLocalExecutionContext(space)), Effect.scoped));
            }
            handleResponse({ data: response });
        }
        catch (err) {
            if (err !== 'stop') {
                const error = err instanceof Error ? err : new Error(err);
                handleResponse({ error });
                log.catch(err);
            }
            else {
                handleResponse();
            }
        }
        finally {
            setIsExecuting(false);
        }
    };
    return (React.createElement("div", { className: mx('flex flex-col is-full bs-full overflow-hidden', props.classNames) },
        React.createElement(MessageThread, { ref: scrollerRef, history: history }),
        React.createElement(Toolbar.Root, null,
            React.createElement(Input.Root, null,
                React.createElement(Input.TextInput, { ref: inputRef, autoFocus: true, placeholder: 'Input JSON', value: input, onChange: (ev) => setInput(ev.target.value), onKeyDown: (ev) => ev.key === 'Enter' && handleRequest(input) })),
            React.createElement(Toolbar.IconButton, { icon: 'ph--play--regular', size: 4, label: 'Execute', iconOnly: true, onClick: () => handleRequest(input) }),
            React.createElement(Toolbar.IconButton, { icon: isExecuting ? 'ph--stop--regular' : 'ph--trash--regular', size: 4, label: isExecuting ? 'Stop' : 'Clear', iconOnly: true, onClick: () => (isExecuting ? handleStop() : handleClear()) }))));
};
const MessageThread = forwardRef(({ history }, forwardedRef) => {
    if (!history.length) {
        return null;
    }
    return (React.createElement("div", { ref: forwardedRef, className: 'flex flex-col gap-6 bs-full p-2 overflow-x-hidden overflow-y-auto' }, history.map((message, i) => (React.createElement("div", { key: i, className: 'grid grid-cols-[2rem_1fr_2rem]' },
        React.createElement("div", { className: 'p-1' }, message.type === 'response' && React.createElement(RobotAvatar, null)),
        React.createElement("div", { className: 'overflow-auto' },
            React.createElement(MessageItem, { message: message })))))));
});
const MessageItem = ({ classNames, message }) => {
    const { type, text, data, error } = message;
    const wrapper = 'p-1 pli-2 rounded-md bg-hoverSurface overflow-auto';
    return (React.createElement("div", { className: mx('flex', type === 'request' ? 'ml-[1rem] justify-end' : 'mr-[1rem]', classNames) },
        error && React.createElement("div", { className: mx(wrapper, 'whitespace-pre', errorText) }, String(error)),
        text !== undefined && (React.createElement("div", { className: mx(wrapper, type === 'request' && 'bg-primary-400 dark:bg-primary-600') }, text || '\u00D8')),
        data && (React.createElement(SyntaxHighlighter, { language: 'json', className: mx(wrapper, 'text-xs') }, JSON.stringify(data, null, 2)))));
};
const RobotAvatar = () => (React.createElement(Avatar.Root, null,
    React.createElement(Avatar.Content, { size: 6, variant: 'circle', icon: 'ph--robot--regular' })));
const createLocalExecutionContext = (space) => {
    return new ServiceContainer()
        .setServices({
        eventLogger: createEventLogger(LogLevel.INFO),
        database: Database.makeService(space.db),
        queues: QueueService.make(space.queues, undefined),
        functionCallService: RemoteFunctionExecutionService.mock(),
    })
        .createLayer();
};
const inputTemplateFromAst = (ast) => {
    return `{ ${SchemaAST.getPropertySignatures(ast)
        .map((property) => `"${property.name.toString()}": ""`)
        .join(', ')} }`;
};
//# sourceMappingURL=WorkflowDebugPanel.js.map