<script lang="ts">
	import {
		DraftService,
		type NewScript,
		ScriptService,
		type NewScriptWithDraft,
		ScheduleService,
		type Script,
		type TriggersCount,
		PostgresTriggerService
	} from '$lib/gen'
	import { inferArgs } from '$lib/infer'
	import { initialCode } from '$lib/script_helpers'
	import { page } from '$app/stores'
	import { defaultScripts, enterpriseLicense, userStore, workspaceStore } from '$lib/stores'
	import {
		cleanValueProperties,
		emptySchema,
		emptyString,
		encodeState,
		formatCron,
		orderedJsonStringify,
		replaceFalseWithUndefined,
		type Value
	} from '$lib/utils'
	import Path from './Path.svelte'
	import ScriptEditor from './ScriptEditor.svelte'
	import { Alert, Badge, Button, Drawer, SecondsInput, Tab, TabContent, Tabs } from './common'
	import LanguageIcon from './common/languageIcons/LanguageIcon.svelte'
	import type { SupportedLanguage } from '$lib/common'
	import Tooltip from './Tooltip.svelte'
	import DrawerContent from './common/drawer/DrawerContent.svelte'
	import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
	import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
	import ErrorHandlerToggleButton from '$lib/components/details/ErrorHandlerToggleButton.svelte'
	import {
		Bug,
		Calendar,
		CheckCircle,
		Code,
		CornerDownLeft,
		DiffIcon,
		Pen,
		Plus,
		Rocket,
		Save,
		Settings,
		Shuffle,
		X
	} from 'lucide-svelte'
	import { sendUserToast } from '$lib/toast'
	import { isCloudHosted } from '$lib/cloud'
	import Awareness from './Awareness.svelte'
	import { fade } from 'svelte/transition'
	import Popover from './Popover.svelte'
	import Toggle from './Toggle.svelte'
	import ScriptSchema from './ScriptSchema.svelte'
	import Section from './Section.svelte'
	import Label from './Label.svelte'
	import type DiffDrawer from './DiffDrawer.svelte'
	import type Editor from './Editor.svelte'
	import WorkerTagPicker from './WorkerTagPicker.svelte'
	import MetadataGen from './copilot/MetadataGen.svelte'
	import { writable } from 'svelte/store'
	import { defaultScriptLanguages, processLangs } from '$lib/scripts'
	import DefaultScripts from './DefaultScripts.svelte'
	import { createEventDispatcher, setContext } from 'svelte'
	import CustomPopover from './CustomPopover.svelte'
	import Summary from './Summary.svelte'
	import type { ScriptBuilderWhitelabelCustomUi } from './custom_ui'
	import DeployOverrideConfirmationModal from '$lib/components/common/confirmationModal/DeployOverrideConfirmationModal.svelte'
	import TriggersEditor from './triggers/TriggersEditor.svelte'
	import type { ScheduleTrigger, TriggerContext, TriggerKind } from './triggers'
	import {
		BUN_PREPROCESSOR_MODULE_CODE,
		PYTHON_PREPROCESSOR_MODULE_CODE
	} from '$lib/script_helpers'
	import CaptureTable from './triggers/CaptureTable.svelte'
	import type { SavedAndModifiedValue } from './common/confirmationModal/unsavedTypes'

	export let script: NewScript
	export let fullyLoaded: boolean = true
	export let initialPath: string = ''
	export let template: 'docker' | 'bunnative' | 'script' = 'script'
	export let initialArgs: Record<string, any> = {}
	export let lockedLanguage = false
	export let showMeta: boolean = false
	export let neverShowMeta: boolean = false
	export let diffDrawer: DiffDrawer | undefined = undefined
	export let savedScript: NewScriptWithDraft | undefined = undefined
	export let searchParams: URLSearchParams = new URLSearchParams()
	export let disableHistoryChange = false
	export let replaceStateFn: (url: string) => void = (url) =>
		window.history.replaceState(null, '', url)
	export let customUi: ScriptBuilderWhitelabelCustomUi = {}
	export let savedPrimarySchedule: ScheduleTrigger | undefined = undefined

	export function getInitialAndModifiedValues(): SavedAndModifiedValue {
		return {
			savedValue: savedScript,
			modifiedValue: script
		}
	}

	let deployedValue: Value | undefined = undefined // Value to diff against
	let deployedBy: string | undefined = undefined // Author
	let confirmCallback: () => void = () => {} // What happens when user clicks `override` in warning
	let open: boolean = false // Is confirmation modal open
	let args: Record<string, any> = initialArgs // Test args input
	let selectedInputTab: 'main' | 'preprocessor' = 'main'
	let hasPreprocessor = false

	let metadataOpen =
		!neverShowMeta &&
		(showMeta ||
			(initialPath == '' &&
				searchParams.get('state') == undefined &&
				searchParams.get('collab') == undefined))

	let editor: Editor | undefined = undefined
	let scriptEditor: ScriptEditor | undefined = undefined
	let captureTable: CaptureTable | undefined = undefined

	const primaryScheduleStore = writable<ScheduleTrigger | undefined | false>(savedPrimarySchedule)
	const triggersCount = writable<TriggersCount | undefined>(
		savedPrimarySchedule
			? { schedule_count: 1, primary_schedule: { schedule: savedPrimarySchedule.cron } }
			: undefined
	)
	const simplifiedPoll = writable(false)
	const selectedTriggerStore = writable<TriggerKind>('webhooks')

	export function setPrimarySchedule(schedule: ScheduleTrigger | undefined | false) {
		primaryScheduleStore.set(schedule)
		loadTriggers()
	}

	const dispatch = createEventDispatcher()

	$: initialPath != '' && loadTriggers()

	async function loadTriggers() {
		$triggersCount = await ScriptService.getTriggersCountOfScript({
			workspace: $workspaceStore!,
			path: initialPath
		})
		if ($primaryScheduleStore && $triggersCount.primary_schedule == undefined) {
			$triggersCount = {
				...($triggersCount ?? {}),
				schedule_count: ($triggersCount.schedule_count ?? 0) + 1,
				primary_schedule: {
					schedule: $primaryScheduleStore.cron
				}
			}
		}
	}

	const triggerDefaultValuesStore = writable<Record<string, any> | undefined>(undefined)

	const captureOn = writable<boolean | undefined>(undefined)
	const showCaptureHint = writable<boolean | undefined>(undefined)
	setContext<TriggerContext>('TriggerContext', {
		selectedTrigger: selectedTriggerStore,
		primarySchedule: primaryScheduleStore,
		triggersCount,
		simplifiedPoll,
		defaultValues: triggerDefaultValuesStore,
		captureOn: captureOn,
		showCaptureHint: showCaptureHint
	})

	const enterpriseLangs = ['bigquery', 'snowflake', 'mssql', 'oracledb']

	export function setCode(code: string): void {
		editor?.setCode(code)
	}

	$: langs = processLangs(
		script.language,
		$defaultScripts?.order ?? Object.keys(defaultScriptLanguages)
	)
		.map((l) => [defaultScriptLanguages[l], l])
		.filter(
			(x) => $defaultScripts?.hidden == undefined || !$defaultScripts.hidden.includes(x[1])
		) as [string, SupportedLanguage | 'docker' | 'bunnative'][]

	const scriptKindOptions: {
		value: Script['kind']
		title: string
		Icon: any
		desc?: string
		documentationLink?: string
	}[] = [
		{
			value: 'script',
			title: 'Action',
			Icon: Code
		},
		{
			value: 'trigger',
			title: 'Trigger',
			desc: 'First module of flows to trigger them based on external changes. These kind of scripts are usually running on a schedule to periodically look for changes.',
			documentationLink: 'https://www.windmill.dev/docs/flows/flow_trigger',
			Icon: Rocket
		},
		{
			value: 'approval',
			title: 'Approval',
			desc: 'Send notifications externally to ask for approval to continue a flow.',
			documentationLink: 'https://www.windmill.dev/docs/flows/flow_approval',
			Icon: CheckCircle
		},
		{
			value: 'failure',
			title: 'Error Handler',
			desc: 'Handle errors in flows after all retry attempts have been exhausted.',
			documentationLink: 'https://www.windmill.dev/docs/flows/flow_error_handler',
			Icon: Bug
		},
		{
			value: 'preprocessor',
			title: 'Preprocessor',
			desc: 'Transform incoming requests before they are passed to the flow.',
			documentationLink: 'https://www.windmill.dev/docs/core_concepts/preprocessors',
			Icon: Shuffle
		}
	]

	let pathError = ''
	let loadingSave = false
	let loadingDraft = false

	$: {
		;['collab', 'path'].forEach((x) => {
			if (searchParams.get(x)) {
				searchParams.delete(x)
			}
		})
	}

	$: !disableHistoryChange &&
		replaceStateFn('#' + encodeState({ ...script, primarySchedule: $primaryScheduleStore }))
	if (script.content == '') {
		initContent(script.language, script.kind, template)
	}

	async function isTemplateScript() {
		let getInitBlockTemplate = $page.url.searchParams.get('id')
		if (getInitBlockTemplate === null) {
			return undefined
		}
		try {
			getInitBlockTemplate = await PostgresTriggerService.getTemplateScript({
				workspace: $workspaceStore!,
				id: getInitBlockTemplate as string
			})
			return getInitBlockTemplate
		} catch (error) {
			sendUserToast(
				'An error occured when trying to load your template script, please try again later',
				true
			)
		}
	}

	async function initContent(
		language: SupportedLanguage,
		kind: Script['kind'] | undefined,
		template: 'pgsql' | 'mysql' | 'script' | 'docker' | 'powershell' | 'bunnative'
	) {
		scriptEditor?.disableCollaboration()
		const templateScript = await isTemplateScript()
		script.content = initialCode(language, kind, template, templateScript != undefined)
		if (templateScript) {
			script.content += '\r\n' + templateScript
		}
		scriptEditor?.inferSchema(script.content, language, true)
		if (script.content != editor?.getCode()) {
			setCode(script.content)
		}
	}

	async function createSchedule(path: string) {
		if (!$primaryScheduleStore) {
			return
		}
		const { cron, timezone, args, enabled, summary } = $primaryScheduleStore

		try {
			await ScheduleService.createSchedule({
				workspace: $workspaceStore!,
				requestBody: {
					path: path,
					schedule: formatCron(cron),
					timezone,
					script_path: path,
					is_flow: false,
					args,
					enabled,
					summary
				}
			})
		} catch (err) {
			sendUserToast(`The primary schedule could not be created: ${err}`, true)
		}
	}

	async function handleEditScript(stay: boolean, deployMsg?: string): Promise<void> {
		// Fetch latest version and fetch entire script after if needed
		let actual_parent_hash: string | undefined = undefined

		try {
			if (initialPath && initialPath != '') {
				actual_parent_hash = (
					await ScriptService.getScriptLatestVersion({
						workspace: $workspaceStore!,
						path: initialPath
					})
				)?.script_hash
			}
		} catch (error) {
			//
		}

		// Usually when we create new script, we put current hash as a parent_hash
		// But if we specify parent_hash that is already used, than we get error
		// In order to fix it we make sure that client's understanding of parent_hash
		// is aligns with understanding of backend.
		if (actual_parent_hash == undefined || script.parent_hash == actual_parent_hash) {
			// Handle directly
			await editScript(stay, script.parent_hash!, deployMsg)
		} else {
			// Fetch entire script, since we need it to show Diff
			await syncWithDeployed()

			if (
				deployedValue &&
				script &&
				orderedJsonStringify({ ...deployedValue, hash: undefined }) ===
					orderedJsonStringify(
						replaceFalseWithUndefined({ ...script, hash: undefined, parent_hash: undefined })
					)
			) {
				await editScript(stay, actual_parent_hash, deployMsg)
			} else {
				// Handle through confirmation modal
				confirmCallback = async () => {
					open = false
					if (actual_parent_hash) {
						await editScript(stay, actual_parent_hash, deployMsg)
					} else {
						sendUserToast('Could not fetch latest version of the script', true)
					}
				}
				// Open confirmation modal
				open = true
			}
		}
	}

	async function syncWithDeployed() {
		const latestScript = await ScriptService.getScriptByPath({
			workspace: $workspaceStore!,
			path: initialPath,
			withStarredInfo: true
		})

		deployedValue = replaceFalseWithUndefined({
			...latestScript,
			workspace_id: undefined,
			created_at: undefined,
			created_by: undefined,
			extra_perms: undefined,
			lock: undefined,
			lock_error_logs: undefined,
			parent_hashes: undefined
		})

		deployedBy = latestScript.created_by
	}

	async function editScript(
		stay: boolean,
		parentHash: string,
		deploymentMsg?: string
	): Promise<void> {
		loadingSave = true
		try {
			try {
				localStorage.removeItem(script.path)
			} catch (e) {
				console.error('error interacting with local storage', e)
			}
			script.schema = script.schema ?? emptySchema()
			try {
				const result = await inferArgs(script.language, script.content, script.schema as any)
				script.no_main_func = result?.no_main_func || undefined
				script.has_preprocessor = result?.has_preprocessor || undefined
			} catch (error) {
				sendUserToast(`Could not parse code, are you sure it is valid?`, true)
			}

			const newHash = await ScriptService.createScript({
				workspace: $workspaceStore!,
				requestBody: {
					path: script.path,
					summary: script.summary,
					description: script.description ?? '',
					content: script.content,
					parent_hash: parentHash,
					schema: script.schema,
					is_template: script.is_template,
					language: script.language,
					kind: script.kind,
					tag: script.tag,
					envs: script.envs,
					dedicated_worker: script.dedicated_worker,
					concurrent_limit: script.concurrent_limit,
					concurrency_time_window_s: script.concurrency_time_window_s,
					cache_ttl: script.cache_ttl,
					ws_error_handler_muted: script.ws_error_handler_muted,
					priority: script.priority,
					restart_unless_cancelled: script.restart_unless_cancelled,
					delete_after_use: script.delete_after_use,
					timeout: script.timeout,
					concurrency_key: emptyString(script.concurrency_key) ? undefined : script.concurrency_key,
					visible_to_runner_only: script.visible_to_runner_only,
					no_main_func: script.no_main_func,
					has_preprocessor: script.has_preprocessor,
					deployment_message: deploymentMsg || undefined,
					on_behalf_of_email: script.on_behalf_of_email
				}
			})

			const scheduleExists =
				initialPath != '' &&
				(await ScheduleService.existsSchedule({
					workspace: $workspaceStore ?? '',
					path: script.path
				}))
			if ($primaryScheduleStore) {
				const { enabled, timezone, args, cron, summary } = $primaryScheduleStore

				if (scheduleExists) {
					const schedule = await ScheduleService.getSchedule({
						workspace: $workspaceStore ?? '',
						path: script.path
					})
					if (
						JSON.stringify(schedule.args) != JSON.stringify(args) ||
						schedule.schedule != cron ||
						schedule.timezone != timezone ||
						schedule.summary != summary
					) {
						await ScheduleService.updateSchedule({
							workspace: $workspaceStore ?? '',
							path: script.path,
							requestBody: {
								schedule: formatCron(cron),
								timezone,
								args,
								summary
							}
						})
					}
					if (enabled != schedule.enabled) {
						await ScheduleService.setScheduleEnabled({
							workspace: $workspaceStore ?? '',
							path: script.path,
							requestBody: { enabled }
						})
					}
				} else if (enabled) {
					await createSchedule(script.path)
				}
			} else if (scheduleExists && !$triggersCount?.primary_schedule) {
				await ScheduleService.deleteSchedule({
					workspace: $workspaceStore ?? '',
					path: script.path
				})
			}

			savedScript = structuredClone(script) as NewScriptWithDraft
			if (!disableHistoryChange) {
				history.replaceState(history.state, '', `/scripts/edit/${script.path}`)
			}
			if (stay) {
				script.parent_hash = newHash
			} else {
				dispatch('deploy', newHash)
			}
		} catch (error) {
			dispatch('deployError', error)
			sendUserToast(`Error while saving the script: ${error.body || error.message}`, true)
		}
		loadingSave = false
	}

	async function saveDraft(forceSave = false): Promise<void> {
		if (initialPath != '' && !savedScript) {
			return
		}

		if (savedScript) {
			const draftOrDeployed = cleanValueProperties(savedScript.draft || savedScript)
			const current = cleanValueProperties(script)
			if (!forceSave && orderedJsonStringify(draftOrDeployed) === orderedJsonStringify(current)) {
				sendUserToast('No changes detected, ignoring', false, [
					{
						label: 'Save anyway',
						callback: () => {
							saveDraft(true)
						}
					}
				])
				return
			}
		}

		loadingDraft = true
		try {
			try {
				localStorage.removeItem(script.path)
			} catch (e) {
				console.error('error interacting with local storage', e)
			}
			script.schema = script.schema ?? emptySchema()
			try {
				const result = await inferArgs(script.language, script.content, script.schema as any)
				script.no_main_func = result?.no_main_func || undefined
				script.has_preprocessor = result?.has_preprocessor || undefined
			} catch (error) {
				sendUserToast(`Could not parse code, are you sure it is valid?`, true)
			}

			if (initialPath == '' || savedScript?.draft_only) {
				if (savedScript?.draft_only) {
					await ScriptService.deleteScriptByPath({
						workspace: $workspaceStore!,
						path: initialPath
					})
					script.parent_hash = undefined
				}
				await ScriptService.createScript({
					workspace: $workspaceStore!,
					requestBody: {
						path: script.path,
						summary: script.summary,
						description: script.description ?? '',
						content: script.content,
						schema: script.schema,
						is_template: script.is_template,
						language: script.language,
						kind: script.kind,
						tag: script.tag,
						draft_only: true,
						envs: script.envs,
						concurrent_limit: script.concurrent_limit,
						concurrency_time_window_s: script.concurrency_time_window_s,
						cache_ttl: script.cache_ttl,
						ws_error_handler_muted: script.ws_error_handler_muted,
						priority: script.priority,
						restart_unless_cancelled: script.restart_unless_cancelled,
						delete_after_use: script.delete_after_use,
						timeout: script.timeout,
						concurrency_key: emptyString(script.concurrency_key)
							? undefined
							: script.concurrency_key,
						visible_to_runner_only: script.visible_to_runner_only,
						no_main_func: script.no_main_func,
						has_preprocessor: script.has_preprocessor,
						on_behalf_of_email: script.on_behalf_of_email
					}
				})
			}
			await DraftService.createDraft({
				workspace: $workspaceStore!,
				requestBody: {
					path: initialPath == '' || savedScript?.draft_only ? script.path : initialPath,
					typ: 'script',
					value: { ...script, primary_schedule: $primaryScheduleStore }
				}
			})

			savedScript = {
				...(initialPath == '' || savedScript?.draft_only
					? { ...structuredClone(script), draft_only: true }
					: savedScript),
				draft: structuredClone(script)
			} as NewScriptWithDraft

			let savedAtNewPath = false
			if (initialPath == '' || (savedScript?.draft_only && script.path !== initialPath)) {
				savedAtNewPath = true
				initialPath = script.path
				dispatch('saveInitial', script.path)
			}
			dispatch('saveDraft', { path: script.path, savedAtNewPath, script })

			sendUserToast('Saved as draft')
		} catch (error) {
			sendUserToast(
				`Error while saving the script as a draft: ${error.body || error.message}`,
				true
			)
			dispatch('saveDraftError', error)
		}
		loadingDraft = false
	}

	function computeDropdownItems(initialPath: string) {
		let dropdownItems: { label: string; onClick: () => void }[] =
			initialPath != '' && customUi?.topBar?.extraDeployOptions != false
				? [
						{
							label: 'Deploy & Stay here',
							onClick: () => {
								handleEditScript(true)
							}
						},
						{
							label: 'Fork',
							onClick: () => {
								window.open(`/scripts/add?template=${initialPath}`)
							}
						},
						...(!script.draft_only
							? [
									{
										label: 'Exit & See details',
										onClick: () => {
											dispatch('seeDetails', initialPath)
										}
									}
							  ]
							: [])
				  ]
				: []

		return dropdownItems.length > 0 ? dropdownItems : undefined
	}

	function onKeyDown(event: KeyboardEvent) {
		switch (event.key) {
			case 's':
				if (event.ctrlKey || event.metaKey) {
					saveDraft()
					event.preventDefault()
				}
				break
		}
	}

	let path: Path | undefined = undefined
	let dirtyPath = false

	let selectedTab: 'metadata' | 'runtime' | 'ui' | 'triggers' = 'metadata'

	let deploymentMsg = ''
	let msgInput: HTMLInputElement | undefined = undefined

	function langToLanguage(lang: SupportedLanguage | 'docker' | 'bunnative'): SupportedLanguage {
		if (lang == 'docker') {
			return 'bash'
		}
		if (lang == 'bunnative') {
			return 'bun'
		}
		return lang
	}

	async function applyArgs(e) {
		selectedInputTab = e.detail.kind
		metadataOpen = false
		if (scriptEditor) {
			scriptEditor.updateArgs(e.detail.args ?? {})
		}
	}

	function openTriggers(ev) {
		metadataOpen = true
		selectedTab = 'triggers'
		selectedTriggerStore.set(ev.detail.kind)
		triggerDefaultValuesStore.set(ev.detail.config)
		captureOn.set(true)
	}

	function addPreprocessor() {
		const code = editor?.getCode()
		if (code) {
			const preprocessorCode =
				script.language === 'python3'
					? PYTHON_PREPROCESSOR_MODULE_CODE
					: BUN_PREPROCESSOR_MODULE_CODE
			const mainIndex = code.indexOf(
				script.language === 'python3' ? 'def main' : 'export async function main'
			)
			if (mainIndex === -1) {
				editor?.setCode(code + preprocessorCode)
			} else {
				editor?.setCode(
					code.slice(0, mainIndex) + preprocessorCode + '\n' + code.slice(mainIndex) + '\n\n'
				)
			}
		}
		selectedInputTab = 'preprocessor'
	}
