<template>
	<el-scrollbar class="w-full box-border p-12px">
		<div class="webhook-setter">
			<section class="section-block">
				<div class="text-14px font-bold text-gray-700">WEBHOOK URL</div>
				<div class="url-row">
					<el-select v-model="formData.method" class="method-select">
						<el-option
							v-for="item in methodOptions"
							:key="item.value"
							:label="item.label"
							:value="item.value"
						/>
					</el-select>

					<el-input :model-value="formData.webhook_url" readonly class="url-input">
						<template #append>
							<IconButton
								link
								icon="lucide:copy"
								@click="copyText(formData.webhook_url, 'Webhook URL')"
							/>
						</template>
					</el-input>
				</div>

				<div class="debug-tip">
					<div class="debug-tip__label">{{ texts.webhookDebugHint }}</div>
					<div
						class="debug-tip__value cursor-pointer"
						@click="copyText(formData.webhook_debug_url, texts.testUrl)"
					>
						{{ formData.webhook_debug_url || '--' }}
					</div>
				</div>
			</section>

			<section class="section-block">
				<div class="field-title">{{ texts.contentType }}</div>
				<el-select v-model="formData.content_type" class="w-full">
					<el-option
						v-for="item in contentTypeOptions"
						:key="item.value"
						:label="item.label"
						:value="item.value"
					/>
				</el-select>
			</section>

			<section class="section-block">
				<div class="text-14px font-bold text-gray-700">{{ texts.queryParams }}</div>
				<div class="table-shell">
					<el-table :data="queryRows" border row-class-name="param-table">
						<el-table-column :label="texts.variableName">
							<template #default="{ row, $index }">
								<el-input
									v-model="row.name"
									:placeholder="texts.variablePlaceholder"
									@focus="ensureExtraQueryRow($index)"
								/>
							</template>
						</el-table-column>
						<el-table-column :label="texts.type" width="100">
							<template #default="{ row }">
								<el-select v-model="row.type" class="w-full">
									<el-option
										v-for="option in scalarTypeOptions"
										:key="option.value"
										:label="option.label"
										:value="option.value"
									/>
								</el-select>
							</template>
						</el-table-column>
						<el-table-column :label="texts.required" width="88" align="center">
							<template #default="{ row, $index }">
								<div class="flex justify-between">
									<el-checkbox v-model="row.required" />
									<IconButton
										v-if="queryRows.length > 1"
										link
										icon="lucide:trash-2"
										class="delete-button"
										@click="removeQueryRow($index)"
									/>
								</div>
							</template>
						</el-table-column>
					</el-table>
				</div>
			</section>

			<section class="section-block">
				<div class="text-14px font-bold text-gray-700">{{ texts.headerParams }}</div>
				<div class="table-shell">
					<el-table :data="headerRows" border row-class-name="param-table">
						<el-table-column :label="texts.variableName">
							<template #default="{ row, $index }">
								<el-input
									v-model="row.name"
									:placeholder="texts.variablePlaceholder"
									@focus="ensureExtraHeaderRow($index)"
								/>
							</template>
						</el-table-column>
						<el-table-column :label="texts.required" width="88" align="center">
							<template #default="{ row, $index }">
								<div class="flex justify-between">
									<el-checkbox v-model="row.required" />
									<IconButton
										v-if="headerRows.length > 1"
										link
										icon="lucide:trash-2"
										class="delete-button"
										@click="removeHeaderRow($index)"
									/>
								</div>
							</template>
						</el-table-column>
					</el-table>
				</div>
			</section>

			<section class="section-block">
				<div class="text-14px font-bold text-gray-700">{{ texts.bodyParams }}</div>
				<div class="table-shell">
					<el-table :data="bodyRows" border row-class-name="param-table">
						<el-table-column :label="texts.variableName">
							<template #default="{ row, $index }">
								<el-input
									v-model="row.name"
									:placeholder="texts.variablePlaceholder"
									@focus="ensureExtraBodyRow($index)"
								/>
							</template>
						</el-table-column>
						<el-table-column :label="texts.type" width="100">
							<template #default="{ row }">
								<el-select v-model="row.type" class="w-full">
									<el-option
										v-for="option in bodyTypeOptions"
										:key="option.value"
										:label="option.label"
										:value="option.value"
									/>
								</el-select>
							</template>
						</el-table-column>
						<el-table-column :label="texts.required" width="88" align="center">
							<template #default="{ row, $index }">
								<div class="flex justify-between">
									<el-checkbox v-model="row.required" />
									<IconButton
										v-if="bodyRows.length > 1"
										link
										icon="lucide:trash-2"
										class="delete-button"
										@click="removeBodyRow($index)"
									/>
								</div>
							</template>
						</el-table-column>
					</el-table>
				</div>
			</section>

			<section class="section-block section-block--response">
				<div class="field-title">{{ texts.response }}</div>

				<div class="response-grid">
					<div class="text-14px">{{ texts.statusCode }}</div>
					<el-input-number
						v-model="formData.response_status_code"
						:min="100"
						:max="599"
						:step="1"
						class="status-input"
					/>
				</div>

				<div class="field-stack">
					<div class="text-14px">{{ texts.responseBody }}</div>
					<CodeEditor
						v-model="formData.response_body"
						:tools="false"
						:copy-code="false"
						:allow-change-language="false"
						:language="responseEditorLanguage"
						:height="180"
						theme="vs-light"
					/>
				</div>
			</section>

			<section class="section-block mb-10">
				<button type="button" class="output-toggle" @click="outputsExpanded = !outputsExpanded">
					<span>{{ texts.outputVariables }}</span>
					<Icon :icon="outputsExpanded ? 'lucide:chevron-down' : 'lucide:chevron-right'" />
				</button>

				<div v-if="outputsExpanded" class="output-list">
					<div v-for="output in formData.outputs" :key="output.name" class="output-item">
						<span class="output-item__name">{{ output.name }}</span>
						<span class="output-item__type">{{ output.type }}</span>
					</div>
				</div>
			</section>
			<NodeRuntimeConfig v-model="formData" />
		</div>
	</el-scrollbar>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { cloneDeep, isEqual } from 'lodash-es'
