<script setup lang="ts">
import { nextTick, onUnmounted, ref, watch } from 'vue';

import { EasyFlowPanelModal } from '@easyflow/common-ui';
import { $t } from '@easyflow/locales';
import { preferences } from '@easyflow/preferences';
import { sortNodes } from '@easyflow/utils';

import { VideoPlay } from '@element-plus/icons-vue';
import { ElAlert, ElButton, ElMenu, ElMenuItem, ElMessage } from 'element-plus';
import { JsonViewer } from 'vue3-json-viewer';

import { api } from '#/api/request';
import PluginRunParams from '#/views/ai/plugin/PluginRunParams.vue';
import WorkflowSteps from '#/views/ai/workflow/components/WorkflowSteps.vue';

import 'vue3-json-viewer/dist/vue3-json-viewer.css';

const props = defineProps<{
  pluginToolId: string;
}>();

const themeMode = ref(preferences.theme.mode);
watch(
  () => preferences.theme.mode,
  (newVal) => {
    themeMode.value = newVal;
  },
);
const dialogVisible = ref(false);
const runTitle = ref('');
const runResult = ref('');
const inputDataParams = ref<any>(null);
const runResultResponse = ref<any>(null);
const runParamsRef = ref();
const runLoading = ref(false);
const pluginAvailable = ref(true);
const pluginReasonMessage = ref('');
const isWorkflowPlugin = ref(false);
const workflowId = ref<null | string>(null);
const workflowSnapshot = ref<any>(null);
const workflowNodeJson = ref<any[]>([]);
const pollingNodes = ref<any[]>([]);
const executeId = ref('');
const pollingData = ref<any>({ nodes: {} });
const initSignal = ref(false);
const POLLING_INTERVAL_MS = 1000;
const pollingTimer = ref<null | ReturnType<typeof setTimeout>>(null);
let pollingActive = false;
let pollingGeneration = 0;
const activeIndex = ref('1');
const dialogContentKey = ref(0);
const dialogPreparing = ref(false);

defineExpose({
  openDialog,
});

async function openDialog() {
  if (dialogPreparing.value) {
    return;
  }
  dialogPreparing.value = true;
  resetDialogState();
  const ready = await getPluginToolInfo();
  dialogPreparing.value = false;
  if (!ready) {
    return;
  }
  dialogContentKey.value += 1;
  await nextTick();
  dialogVisible.value = true;
}

function resetExecutionState() {
  stopPolling();
  runResultResponse.value = null;
  runLoading.value = false;
  executeId.value = '';
  pollingData.value = { nodes: {} };
  initSignal.value = !initSignal.value;
  activeIndex.value = '1';
}

function resetDialogState() {
  resetExecutionState();
  runTitle.value = '';
  runResult.value = '';
  inputDataParams.value = [];
  pluginAvailable.value = true;
  pluginReasonMessage.value = '';
  isWorkflowPlugin.value = false;
  workflowId.value = null;
  workflowSnapshot.value = null;
  workflowNodeJson.value = [];
  pollingNodes.value = [];
}

async function getPluginToolInfo() {
  try {
    const res = await api.post('/api/v1/pluginItem/tool/search', {
      aiPluginToolId: props.pluginToolId,
    });
    if (res.errorCode !== 0 || !res.data) {
      ElMessage.error(res?.message || '加载试运行信息失败');
      return false;
    }
    runTitle.value = `${res.data.aiPlugin.title} - ${res.data.data.name} ${$t(
      'pluginItem.inputData',
    )}`;
    runResult.value = `${$t('pluginItem.pluginToolEdit.runResult')}`;
    inputDataParams.value = JSON.parse(res.data.data.inputData || '[]');
    pluginAvailable.value = res.data.aiPlugin?.available !== false;
    pluginReasonMessage.value = res.data.aiPlugin?.reasonMessage || '';
    isWorkflowPlugin.value = Number(res.data.aiPlugin?.type || 1) === 2;
    workflowId.value = res.data.aiPlugin?.workflowId
      ? String(res.data.aiPlugin.workflowId)
      : null;
    workflowSnapshot.value = res.data.workflowSnapshot || null;
    hydrateWorkflowNodes(workflowSnapshot.value);
    return true;
  } catch (error) {
    ElMessage.error(buildErrorResult(error).error);
    return false;
  }
}

function hydrateWorkflowNodes(snapshot: any) {
  workflowNodeJson.value = [];
  pollingNodes.value = [];
  const content = snapshot?.content;
  if (!content) {
    return;
  }
  try {
    const workflowContent = JSON.parse(content);
    workflowNodeJson.value = sortNodes(workflowContent) || [];
    pollingNodes.value = Array.isArray(workflowContent?.nodes)
      ? workflowContent.nodes.map((node: any) => ({
          nodeId: node.id,
          nodeName: node?.data?.title || node.id,
        }))
      : [];
  } catch (error) {
    console.error('解析工作流插件快照失败', error);
  }
}

