<svelte:options customElement={{ props: {} }} />

<script lang="ts">
  import NodeWrapper from '../core/NodeWrapper.svelte';
  import {type NodeProps, useNodesData, useSvelteFlow} from '@xyflow/svelte';
  import {Button, Heading, Select} from '../base';
  import RefParameterList from '../core/RefParameterList.svelte';
  import {getCurrentNodeId, getOptions} from '#components/utils/NodeUtils';
  import {useAddParameter} from '../utils/useAddParameter.svelte';
  import OutputDefList from '../core/OutputDefList.svelte';
  import {onDestroy, onMount} from 'svelte';
  import type {
    Parameter,
    ParameterChangeHandler,
    SelectItem,
    TinyflowNodeData,
  } from '#types';
  import CodeScriptEditor from '../core/CodeScriptEditor.svelte';
  import {genShortId} from '../utils/IdGen';
  import {createCodeNodeScaffold} from '../utils/codeNodeScaffold';
  import {
    analyzeCodeNodeOutputs,
    reconcileInferredCodeNodeOutputs,
    syncCodeNodeInputParameters,
    syncCodeNodeOutputDefinitions,
  } from '../utils/codeNodeSync';

  const { data, ...rest }: {
        data: TinyflowNodeData,
        [key: string]: any
    } = $props();

    const currentNodeId = getCurrentNodeId();
    const options = getOptions();
    let currentNode = useNodesData(currentNodeId);
    const { addParameter } = useAddParameter();
    const { updateNodeData } = useSvelteFlow();
    let syncHint = $state('');
    const codeNodeHelp = `代码如何返回结果
- 输入参数会按名称传入 main，例如输入参数 data 对应 main(data)。
- JavaScript 的 main 返回对象；Python 的 main 返回 dict。
- 历史代码中的 _result = main(...) 写法仍然兼容。

输出参数如何配置
- return 中的静态字段会自动补全到“输出参数”，并推断 String、Number、Boolean、File、Object、Array。
- 在“输出参数”中新增或改名，也会同步 main 的 return 字段。
- 下游节点可引用：代码节点ID.输出参数名。

JavaScript 示例
  function main(data) {
    return { answer: data, score: 95 };
  }

Python 示例
  def main(data):
      return {'answer': data, 'score': 95}

- 输出参数配置：answer(String)、score(Number)
- 结束节点输出参数可引用：代码节点ID.answer、代码节点ID.score`;

    const nodeData = $derived.by(() => {
        return (currentNode?.current?.data || data) as TinyflowNodeData;
    });
    const editorParameters = $derived.by(() => {
        return (nodeData.parameters as Parameter[]) || [];
    });

    let engines = $state<SelectItem[]>([
        { label: 'JavaScript', value: 'js' }
    ]);
    const defaultEngine = $derived.by(() => {
        const firstAvailable = engines.find((item) => item.selectable !== false);
        return firstAvailable?.value || 'js';
    });
    const codePlaceholder = $derived.by(() => {
        const engine = String(nodeData.engine || defaultEngine).trim().toLowerCase();
        if (engine === 'python' || engine === 'py') {
            return "请输入代码，例如：def main(data):\n    return {'output': data}";
        }
        return '请输入代码，例如：function main(data) { return { output: data }; }';
    });

    const reconcileOutputs = (
        code: string,
        engine: string,
        parameters: Parameter[],
        outputDefs: Parameter[],
    ) => {
        const inferred = analyzeCodeNodeOutputs(code, engine, parameters);
        return inferred
            ? reconcileInferredCodeNodeOutputs(
                outputDefs,
                inferred,
                genShortId
            )
            : outputDefs;
    };

    const handleInputParametersChange: ParameterChangeHandler = (
        previousParameters,
        nextParameters,
        currentData,
    ) => {
        const engine = String(currentData.engine || defaultEngine);
        const syncResult = syncCodeNodeInputParameters(
            String(currentData.code || ''),
            engine,
            previousParameters,
            nextParameters,
        );
        syncHint = syncResult.synced ? '' : (syncResult.reason || '');
        return {
            code: syncResult.code,
            mainArgsMode: syncResult.synced
                ? 'named'
                : currentData.mainArgsMode,
            parameters: syncResult.parameters,
            outputDefs: reconcileOutputs(
                syncResult.code,
                engine,
                syncResult.parameters,
                (currentData.outputDefs as Parameter[]) || [],
            ),
        };
    };

    const handleOutputDefinitionsChange: ParameterChangeHandler = (
        previousParameters,
        nextParameters,
        currentData,
    ) => {
        const syncResult = syncCodeNodeOutputDefinitions(
            String(currentData.code || ''),
            String(currentData.engine || defaultEngine),
            previousParameters,
            nextParameters,
        );
        syncHint = syncResult.synced ? '' : (syncResult.reason || '');
        return {
            code: syncResult.code,
            outputDefs: syncResult.outputDefs,
        };
    };

    let analysisTimer: ReturnType<typeof setTimeout> | undefined;
    let pendingAnalysisCode: string | undefined;

    // mainArgsMode 是持久化运行契约，人工编辑代码时只分析输出，禁止按形参文本切换协议。
    const applyOutputAnalysis = (code: string) => {
        updateNodeData(currentNodeId, (node) => {
            if (String(node.data.code || '') !== code) {
                return {};
            }
            const outputDefs = reconcileOutputs(
                code,
                String(node.data.engine || defaultEngine),
                (node.data.parameters as Parameter[]) || [],
                (node.data.outputDefs as Parameter[]) || [],
            );
            if (
                JSON.stringify(outputDefs) ===
                JSON.stringify(node.data.outputDefs || [])
            ) {
                return {};
            }
            return { outputDefs };
        });
    };

    const flushOutputAnalysis = () => {
        if (analysisTimer) {
            clearTimeout(analysisTimer);
            analysisTimer = undefined;
        }
        const code = pendingAnalysisCode;
        pendingAnalysisCode = undefined;
        if (code !== undefined) {
            applyOutputAnalysis(code);
        }
    };

    const scheduleOutputAnalysis = (code: string) => {
        if (analysisTimer) {
            clearTimeout(analysisTimer);
        }
        pendingAnalysisCode = code;
        analysisTimer = setTimeout(() => {
            analysisTimer = undefined;
            const pendingCode = pendingAnalysisCode;
            pendingAnalysisCode = undefined;
            if (pendingCode !== undefined) {
                applyOutputAnalysis(pendingCode);
            }
        }, 300);
    };

    const updateEngine = (engine: string) => {
        syncHint = '';
        updateNodeData(currentNodeId, (node) => {
            const patch: TinyflowNodeData = { engine };
            if (node.data.codeScaffoldManaged === true) {
                patch.code = createCodeNodeScaffold(
                    engine,
                    (node.data.parameters as Parameter[]) || [],
                    (node.data.outputDefs as Parameter[]) || [],
                );
                patch.codeScaffoldVersion = 2;
                patch.mainArgsMode = 'named';
            }
            return patch;
        });
    };

    onMount(async () => {
        const codeEngines = await options.provider?.codeEngine?.();
        if (codeEngines && codeEngines.length > 0) {
            engines = codeEngines;
        }

        const currentEngine = nodeData.engine;
        const currentEngineSupported = engines.some((item) => item.value === currentEngine && item.selectable !== false);
        if (!currentEngine || !currentEngineSupported) {
            updateEngine(String(defaultEngine));
        }
    });

    onDestroy(() => {
        // 页面切换前刷新最后一次分析，避免代码与输出参数持久化状态不一致。
        flushOutputAnalysis();
    });