import { ElMessage } from 'element-plus'
import { Icon, IconButton } from '@repo/ui'
import NodeRuntimeConfig from '@/nodes/_base/NodeRuntimeConfig.vue'

import { VARIABLE_TYPE_OPTIONS } from '@/constant'
import { useSetterModel } from '../_shared/useSetterModel'
import { useI18n } from '@/composables/useI18n'

import type { NodeVariableType } from '../../Interface'
import type {
	WebhookBodyParam,
	WebhookContentType,
	WebhookData,
	WebhookHeader,
	WebhookMethod,
	WebhookParam
} from './index'

interface Emits {
	(e: 'update', value: WebhookData): void
}

const props = defineProps<{
	data: WebhookData
}>()

const emit = defineEmits<Emits>()
const { t, locale } = useI18n()

const texts = computed(() => ({
	webhookDebugHint: t('pages.webhookSetter.webhookDebugHint'),
	testUrl: t('pages.webhookSetter.testUrl'),
	contentType: t('pages.webhookSetter.contentType'),
	queryParams: t('pages.webhookSetter.queryParams'),
	headerParams: t('pages.webhookSetter.headerParams'),
	bodyParams: t('pages.webhookSetter.bodyParams'),
	variableName: t('pages.webhookSetter.variableName'),
	variablePlaceholder: t('pages.webhookSetter.variablePlaceholder'),
	type: t('pages.webhookSetter.type'),
	required: t('pages.webhookSetter.required'),
	response: t('pages.webhookSetter.response'),
	statusCode: t('pages.webhookSetter.statusCode'),
	responseBody: t('pages.webhookSetter.responseBody'),
	outputVariables: t('pages.webhookSetter.outputVariables'),
	rawRequestBody: t('pages.webhookSetter.rawRequestBody'),
	defaultTitle: t('pages.webhookSetter.defaultTitle'),
	emptySuffix: t('pages.webhookSetter.emptySuffix'),
	copiedSuffix: t('pages.webhookSetter.copiedSuffix'),
	copyFailedSuffix: t('pages.webhookSetter.copyFailedSuffix')
}))