function buildErrorResult(error: any) {
  const responseData = error?.response?.data ?? {};
  return {
    error:
      responseData?.error ||
      responseData?.message ||
      error?.message ||
      '试运行失败',
  };
}

function buildUnavailableResult() {
  runResultResponse.value = {
    skipped: true,
    reasonMessage:
      pluginReasonMessage.value ||
      $t('pluginItem.pluginToolEdit.unavailableHint'),
  };
}

function handleSubmitRun() {
  if (!pluginAvailable.value) {
    buildUnavailableResult();
    return;
  }
  const runParams = runParamsRef.value?.handleSubmitParams?.();
  if (runParams === null || runParams === undefined) {
    return;
  }
  if (isWorkflowPlugin.value) {
    handleWorkflowSubmit(runParams);
    return;
  }
  handleHttpSubmit(runParams);
}

function handleHttpSubmit(runParams: any) {
  runLoading.value = true;
  api
    .post('/api/v1/pluginItem/test', {
      pluginToolId: props.pluginToolId,
      inputData: JSON.stringify(runParams),
    })
    .then((res) => {
      if (res.errorCode === 0) {
        runResultResponse.value = res.data;
        activeIndex.value = '2';
      }
    })
    .catch((error) => {
      runResultResponse.value = buildErrorResult(error);
    })
    .finally(() => {
      runLoading.value = false;
    });
}

function handleWorkflowSubmit(runParams: any) {
  if (!workflowId.value) {
    runResultResponse.value = {
      error: '当前插件未绑定有效工作流，无法试运行。',
    };
    return;
  }
  resetExecutionState();
  runLoading.value = true;
  api
    .post('/api/v1/pluginItem/testAsync', {
      pluginToolId: props.pluginToolId,
      inputData: JSON.stringify(runParams),
    })
    .then((res) => {
      if (res.errorCode === 0 && res.data) {
        executeId.value = String(res.data);
        pollingData.value = {
          executeId: executeId.value,
          status: 1,
          nodes: {},
        };
        runResultResponse.value = pollingData.value;
        activeIndex.value = '2';
        startPolling(executeId.value);
      }
    })
    .catch((error) => {
      runResultResponse.value = buildErrorResult(error);
    })
    .finally(() => {
      runLoading.value = false;
    });
}

function startPolling(nextExecuteId: string) {
  stopPolling();
  pollingActive = true;
  pollingGeneration += 1;
  schedulePolling(nextExecuteId, pollingGeneration);
}

function schedulePolling(nextExecuteId: string, generation: number) {
  pollingTimer.value = setTimeout(() => {
    pollingTimer.value = null;
    void executePolling(nextExecuteId, generation);
  }, POLLING_INTERVAL_MS);
}

function stopPolling() {
  pollingActive = false;
  pollingGeneration += 1;
  if (pollingTimer.value) {
    clearTimeout(pollingTimer.value);
    pollingTimer.value = null;
  }
}

async function executePolling(nextExecuteId: string, generation: number) {
  try {
    const res = await api.post('/api/v1/pluginItem/testChainStatus', {
      executeId: nextExecuteId,
      nodes: pollingNodes.value,
    });
    if (!pollingActive || generation !== pollingGeneration) return;
    if (res.errorCode !== 0) {
      stopPolling();
      return;
    }

    const nextData = {
      ...res.data,
      nodes: res.data?.nodes || {},
    };
    pollingData.value = nextData;
    runResultResponse.value = nextData;
    if (nextData.status !== 1) {
      stopPolling();
      return;
    }
    schedulePolling(nextExecuteId, generation);
  } catch (error) {
    if (!pollingActive || generation !== pollingGeneration) return;
    stopPolling();
    runResultResponse.value = buildErrorResult(error);
  }
}

function resumeChain(payload: any) {
  if (!executeId.value) {
    return;
  }
  api
    .post('/api/v1/pluginItem/testResume', {
      executeId: executeId.value,
      confirmParams: payload?.confirmParams || {},
    })
    .then((res) => {
      if (res.errorCode === 0) {
        startPolling(executeId.value);
      }
    })
    .catch((error) => {
      runResultResponse.value = buildErrorResult(error);
    });
}

function showWorkflowSteps() {
  return isWorkflowPlugin.value === true;
}

function showWorkflowStepList() {
  return showWorkflowSteps();
}

function showWorkflowResultAlert() {
  return (
    showWorkflowSteps() &&
    (!!runResultResponse.value?.error || !!runResultResponse.value?.skipped)
  );
}

function workflowResultAlertType() {
  return runResultResponse.value?.skipped ? 'warning' : 'error';
}