</script>


<NodeWrapper
    {data}
    titleHelp={codeNodeHelp}
    {...rest}
>

    {#snippet icon()}
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
            <path
                d="M23 12L15.9289 19.0711L14.5147 17.6569L20.1716 12L14.5147 6.34317L15.9289 4.92896L23 12ZM3.82843 12L9.48528 17.6569L8.07107 19.0711L1 12L8.07107 4.92896L9.48528 6.34317L3.82843 12Z"></path>
        </svg>
    {/snippet}

    <div class="heading">
        <Heading level={3}>输入参数</Heading>
        <Button class="input-btn-more" style="margin-left: auto" onclick={()=>{
            addParameter(currentNodeId)
        }}>
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                <path d="M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z"></path>
            </svg>
        </Button>
    </div>
    <RefParameterList onParametersChange={handleInputParametersChange} />

    <Heading level={3} mt="10px">代码</Heading>
    <div class="setting-title">执行引擎</div>
    <div class="setting-item">
        <Select items={engines} style="width: 100%" placeholder="请选择执行引擎" onSelect={(item)=>{
              updateEngine(String(item.value));
        }} value={nodeData.engine ? [nodeData.engine] : [defaultEngine]} />
    </div>

    <div class="setting-title">执行代码</div>
    <div class="setting-item">
        <CodeScriptEditor
                  mode="textarea"
                  rows={10}
                  placeholder={codePlaceholder}
                  style="width: 100%"
                  engine={(nodeData.engine as string) || (defaultEngine as string)}
                  parameters={editorParameters}
                  oninput={(e:any)=>{
            const code = String(e.target.value || '');
            syncHint = '';
            updateNodeData(currentNodeId, ()=>{
                return {
                    code,
                    codeScaffoldManaged: false
                }
            });
            scheduleOutputAnalysis(code);
        }}
                  onchange={flushOutputAnalysis}
                  value={nodeData.code as string||""}
        />
    </div>
    {#if syncHint}
        <div class="sync-hint" role="status">{syncHint}</div>
    {/if}


    <div class="heading">
        <Heading level={3} mt="10px">输出参数</Heading>
        <Button class="input-btn-more" style="margin-left: auto" onclick={()=>{
            addParameter(currentNodeId,'outputDefs')
        }}>
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                <path d="M11 11V5H13V11H19V13H13V19H11V13H5V11H11Z"></path>
            </svg>
        </Button>
    </div>
    <OutputDefList onParametersChange={handleOutputDefinitionsChange} />

</NodeWrapper>

<style>
    .heading {
        display: flex;
        margin-bottom: 10px;
    }

    .setting-title {
        font-size: 12px;
        color: var(--tf-text-muted);
        margin-bottom: 4px;
        margin-top: 10px;
    }

    .setting-item {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 10px;
        gap: 10px;
    }

    .sync-hint {
        color: var(--tf-warning-soft-text);
        font-size: 12px;
        line-height: 18px;
        margin: -4px 0 8px;
    }

</style>