type QueryRow = WebhookParam
type HeaderRow = WebhookHeader
type BodyRow = WebhookBodyParam

const methodOptions: Array<{ label: string; value: WebhookMethod }> = [
	{ label: 'GET', value: 'get' },
	{ label: 'POST', value: 'post' },
	{ label: 'PUT', value: 'put' },
	{ label: 'DELETE', value: 'delete' },
	{ label: 'PATCH', value: 'patch' },
	{ label: 'HEAD', value: 'head' }
]

const contentTypeOptions: Array<{ label: string; value: WebhookContentType }> = [
	{ label: 'application/json', value: 'application/json' },
	{
		label: 'application/x-www-form-urlencoded',
		value: 'application/x-www-form-urlencoded'
	},
	{ label: 'text/plain', value: 'text/plain' },
	{ label: 'application/octet-stream', value: 'application/octet-stream' },
	{ label: 'multipart/form-data', value: 'multipart/form-data' }
]

const scalarTypeOptions = VARIABLE_TYPE_OPTIONS.filter((item) =>
	['string', 'number', 'boolean'].includes(item.value)
) as Array<{ label: string; value: QueryRow['type'] }>

const bodyTypeOptions = VARIABLE_TYPE_OPTIONS.filter((item) =>
	[
		'string',
		'number',
		'boolean',
		'object',
		'array[string]',
		'array[number]',
		'array[boolean]',
		'array[object]'
	].includes(item.value)
) as Array<{ label: string; value: BodyRow['type'] }>

const formData = useSetterModel<WebhookData>(props, emit)
const outputsExpanded = ref(true)
const queryRows = ref<QueryRow[]>([])
const headerRows = ref<HeaderRow[]>([])
const bodyRows = ref<BodyRow[]>([])

const createEmptyQueryRow = (): QueryRow => ({
	name: '',
	type: 'string',
	required: false
})

const createEmptyHeaderRow = (): HeaderRow => ({
	name: '',
	required: false
})

const createEmptyBodyRow = (): BodyRow => ({
	name: '',
	type: 'string',
	required: false
})

const defaultOutputs = () => [
	{
		name: 'payload._webhook_raw',
		describe: texts.value.rawRequestBody,
		type: 'object' as NodeVariableType
	}
]

const normalizeWebhookState = () => {
	if (!formData.value.title) {
		formData.value.title = texts.value.defaultTitle
	}
	if (!formData.value.type) {
		formData.value.type = 'trigger-webhook'
	}
	if (!formData.value.variables) {
		formData.value.variables = []
	}
	if (!formData.value.retry_config) {
		formData.value.retry_config = {
			max_retries: 3,
			retry_enabled: false,
			retry_interval: 100
		}
	}
	if (!formData.value.error_strategy) {
		formData.value.error_strategy = 'none'
	}
	if (!formData.value.fail_branch_node_id) {
		formData.value.fail_branch_node_id = ''
	}
	if (!formData.value.default_value) {
		formData.value.default_value = []
	}
	if (typeof formData.value.output_can_alter !== 'boolean') {
		formData.value.output_can_alter = false
	}
	if (!Array.isArray(formData.value.outputs) || !formData.value.outputs.length) {
		formData.value.outputs = defaultOutputs()
	}
	if (typeof formData.value.async_mode !== 'boolean') {
		formData.value.async_mode = false
	}
	if (!formData.value.method) {
		formData.value.method = 'post'
	}
	if (!formData.value.content_type) {
		formData.value.content_type = 'application/json'
	}
	if (!formData.value.response_content_type) {
		formData.value.response_content_type = 'application/json'
	}
	if (!Array.isArray(formData.value.headers)) {
		formData.value.headers = []
	}
	if (!Array.isArray(formData.value.params)) {
		formData.value.params = []
	}
	if (!Array.isArray(formData.value.body)) {
		formData.value.body = []
	}
	if (typeof formData.value.response_body !== 'string') {
		formData.value.response_body = '{\n  "success": true\n}'
	}

	const statusCode = Number(formData.value.response_status_code)
	formData.value.response_status_code = Number.isFinite(statusCode)
		? Math.min(599, Math.max(100, Math.trunc(statusCode)))
		: 200

	formData.value.webhook_url = `${formData.value.webhook_url || ''}`.trim()
	formData.value.webhook_debug_url = `${formData.value.webhook_debug_url || ''}`.trim()
}