function workflowResultAlertMessage() {
  return (
    runResultResponse.value?.reasonMessage ||
    runResultResponse.value?.error ||
    ''
  );
}

function showWorkflowStepsEmpty() {
  return showWorkflowSteps() && workflowNodeJson.value.length === 0;
}

function showHttpResultEmpty() {
  return (
    showWorkflowSteps() !== true &&
    activeIndex.value === '2' &&
    !runResultResponse.value
  );
}

function handleSelect(index: string) {
  activeIndex.value = index;
}

function closeDialog() {
  stopPolling();
  dialogVisible.value = false;
}

onUnmounted(() => {
  stopPolling();
});
</script>

<template>
  <EasyFlowPanelModal
    v-model:open="dialogVisible"
    width="80%"
    align-center
    :title="$t('pluginItem.pluginToolEdit.trialRun')"
    :before-close="closeDialog"
  >
    <div :key="dialogContentKey" class="run-test-container">
      <div class="run-test-params">
        <div class="run-title-style">
          {{ runTitle }}
        </div>
        <ElAlert
          v-if="!pluginAvailable"
          class="mb-4"
          type="warning"
          :closable="false"
          show-icon
          :title="$t('pluginItem.pluginToolEdit.unavailableHint')"
          :description="pluginReasonMessage"
        />
        <div>
          <PluginRunParams
            ref="runParamsRef"
            v-model="inputDataParams"
            :editable="pluginAvailable"
            :is-edit-output="true"
            :payload-mode="isWorkflowPlugin ? 'workflow' : 'plugin'"
          />
        </div>
      </div>
      <div class="run-test-result">
        <div class="run-title-style">
          {{ showWorkflowSteps() ? $t('aiWorkflow.steps') : runResult }}
        </div>
        <div v-if="!showWorkflowSteps()">
          <ElMenu
            :default-active="activeIndex"
            class="el-menu-demo"
            mode="horizontal"
            :ellipsis="false"
            @select="handleSelect"
          >
            <ElMenuItem index="1">Request</ElMenuItem>
            <ElMenuItem index="2">Response</ElMenuItem>
          </ElMenu>
        </div>
        <div class="run-res-json">
          <template v-if="showWorkflowSteps()">
            <ElAlert
              v-if="showWorkflowResultAlert()"
              class="run-result-alert"
              :type="workflowResultAlertType()"
              :closable="false"
              show-icon
              :title="workflowResultAlertMessage()"
            />
            <WorkflowSteps
              v-if="showWorkflowStepList()"
              :workflow-id="workflowId"
              :node-json="workflowNodeJson"
              :init-signal="initSignal"
              :polling-data="pollingData"
              @resume="resumeChain"
            />
            <div v-if="showWorkflowStepsEmpty()" class="run-result-placeholder">
              {{ $t('pluginItem.pluginToolEdit.runWorkflowStepsEmpty') }}
            </div>
          </template>
          <template v-else>
            <JsonViewer
              v-if="activeIndex === '1'"
              :value="inputDataParams || {}"
              copyable
              :expand-depth="Infinity"
              :theme="themeMode"
            />
            <JsonViewer
              v-if="activeIndex === '2' && runResultResponse"
              :value="runResultResponse || {}"
              copyable
              :expand-depth="Infinity"
              :theme="themeMode"
            />
            <div v-if="showHttpResultEmpty()" class="run-result-placeholder">
              {{ $t('common.noDataAvailable') }}
            </div>
          </template>
        </div>
      </div>
    </div>
    <template #footer>
      <ElButton @click="closeDialog">
        {{ $t('button.cancel') }}
      </ElButton>
      <ElButton
        type="primary"
        :icon="VideoPlay"
        :loading="runLoading"
        @click="handleSubmitRun"
      >
        {{ $t('pluginItem.pluginToolEdit.run') }}
      </ElButton>
    </template>
  </EasyFlowPanelModal>
</template>

<style scoped>
.run-test-container {
  display: flex;
  gap: 16px;
  width: 100%;
  height: calc(100vh - 161px);
}

.run-test-params {
  flex: 1;
  width: 100%;
  overflow: auto;
}

.run-test-result {
  display: flex;
  flex: 1;
  flex-direction: column;
}

.run-res-json {
  flex: 1;
  width: 100%;
  overflow: auto;
}

.run-result-alert {
  margin-bottom: 16px;
}

.run-result-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 240px;
  padding: 24px;
  color: var(--el-text-color-secondary);
  font-size: 14px;
  text-align: center;
  border: 1px dashed var(--el-border-color);
  border-radius: 10px;
  background: var(--el-fill-color-extra-light);
}

.run-title-style {
  margin-bottom: 8px;
  font-size: 16px;
  font-weight: bold;
}

:deep(.el-table td.el-table__cell.first-column div) {
  display: flex;
  gap: 2px;
  align-items: center;
}
</style>