</script>

<svelte:window on:keydown={onKeyDown} />
<slot />

<DeployOverrideConfirmationModal
	bind:deployedBy
	bind:confirmCallback
	bind:open
	{diffDrawer}
	bind:deployedValue
	currentValue={script}
/>
{#if !$userStore?.operator}
	<Drawer
		placement="right"
		bind:open={metadataOpen}
		size={selectedTab === 'ui' ? '1200px' : '800px'}
	>
		<DrawerContent noPadding title="Settings" on:close={() => (metadataOpen = false)}>
			<!-- svelte-ignore a11y-autofocus -->
			<div class="flex flex-col h-full">
				<Tabs bind:selected={selectedTab} wrapperClass="flex-none w-full">
					<Tab value="metadata">Metadata</Tab>
					<Tab value="runtime">Runtime</Tab>
					<Tab value="ui">
						Generated UI
						<Tooltip
							documentationLink="https://www.windmill.dev/docs/core_concepts/json_schema_and_parsing"
						>
							The arguments are synced with the main signature but you may refine the parts that
							cannot be inferred from the type directly.
						</Tooltip>
					</Tab>
					<Tab value="triggers">
						Triggers
						<Tooltip documentationLink="https://www.windmill.dev/docs/getting_started/triggers">
							Configure how this script will be triggered.
						</Tooltip>
					</Tab>

					<svelte:fragment slot="content">
						<div class="min-h-0 grow overflow-y-auto">
							<TabContent value="metadata">
								<div class="flex flex-col gap-8 px-4 py-2">
									<Section label="Metadata">
										<svelte:fragment slot="action">
											<div class="flex flex-row items-center gap-2">
												<ErrorHandlerToggleButton
													kind="script"
													scriptOrFlowPath={script.path}
													bind:errorHandlerMuted={script.ws_error_handler_muted}
													iconOnly={false}
												/>
											</div>
										</svelte:fragment>
										<div class="flex flex-col gap-4">
											<Label label="Summary">
												<MetadataGen
													label="Summary"
													bind:content={script.summary}
													lang={script.language}
													code={script.content}
													promptConfigName="summary"
													generateOnAppear
													on:change={() => {
														if (initialPath == '' && script.summary?.length > 0 && !dirtyPath) {
															path?.setName(
																script.summary
																	.toLowerCase()
																	.replace(/[^a-z0-9_]/g, '_')
																	.replace(/-+/g, '_')
																	.replace(/^-|-$/g, '')
															)
														}
													}}
													elementProps={{
														type: 'text',
														placeholder: 'Short summary to be displayed when listed'
													}}
												/>
											</Label>
											<Label label="Path">
												<svelte:fragment slot="header">
													<Tooltip
														documentationLink="https://www.windmill.dev/docs/core_concepts/roles_and_permissions#path"
													>
														The unique identifier of the script in the workspace that defines
														permissions
													</Tooltip>
												</svelte:fragment>
												<Path
													bind:this={path}
													bind:error={pathError}
													bind:path={script.path}
													bind:dirty={dirtyPath}
													{initialPath}
													autofocus={false}
													namePlaceholder="script"
													kind="script"
												/>
											</Label>
											<Label label="Description">
												<MetadataGen
													bind:content={script.description}
													lang={script.language}
													code={script.content}
													promptConfigName="description"
													elementType="textarea"
													elementProps={{
														placeholder: 'What this script does and how to use it'
													}}
												/>
											</Label>
										</div>
									</Section>

									<Section label="Language">
										<svelte:fragment slot="action"><DefaultScripts /></svelte:fragment>
										{#if lockedLanguage}
											<div class="text-sm text-tertiary italic mb-2">
												As a forked script, the language '{script.language}' cannot be modified.
											</div>
										{/if}
										<div class=" grid grid-cols-3 gap-2">
											{#each langs as [label, lang] (lang)}
												{@const isPicked =
													(lang == script.language && template == 'script') ||
													(template == 'bunnative' && lang == 'bunnative') ||
													(template == 'docker' && lang == 'docker')}
												<Popover
													disablePopup={!enterpriseLangs.includes(lang) || !!$enterpriseLicense}
												>
													<Button
														size="sm"
														variant="border"
														color={isPicked ? 'blue' : 'light'}
														btnClasses={isPicked
															? '!border-2 !bg-blue-50/75 dark:!bg-frost-900/75'
															: 'm-[1px]'}
														on:click={() => {
															if (lang == 'docker') {
																if (isCloudHosted()) {
																	sendUserToast(
																		'You cannot use Docker scripts on the multi-tenant platform. Use a dedicated instance or self-host windmill instead.',
																		true,
																		[
																			{
																				label: 'Learn more',
																				callback: () => {
																					window.open(
																						'https://www.windmill.dev/docs/advanced/docker',
																						'_blank'
																					)
																				}
																			}
																		]
																	)
																	return
																}
																template = 'docker'
															} else if (lang == 'bunnative') {
																template = 'bunnative'
															} else {
																template = 'script'
															}
															let language = langToLanguage(lang)
															//
															initContent(language, script.kind, template)
															script.language = language
														}}
														disabled={lockedLanguage ||
															(enterpriseLangs.includes(lang) && !$enterpriseLicense)}
													>
														<LanguageIcon {lang} />
														<span class="ml-2 py-2 truncate">{label}</span>
														{#if lang === 'ansible'}
															<span class="text-tertiary !text-xs"> BETA </span>
														{/if}
													</Button>
													<svelte:fragment slot="text"
														>{label} is only available with an enterprise license</svelte:fragment
													>
												</Popover>
											{/each}
										</div>
									</Section>

									<Section label="Script kind">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/script_editor/script_kinds"
											>
												Tag this script's purpose within flows such that it is available as the
												corresponding action.
											</Tooltip>
										</svelte:fragment>
										<ToggleButtonGroup
											class="h-10"
											selected={script.kind}
											on:selected={({ detail }) => {
												template = 'script'
												script.kind = detail
												initContent(script.language, detail, template)
											}}
										>
											{#each scriptKindOptions as { value, title, desc, documentationLink, Icon }}
												<ToggleButton
													label={title}
													{value}
													tooltip={desc}
													{documentationLink}
													icon={Icon}
													showTooltipIcon={Boolean(desc)}
												/>
											{/each}
										</ToggleButtonGroup>
									</Section>
								</div>
							</TabContent>
							<TabContent value="runtime">
								<div class="flex flex-col gap-8 px-4 py-2">
									<Section label="Concurrency limits" eeOnly>
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/core_concepts/concurrency_limits"
											>
												Allowed concurrency within a given timeframe
											</Tooltip>
										</svelte:fragment>
										<div class="flex flex-col gap-4">
											<Label label="Max number of executions within the time window">
												<div class="flex flex-row gap-2 max-w-sm">
													<input
														disabled={!$enterpriseLicense}
														bind:value={script.concurrent_limit}
														type="number"
													/>
													<Button
														size="sm"
														color="light"
														on:click={() => {
															script.concurrent_limit = undefined
															script.concurrency_time_window_s = undefined
															script.concurrency_key = undefined
														}}
														variant="border">Remove Limits</Button
													>
												</div>
											</Label>
											<Label label="Time window in seconds">
												<SecondsInput
													disabled={!$enterpriseLicense}
													bind:seconds={script.concurrency_time_window_s}
												/>
											</Label>
											<Label label="Custom concurrency key (optional)">
												<svelte:fragment slot="header">
													<Tooltip
														documentationLink="https://www.windmill.dev/docs/core_concepts/concurrency_limits#custom-concurrency-key"
													>
														Concurrency keys are global, you can have them be workspace specific
														using the variable `$workspace`. You can also use an argument's value
														using `$args[name_of_arg]`</Tooltip
													>
												</svelte:fragment>
												<input
													disabled={!$enterpriseLicense}
													type="text"
													autofocus
													bind:value={script.concurrency_key}
													placeholder={`$workspace/script/${script.path}-$args[foo]`}
												/>
											</Label>
										</div>
									</Section>
									<Section label="Worker group tag (queue)">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/core_concepts/worker_groups"
											>
												The script will be executed on a worker configured to listen to this worker
												group tag (queue). For instance, you could setup an "highmem", or "gpu" tag.
											</Tooltip>
										</svelte:fragment>
										<WorkerTagPicker bind:tag={script.tag} />
									</Section>
									<Section label="Cache">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/core_concepts/caching"
											>
												Cache the results for each possible inputs
											</Tooltip>
										</svelte:fragment>
										<div class="flex gap-2 shrink flex-col">
											<Toggle
												size="sm"
												checked={Boolean(script.cache_ttl)}
												on:change={() => {
													if (script.cache_ttl && script.cache_ttl != undefined) {
														script.cache_ttl = undefined
													} else {
														script.cache_ttl = 300
													}
												}}
												options={{
													right: 'Cache the results for each possible inputs'
												}}
											/>
											<span class="text-secondary text-sm leading-none">
												How long to the keep cache valid
											</span>
											{#if script.cache_ttl}
												<SecondsInput bind:seconds={script.cache_ttl} />
											{:else}
												<SecondsInput disabled />
											{/if}
										</div>
									</Section>
									<Section label="Timeout">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/script_editor/settings#timeout"
											>
												Add a custom timeout for this script
											</Tooltip>
										</svelte:fragment>
										<div class="flex gap-2 shrink flex-col">
											<Toggle
												size="sm"
												checked={Boolean(script.timeout)}
												on:change={() => {
													if (script.timeout && script.timeout != undefined) {
														script.timeout = undefined
													} else {
														script.timeout = 300
													}
												}}
												options={{
													right: 'Add a custom timeout for this script'
												}}
											/>
											<span class="text-secondary text-sm leading-none"> Timeout duration </span>
											{#if script.timeout}
												<SecondsInput bind:seconds={script.timeout} />
											{:else}
												<SecondsInput disabled />
											{/if}
										</div>
									</Section>
									<Section label="Perpetual script">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/script_editor/perpetual_scripts"
											>
												Restart the script upon ending unless cancelled
											</Tooltip>
										</svelte:fragment>
										<div class="flex gap-2 shrink flex-col">
											<Toggle
												size="sm"
												checked={Boolean(script.restart_unless_cancelled)}
												on:change={() => {
													if (script.restart_unless_cancelled) {
														script.restart_unless_cancelled = undefined
													} else {
														script.restart_unless_cancelled = true
													}
												}}
												options={{
													right: 'Restart upon ending unless cancelled'
												}}
											/>
										</div>
									</Section>
									<Section label="Dedicated workers" eeOnly>
										<Toggle
											disabled={!$enterpriseLicense ||
												isCloudHosted() ||
												(script.language != 'bun' &&
													script.language != 'python3' &&
													script.language != 'deno')}
											size="sm"
											checked={Boolean(script.dedicated_worker)}
											on:change={() => {
												if (script.dedicated_worker) {
													script.dedicated_worker = undefined
												} else {
													script.dedicated_worker = true
												}
											}}
											options={{
												right: 'Script is run on dedicated workers'
											}}
										/>
										{#if script.dedicated_worker}
											<div class="py-2">
												<Alert type="info" title="Require dedicated workers">
													One worker in a worker group needs to be configured with dedicated worker
													set to: <pre>{$workspaceStore}:{script.path}</pre>
												</Alert>
											</div>
										{/if}
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/core_concepts/dedicated_workers"
											>
												In this mode, the script is meant to be run on dedicated workers that run
												the script at native speed. Can reach >1500rps per dedicated worker. Only
												available on enterprise edition and for Python3, Deno and Bun. For other
												languages, the efficiency is already on par with deidcated workers since
												they do not spawn a full runtime</Tooltip
											>
										</svelte:fragment>
									</Section>
									<Section label="Delete after use">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/script_editor/settings#delete-after-use"
											>
												WARNING: This settings ONLY applies to synchronous webhooks or when the
												script is used within a flow. If used individually, this script must be
												triggered using a synchronous endpoint to have the desired effect.
												<br />
												<br />
												The logs, arguments and results of the job will be completely deleted from Windmill
												once it is complete and the result has been returned.
												<br />
												<br />
												The deletion is irreversible.
												{#if !$enterpriseLicense}
													<br />
													<br />
													This option is only available on Windmill Enterprise Edition.
												{/if}
											</Tooltip>
										</svelte:fragment>
										<div class="flex gap-2 shrink flex-col">
											<Toggle
												disabled={!$enterpriseLicense}
												size="sm"
												checked={Boolean(script.delete_after_use)}
												on:change={() => {
													if (script.delete_after_use) {
														script.delete_after_use = undefined
													} else {
														script.delete_after_use = true
													}
												}}
												options={{
													right: 'Delete logs, arguments and results after use'
												}}
											/>
										</div>
									</Section>
									{#if !isCloudHosted()}
										<Section label="High priority script" eeOnly>
											<Toggle
												disabled={!$enterpriseLicense || isCloudHosted()}
												size="sm"
												checked={script.priority !== undefined && script.priority > 0}
												on:change={() => {
													if (script.priority) {
														script.priority = undefined
													} else {
														script.priority = 100
													}
												}}
												options={{
													right: 'Label as high priority'
												}}
											>
												<svelte:fragment slot="right">
													<input
														type="number"
														class="!w-16 ml-4"
														disabled={script.priority === undefined}
														bind:value={script.priority}
														on:focus
														on:change={() => {
															if (script.priority && script.priority > 100) {
																script.priority = 100
															} else if (script.priority && script.priority < 0) {
																script.priority = 0
															}
														}}
													/>
												</svelte:fragment>
											</Toggle>
											<svelte:fragment slot="header">
												<!-- TODO: Add EE-only badge when we have it -->
												<Tooltip
													documentationLink="https://www.windmill.dev/docs/core_concepts/jobs#high-priority-jobs"
												>
													Jobs from script labeled as high priority take precedence over the other
													jobs when in the jobs queue.
													{#if !$enterpriseLicense}This is a feature only available on enterprise
														edition.{/if}
												</Tooltip>
											</svelte:fragment>
										</Section>
									{/if}
									<Section label="Runs visibility">
										<svelte:fragment slot="header">
											<Tooltip
												documentationLink="https://www.windmill.dev/docs/core_concepts/monitor_past_and_future_runs#invisible-runs"
											>
												When this option is enabled, manual executions of this script are invisible
												to users other than the user running it, including the owner(s). This
												setting can be overridden when this script is run manually from the advanced
												menu.
											</Tooltip>
										</svelte:fragment>
										<div class="flex gap-2 shrink flex-col">
											<Toggle
												size="sm"
												checked={Boolean(script.visible_to_runner_only)}
												on:change={() => {
													if (script.visible_to_runner_only) {
														script.visible_to_runner_only = undefined
													} else {
														script.visible_to_runner_only = true
													}
												}}
												options={{
													right: 'Make runs invisible to others'
												}}
											/>
										</div>
									</Section>
									<Section label="On behalf of last editor">
										<svelte:fragment slot="header">
											<Tooltip>
												When this option is enabled, the script will be run with the permissions of
												the last editor.
											</Tooltip>
										</svelte:fragment>
										<div class="flex gap-2 shrink flex-col">
											<Toggle
												size="sm"
												checked={Boolean(script.on_behalf_of_email)}
												on:change={() => {
													if (script.on_behalf_of_email) {
														script.on_behalf_of_email = undefined
													} else {
														script.on_behalf_of_email = $userStore?.email
													}
												}}
												options={{
													right: 'Run on behalf of last editor'
												}}
											/>
										</div>
									</Section>
									{#if !isCloudHosted()}
										<Section label="Custom env variables">
											<svelte:fragment slot="header">
												<Tooltip
													documentationLink="https://www.windmill.dev/docs/script_editor/custom_environment_variables"
												>
													Additional static custom env variables to pass to the script.
												</Tooltip>
											</svelte:fragment>
											{#if script.envs && script.envs.length > 0}
												<Alert type="warning" title="Not passed in previews" size="xs">
													Static envs variables are not passed in preview but solely on deployed
													scripts.
												</Alert>
											{/if}
											<div class="w-full mt-2">
												<span class="text-tertiary text-xs pb-2"
													>Format is: `{'<KEY>=<VALUE>'}`</span
												>
												{#if Array.isArray(script.envs ?? [])}
													{#each script.envs ?? [] as v, i}
														<div class="flex max-w-md mt-1 w-full items-center relative">
															<input type="text" bind:value={v} placeholder="<KEY>=<VALUE>" />
															<button
																transition:fade|local={{ duration: 50 }}
																class="rounded-full p-1 bg-surface/60 duration-200 hover:bg-gray-200 absolute right-2"
																aria-label="Clear"
																on:click={() => {
																	script.envs && script.envs.splice(i, 1)
																	script.envs = script.envs
																}}
															>
																<X size={14} />
															</button>
														</div>
													{/each}
												{/if}
											</div>
											<div class="flex mt-2">
												<Button
													variant="border"
													color="light"
													size="xs"
													on:click={() => {
														if (script.envs == undefined || !Array.isArray(script.envs)) {
															script.envs = []
														}
														script.envs = script.envs.concat('')
													}}
												>
													<div class="flex flex-row gap-1">
														<Plus size="16" />
														Add item
													</div>
												</Button>
											</div>
										</Section>
									{/if}
								</div>
							</TabContent>
							<TabContent value="ui" class="h-full p-4">
								<ScriptSchema bind:schema={script.schema} />
							</TabContent>
							<TabContent value="triggers">
								<TriggersEditor
									on:applyArgs={applyArgs}
									on:addPreprocessor={addPreprocessor}
									on:exitTriggers={() => {
										captureTable?.loadCaptures(true)
									}}
									{args}
									{initialPath}
									schema={script.schema}
									noEditor={true}
									isFlow={false}
									currentPath={script.path}
									hash={script.parent_hash}
									newItem={initialPath == ''}
									canHavePreprocessor={script.language === 'bun' ||
										script.language === 'deno' ||
										script.language === 'python3'}
									{hasPreprocessor}
								/>
								<!-- <ScriptSchedules {initialPath} schema={script.schema} schedule={scheduleStore} /> -->
							</TabContent>
						</div>
					</svelte:fragment>
				</Tabs>
			</div>
		</DrawerContent>
	</Drawer>

	<div class="flex flex-col h-screen">
		<div class="flex h-12 items-center px-4">
			<div class="justify-between flex gap-2 lg:gap-8 w-full items-center">
				<div class="flex flex-row gap-2 grow max-w-md">
					<div class="center-center">
						<button
							disabled={customUi?.topBar?.settings == false}
							on:click={async () => {
								metadataOpen = true
							}}
						>
							<LanguageIcon lang={script.language} height={20} />
						</button>
					</div>
					<Summary
						disabled={customUi?.topBar?.editableSummary == false}
						bind:value={script.summary}
					/>
				</div>

				<div class="gap-4 flex">
					{#if $primaryScheduleStore != undefined ? $primaryScheduleStore && $primaryScheduleStore?.enabled : $triggersCount?.primary_schedule}
						<Button
							btnClasses="hidden lg:inline-flex"
							startIcon={{ icon: Calendar }}
							variant="contained"
							color="light"
							size="xs"
							on:click={async () => {
								metadataOpen = true
								selectedTab = 'triggers'
								$selectedTriggerStore = 'schedules'
							}}
						>
							{$primaryScheduleStore != undefined
								? $primaryScheduleStore
									? $primaryScheduleStore?.cron
									: ''
								: $triggersCount?.primary_schedule?.schedule}
						</Button>
					{/if}
					{#if customUi?.topBar?.path != false}
						<div class="flex justify-start w-full border rounded-md overflow-hidden">
							<div>
								<button
									on:click={async () => {
										metadataOpen = true
									}}
								>
									<Badge
										color="gray"
										class="center-center !bg-surface-secondary !text-tertiary !h-[28px]  !w-[70px] rounded-none hover:!bg-surface-hover transition-all"
									>
										<Pen size={12} class="mr-2" /> Path
									</Badge>
								</button>
							</div>
							<input
								type="text"
								readonly
								value={script.path}
								size={script.path?.length || 50}
								class="font-mono !text-xs !min-w-[96px] !max-w-[300px] !w-full !h-[28px] !my-0 !py-0 !border-l-0 !rounded-l-none !border-0 !shadow-none"
								on:focus={({ currentTarget }) => {
									currentTarget.select()
								}}
							/>
						</div>
					{/if}
				</div>

				{#if $enterpriseLicense && initialPath != ''}
					<Awareness />
				{/if}

				<div class="flex flex-row gap-x-1 lg:gap-x-2">
					{#if customUi?.topBar?.diff != false}
						<Button
							color="light"
							variant="border"
							size="xs"
							on:click={async () => {
								if (!savedScript) {
									return
								}
								await syncWithDeployed()

								diffDrawer?.openDrawer()
								diffDrawer?.setDiff({
									mode: 'normal',
									deployed: deployedValue ?? savedScript,
									draft: savedScript['draft'],
									current: script
								})
							}}
							disabled={!savedScript || !diffDrawer}
						>
							<div class="flex flex-row gap-2 items-center">
								<DiffIcon size={14} />
								<span class="hidden lg:flex"> Diff </span>
							</div>
						</Button>
					{/if}
					{#if customUi?.topBar?.settings != false}
						<Button
							color="light"
							variant="border"
							size="xs"
							on:click={() => {
								metadataOpen = true
							}}
							startIcon={{ icon: Settings }}
						>
							<span class="hidden lg:flex"> Settings </span>
						</Button>
					{/if}
					<Button
						loading={loadingDraft}
						size="xs"
						startIcon={{ icon: Save }}
						on:click={() => saveDraft()}
						disabled={initialPath != '' && !savedScript}
						shortCut={{
							key: 'S'
						}}
					>
						<span class="hidden lg:flex"> Draft </span>
					</Button>

					<CustomPopover appearTimeout={0} focusEl={msgInput}>
						<Button
							loading={loadingSave}
							size="xs"
							disabled={!fullyLoaded}
							startIcon={{ icon: Save }}
							on:click={() => handleEditScript(false)}
							dropdownItems={computeDropdownItems(initialPath)}
						>
							Deploy
						</Button>
						<svelte:fragment slot="overlay">
							<div class="flex flex-row gap-2 min-w-72">
								<input
									type="text"
									placeholder="Deployment message"
									bind:value={deploymentMsg}
									bind:this={msgInput}
									on:keydown={(e) => {
										if (e.key === 'Enter') {
											handleEditScript(false, deploymentMsg)
										}
									}}
								/>
								<Button
									size="xs"
									on:click={() => handleEditScript(false, deploymentMsg)}
									endIcon={{ icon: CornerDownLeft }}
									loading={loadingSave}
								>
									Deploy
								</Button>
							</div>
						</svelte:fragment>
					</CustomPopover>
				</div>
			</div>
		</div>

		<ScriptEditor
			bind:selectedTab={selectedInputTab}
			{customUi}
			collabMode
			edit={initialPath != ''}
			on:format={() => {
				saveDraft()
			}}
			on:saveDraft={() => {
				saveDraft()
			}}
			on:openTriggers={openTriggers}
			on:applyArgs={applyArgs}
			on:addPreprocessor={addPreprocessor}
			bind:editor
			bind:this={scriptEditor}
			bind:schema={script.schema}
			path={script.path}
			bind:code={script.content}
			lang={script.language}
			{initialArgs}
			kind={script.kind}
			{template}
			tag={script.tag}
			bind:args
			bind:hasPreprocessor
			bind:captureTable
		/>
	</div>
{:else}
	Script Builder not available to operators
{/if}