normalizeWebhookState()

watch(
	() => props.data,
	() => {
		normalizeWebhookState()
		syncEditorRowsFromFormData()
	},
	{ deep: true }
)

watch(
	texts,
	() => {
		if (!formData.value.outputs?.length) {
			formData.value.outputs = defaultOutputs()
		} else if (formData.value.outputs[0]?.name === 'payload._webhook_raw') {
			formData.value.outputs[0].describe = texts.value.rawRequestBody
		}
		if (
			!formData.value.title ||
			formData.value.title === 'Webhook 触发' ||
			formData.value.title === 'Webhook Trigger'
		) {
			formData.value.title = texts.value.defaultTitle
		}
	},
	{ immediate: true }
)

const syncEditorRowsFromFormData = () => {
	const nextQueryRows = formData.value.params.length
		? cloneDeep(formData.value.params)
		: [createEmptyQueryRow()]
	if (!isEqual(nextQueryRows, queryRows.value)) {
		queryRows.value = nextQueryRows
	}

	const nextHeaderRows = formData.value.headers.length
		? cloneDeep(formData.value.headers)
		: [createEmptyHeaderRow()]
	if (!isEqual(nextHeaderRows, headerRows.value)) {
		headerRows.value = nextHeaderRows
	}

	const nextBodyRows = formData.value.body.length
		? cloneDeep(formData.value.body)
		: [createEmptyBodyRow()]
	if (!isEqual(nextBodyRows, bodyRows.value)) {
		bodyRows.value = nextBodyRows
	}
}

watch(
	() => [formData.value.params, formData.value.headers, formData.value.body],
	() => {
		syncEditorRowsFromFormData()
	},
	{
		deep: true,
		immediate: true
	}
)

watch(
	() => formData.value.content_type,
	(value) => {
		formData.value.response_content_type = value
	},
	{ immediate: true }
)

watch(
	queryRows,
	(value) => {
		const normalized = value
			.map((item) => ({
				name: item.name.trim(),
				type: item.type,
				required: Boolean(item.required)
			}))
			.filter((item) => item.name)

		if (!isEqual(normalized, formData.value.params)) {
			formData.value.params = normalized
		}
	},
	{ deep: true }
)

watch(
	headerRows,
	(value) => {
		const normalized = value
			.map((item) => ({
				name: item.name.trim(),
				required: Boolean(item.required)
			}))
			.filter((item) => item.name)

		if (!isEqual(normalized, formData.value.headers)) {
			formData.value.headers = normalized
		}
	},
	{ deep: true }
)

watch(
	bodyRows,
	(value) => {
		const normalized = value
			.map((item) => ({
				name: item.name.trim(),
				type: item.type,
				required: Boolean(item.required)
			}))
			.filter((item) => item.name)

		if (!isEqual(normalized, formData.value.body)) {
			formData.value.body = normalized
		}
	},
	{ deep: true }
)

const responseEditorLanguage = computed(() =>
	formData.value.response_content_type === 'application/json' ? 'json' : 'javascript'
)

const ensureExtraQueryRow = (index: number) => {
	if (index === queryRows.value.length - 1) {
		queryRows.value.push(createEmptyQueryRow())
	}
}

const ensureExtraHeaderRow = (index: number) => {
	if (index === headerRows.value.length - 1) {
		headerRows.value.push(createEmptyHeaderRow())
	}
}

const ensureExtraBodyRow = (index: number) => {
	if (index === bodyRows.value.length - 1) {
		bodyRows.value.push(createEmptyBodyRow())
	}
}

const removeQueryRow = (index: number) => {
	queryRows.value.splice(index, 1)
	if (!queryRows.value.length) {
		queryRows.value.push(createEmptyQueryRow())
	}
}

const removeHeaderRow = (index: number) => {
	headerRows.value.splice(index, 1)
	if (!headerRows.value.length) {
		headerRows.value.push(createEmptyHeaderRow())
	}
}

const removeBodyRow = (index: number) => {
	bodyRows.value.splice(index, 1)
	if (!bodyRows.value.length) {
		bodyRows.value.push(createEmptyBodyRow())
	}
}

const copyText = async (value: string, label: string) => {
	const text = `${value || ''}`.trim()
	if (!text) {
		ElMessage.warning(
			locale.value === 'zh-cn'
				? `${label}${texts.value.emptySuffix}`
				: `${label} ${texts.value.emptySuffix}`
		)
		return
	}

	try {
		await navigator.clipboard.writeText(text)
		ElMessage.success(
			locale.value === 'zh-cn'
				? `${label}${texts.value.copiedSuffix}`
				: `${label} ${texts.value.copiedSuffix}`
		)
	} catch (error) {
		console.error(`copy ${label} failed`, error)
		ElMessage.error(
			locale.value === 'zh-cn'
				? `${label}${texts.value.copyFailedSuffix}`
				: `${label} ${texts.value.copyFailedSuffix}`
		)
	}
}
</script>

<style scoped lang="less">
.webhook-setter {
	display: flex;
	flex-direction: column;
	gap: 20px;
}

.section-block {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.section-block--response {
	padding-top: 4px;
	border-top: 1px solid #eaecf0;
}

.field-title {
	font-size: 14px;
	font-weight: 700;
	color: #344054;
}

.field-stack {
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.url-row {
	display: grid;
	grid-template-columns: 108px minmax(0, 1fr);
	gap: 12px;
}

.method-select,
.url-input {
	width: 100%;
}

.debug-tip {
	padding-left: 12px;
	border-left: 3px solid #eaecf0;
}

.debug-tip__label {
	font-size: 12px;
	color: #667085;
}

.debug-tip__value {
	margin-top: 2px;
	font-size: 14px;
	color: #344054;
	word-break: break-all;
}

.table-shell {
	border-radius: 8px;
	overflow: hidden;
	border: 1px solid #dfe4ea;
	background: #fff;
}

.delete-button {
	color: #f04438;
}

.response-grid {
	display: grid;
	grid-template-columns: minmax(0, 1fr) 180px;
	align-items: center;
	gap: 16px;
}

.status-input {
	width: 100%;
}

.output-toggle {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 0;
	border: 0;
	background: transparent;
	font-size: 14px;
	font-weight: 700;
	color: #344054;
	cursor: pointer;
}

.output-list {
	display: flex;
	flex-direction: column;
	gap: 10px;
}

.output-item {
	display: flex;
	align-items: center;
	gap: 10px;
	font-size: 14px;
	color: #475467;
	word-break: break-all;
}

.output-item__name {
	font-weight: 600;
	color: #344054;
}

.output-item__type {
	color: #667085;
}

:deep(.param-table) {
	--el-table-border-color: #eef2f6;
	--el-table-header-bg-color: #fff;
	--el-table-tr-bg-color: #fff;
	--el-table-row-hover-bg-color: #f8fbff;
}

:deep(.param-table th.el-table__cell) {
	background: #fff;
	color: #667085;
	font-size: 12px;
	font-weight: 600;
}

:deep(.param-table .cell) {
	padding: 0 8px;
}

:deep(.param-table .el-table__inner-wrapper::before) {
	display: none;
}

:deep(.param-table .el-input__wrapper),
:deep(.param-table .el-select__wrapper) {
	box-shadow: none;
}
</style>
