<script lang="ts">
	import { FlowStatusModule, Job, JobService } from '$lib/gen'
	import { userStore, workspaceStore } from '$lib/stores'
	import FlowJobResult from './FlowJobResult.svelte'
	import FlowPreviewStatus from './preview/FlowPreviewStatus.svelte'
	import Icon from 'svelte-awesome'
	import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
	import { createEventDispatcher } from 'svelte'
	import { onDestroy } from 'svelte'
	import type { FlowState } from './flows/flowState'
	import { Badge, Button, Tab } from './common'
	import DisplayResult from './DisplayResult.svelte'
	import Tabs from './common/tabs/Tabs.svelte'
	import { FlowGraph, type GraphModuleState } from './graph'
	import ModuleStatus from './ModuleStatus.svelte'
	import { displayDate, emptyString, isOwner, pluralize, truncateRev } from '$lib/utils'
	import JobArgs from './JobArgs.svelte'
	import { Loader2 } from 'lucide-svelte'
	import FlowStatusWaitingForEvents from './FlowStatusWaitingForEvents.svelte'

	const dispatch = createEventDispatcher()

	export let jobId: string
	export let workspaceId: string | undefined = undefined
	export let flowState: FlowState | undefined = undefined
	export let flowJobIds:
		| {
				moduleId: string
				flowJobs: string[]
		  }
		| undefined = undefined
	export let job: Job | undefined = undefined

	export let flowModuleStates: Record<string, GraphModuleState> = {}

	let localFlowModuleStates: Record<string, GraphModuleState> = {}
	export let retry_status: Record<string, number> = {}
	export let suspend_status: number | undefined = undefined
	export let render = true

	export let is_owner = false

	let selectedNode: string | undefined = undefined

	let jobResults: any[] = []
	let jobFailures: boolean[] = []

	let forloop_selected = ''
	let timeout: NodeJS.Timeout

	$: localFlowModuleStates && updateFlowModuleStates()

	function updateFlowModuleStates() {
		Object.entries(localFlowModuleStates).forEach(([moduleId, state]) => {
			if (
				flowModuleStates[moduleId] !== state &&
				flowModuleStates[moduleId]?.type !== FlowStatusModule.type.FAILURE
			) {
				flowModuleStates[moduleId] = state
			}
		})
	}
	let lastSize = 0
	$: {
		let len = (flowJobIds?.flowJobs ?? []).length
		if (len != lastSize) {
			forloop_selected = flowJobIds?.flowJobs[len - 1] ?? ''
			lastSize = len
		}
	}

	$: updateFailCount(job?.flow_status?.retry?.fail_count)
	$: suspend_status = job?.flow_status?.modules?.[job?.flow_status.step]?.count

	function updateFailCount(count?: number) {
		if (count) {
			retry_status[jobId ?? ''] = count
		} else {
			delete retry_status[jobId ?? '']
		}
	}

	$: innerModules =
		job?.flow_status?.modules.concat(
			job?.flow_status.failure_module.type != 'WaitingForPriorSteps'
				? job?.flow_status.failure_module
				: []
		) ?? []

	$: innerModules && localFlowModuleStates && updateInnerModules()

	function updateInnerModules() {
		innerModules.forEach((mod, i) => {
			if (
				mod.type === FlowStatusModule.type.WAITING_FOR_EVENTS &&
				localFlowModuleStates?.[innerModules?.[i - 1]?.id ?? '']?.type ==
					FlowStatusModule.type.SUCCESS
			) {
				localFlowModuleStates[mod.id ?? ''] = { type: mod.type, args: job?.args }
			} else if (
				mod.type === FlowStatusModule.type.WAITING_FOR_EXECUTOR &&
				localFlowModuleStates[mod.id ?? '']?.scheduled_for == undefined
			) {
				JobService.getJob({
					workspace: workspaceId ?? $workspaceStore ?? '',
					id: mod.job ?? ''
				}).then((job) => {
					localFlowModuleStates[mod.id ?? ''] = {
						type: mod.type,
						scheduled_for: 'scheduled for ' + displayDate(job?.['scheduled_for'], true),
						job_id: job?.id,
						parent_module: mod['parent_module'],
						args: job?.args
					}
				})
			}
		})
	}

	let errorCount = 0
	async function loadJobInProgress() {
		if (jobId != '00000000-0000-0000-0000-000000000000') {
			try {
				const newJob = await JobService.getJob({
					workspace: workspaceId ?? $workspaceStore ?? '',
					id: jobId ?? ''
				})
				if (JSON.stringify(newJob) !== JSON.stringify(job)) {
					job = newJob
				}
				errorCount = 0
			} catch (e) {
				errorCount += 1
				console.error(e)
			}
		}
		if (job?.type !== 'CompletedJob' && errorCount < 4) {
			timeout = setTimeout(() => loadJobInProgress(), 500)
		}
	}

	$: job && dispatch('jobsLoaded', job)

	async function updateJobId() {
		if (jobId !== job?.id) {
			retry_status = {}
			localFlowModuleStates = {}
			await loadJobInProgress()
			job?.script_path && loadOwner(job.script_path)
		}
	}

	$: jobId && updateJobId()

	$: isListJob = flowJobIds != undefined && Array.isArray(flowJobIds?.flowJobs)

	onDestroy(() => {
		timeout && clearTimeout(timeout)
	})

	function loadOwner(path: string) {
		is_owner = isOwner(path, $userStore!, workspaceId ?? $workspaceStore!)
	}

	$: selected = isListJob ? 'sequence' : 'graph'

	function isSuccess(arg: any): boolean | undefined {
		if (arg == undefined) {
			return undefined
		} else {
			return arg == true
		}
	}

	function onJobsLoaded(mod: FlowStatusModule, job: Job): void {
		if (mod.id && (mod.flow_jobs ?? []).length == 0) {
			if (flowState && flowState[mod.id]) {
				flowState[mod.id].previewResult = job['result']
				flowState[mod.id].previewArgs = job.args
			}
			if (job.type == 'QueuedJob') {
				localFlowModuleStates[mod.id] = {
					type: FlowStatusModule.type.IN_PROGRESS,
					logs: job.logs,
					args: job.args,
					parent_module: mod['parent_module']
				}
			} else {
				localFlowModuleStates[mod.id] = {
					args: job.args,
					type: job['success'] ? FlowStatusModule.type.SUCCESS : FlowStatusModule.type.FAILURE,
					logs: job.logs,
					result: job['result'],
					job_id: job.id,
					parent_module: mod['parent_module'],
					iteration_total: mod.iterator?.itered?.length
					// retries: flowState?.raw_flow
				}
			}
		}
	}
</script>

{#if job}
	<div class="flow-root w-full space-y-4">
		{#if innerModules.length > 0}
			<h3 class="text-md leading-6 font-bold text-primay border-b pb-2">Flow result</h3>
		{/if}
		{#if isListJob}
			{#if render}
				<div class="w-full h-full border border-gray-600 bg-surface p-1">
					<DisplayResult workspaceId={job?.workspace_id} {jobId} result={jobResults} />
				</div>
			{/if}
		{:else if render}
			<div class={innerModules.length > 0 ? 'border rounded-md shadow p-2' : ''}>
				<FlowPreviewStatus {job} />
				{#if `result` in job}
					<div class="w-full h-full">
						<FlowJobResult
							workspaceId={job?.workspace_id}
							jobId={job?.id}
							loading={job['running'] == true}
							result={job.result}
							logs={job.logs ?? ''}
						/>
					</div>
				{:else if job.flow_status?.modules?.[job?.flow_status?.step]?.type === FlowStatusModule.type.WAITING_FOR_EVENTS}
					<FlowStatusWaitingForEvents {workspaceId} {job} {is_owner} />
				{:else if job.logs}
					<div class="text-xs p-4 bg-gray-50 overflow-auto max-h-80 border">
						<pre class="w-full">{job.logs}</pre>
					</div>
				{:else if innerModules?.length > 0}
					<div class="flex flex-col gap-1">
						{#each innerModules as mod, i (mod.id)}
							{#if mod.type == FlowStatusModule.type.IN_PROGRESS}
								{@const rawMod = job.raw_flow?.modules[i]}

								<div
									><span class="inline-flex gap-1"
										><Badge color="indigo">{mod.id}</Badge>
										<span class="font-medium text-primary">
											{#if !emptyString(rawMod?.summary)}
												{rawMod?.summary ?? ''}
											{:else if rawMod?.value.type == 'script'}
												{rawMod.value.path ?? ''}
											{:else if rawMod?.value.type}
												{rawMod?.value.type}
											{/if}
										</span>

										<Loader2 class="animate-spin" /></span
									></div
								>
							{/if}
						{/each}
					</div>
				{/if}
			</div>
		{/if}
		{#if render}
			{#if innerModules.length > 0 && !isListJob}
				<Tabs bind:selected>
					<Tab value="graph"><span class="font-semibold text-md">Graph</span></Tab>
					<Tab value="sequence"><span class="font-semibold">Details</span></Tab>
				</Tabs>
			{/if}
		{/if}
		<div class={selected == 'graph' ? 'hidden' : ''}>
			{#if isListJob}
				<h3 class="text-md leading-6 font-bold text-tertiary border-b mb-4">
					Embedded flows: ({flowJobIds?.flowJobs.length} items)
				</h3>
				{#each flowJobIds?.flowJobs ?? [] as loopJobId, j}
					{#if render}
						<Button
							variant={forloop_selected === loopJobId ? 'contained' : 'border'}
							color={jobFailures[j] === true
								? 'red'
								: forloop_selected === loopJobId
								? 'dark'
								: 'light'}
							btnClasses="w-full flex justify-start"
							on:click={() => {
								if (forloop_selected == loopJobId) {
									forloop_selected = ''
								} else {
									forloop_selected = loopJobId
								}
							}}
						>
							<span class="truncate">
								#{j + 1}: {loopJobId}
							</span>

							<Icon
								class="ml-2"
								data={forloop_selected == loopJobId ? faChevronUp : faChevronDown}
								scale={0.8}
							/>
						</Button>
					{/if}
					<div class="border p-6" class:hidden={forloop_selected != loopJobId}>
						<svelte:self
							render={forloop_selected == loopJobId && selected == 'sequence' && render}
							{workspaceId}
							bind:suspend_status
							bind:retry_status
							bind:flowState
							bind:flowModuleStates={localFlowModuleStates}
							jobId={loopJobId}
							on:jobsLoaded={(e) => {
								if (flowJobIds?.moduleId) {
									if (flowState?.[flowJobIds.moduleId]) {
										if (
											!flowState[flowJobIds.moduleId].previewResult ||
											!Array.isArray(flowState[flowJobIds.moduleId]?.previewResult)
										) {
											flowState[flowJobIds.moduleId].previewResult = []
										}
										flowState[flowJobIds.moduleId].previewResult[j] = e.detail.result
										flowState[flowJobIds.moduleId].previewArgs = e.detail.args
										jobResults[j] =
											e.detail.type == 'QueuedJob' ? 'Job in progress ...' : e.detail.result
										jobFailures[j] = e.detail.success === false
									}
									if (e.detail.type == 'QueuedJob') {
										localFlowModuleStates[flowJobIds.moduleId] = {
											type: FlowStatusModule.type.IN_PROGRESS,
											logs: e.detail.logs,
											job_id: e.detail.id,
											args: e.detail.args,
											iteration_total: flowJobIds?.flowJobs.length
										}
									} else {
										localFlowModuleStates[flowJobIds.moduleId] = {
											args: e.detail.args,
											type: e.detail.success
												? FlowStatusModule.type.SUCCESS
												: FlowStatusModule.type.FAILURE,
											logs: 'All jobs completed',
											result: jobResults,
											job_id: e.detail.id,
											iteration_total: flowJobIds?.flowJobs.length
										}
									}
								}
							}}
						/>
					</div>
				{/each}
			{:else if innerModules.length > 0}
				<ul class="w-full">
					<h3 class="text-md leading-6 font-bold text-primary border-b mb-4 py-2">
						Step-by-step results
					</h3>

					{#each innerModules as mod, i}
						{#if render}
							<div class="line w-8 h-10" />
							<h3 class="text-tertiary mb-2 w-full">
								{#if job?.raw_flow?.modules && i < job?.raw_flow?.modules.length}
									Step
									<span class="font-medium text-primary">
										{i + 1}
									</span>
									out of
									<span class="font-medium text-primary">{job?.raw_flow?.modules.length}</span>
									{#if job.raw_flow?.modules[i]?.summary}
										: <span class="font-medium text-primary">
											{job.raw_flow?.modules[i]?.summary ?? ''}
										</span>
									{/if}
								{:else}
									<h3>Failure module</h3>
								{/if}
							</h3>
							<div class="line w-8 h-10" />
						{/if}
						<li class="w-full border p-6 space-y-2 bg-blue-50/50 dark:bg-frost-900/50">
							{#if [FlowStatusModule.type.IN_PROGRESS, FlowStatusModule.type.SUCCESS, FlowStatusModule.type.FAILURE].includes(mod.type)}
								{#if job.raw_flow?.modules[i]?.value.type == 'flow'}
									<svelte:self
										render={selected == 'sequence' && render}
										{workspaceId}
										jobId={mod.job}
										bind:suspend_status
										bind:retry_status
										on:jobsLoaded={(e) => onJobsLoaded(mod, e.detail)}
									/>
								{:else}
									<svelte:self
										render={selected == 'sequence' && render}
										{workspaceId}
										bind:suspend_status
										bind:retry_status
										bind:flowState
										bind:flowModuleStates={localFlowModuleStates}
										jobId={mod.job}
										flowJobIds={mod.flow_jobs
											? {
													moduleId: mod.id,
													flowJobs: mod.flow_jobs
											  }
											: undefined}
										on:jobsLoaded={(e) => onJobsLoaded(mod, e.detail)}
									/>
								{/if}
							{:else}
								<ModuleStatus
									type={mod.type}
									scheduled_for={localFlowModuleStates?.[mod.id ?? '']?.scheduled_for}
								/>
							{/if}
						</li>
					{/each}
				</ul>
			{/if}
		</div>
	</div>
	{#if render}
		{#if job.raw_flow && !isListJob}
			<div class="{selected != 'graph' ? 'hidden' : ''} mt-4">
				<div class="grid grid-cols-3 border">
					<div class="col-span-2 bg-surface-secondary">
						<div class="flex flex-col">
							{#each Object.values(retry_status) as count}
								<span class="text-sm">
									Retry in progress, # of failed attempts: {count}
								</span>
							{/each}
							{#if suspend_status}
								<span class="text-sm">
									Flow suspended, waiting for {pluralize(suspend_status, 'approval')}
								</span>
							{/if}
						</div>

						<FlowGraph
							success={isSuccess(job?.['success'])}
							flowModuleStates={localFlowModuleStates}
							on:select={(e) => {
								if (typeof e.detail == 'string') {
									if (e.detail == 'Input') {
										selectedNode = 'start'
									} else if (e.detail == 'Result') {
										selectedNode = 'end'
									} else {
										selectedNode = e.detail
									}
								} else {
									selectedNode = e.detail.id
								}
							}}
							modules={job.raw_flow?.modules ?? []}
							failureModule={job.raw_flow?.failure_module}
						/>
					</div>
					<div class="border-l border-gray-400 pt-1 overflow-auto min-h-[800px] flex flex-col">
						{#if selectedNode}
							{@const node = localFlowModuleStates[selectedNode]}
							{#if selectedNode == 'end'}
								<FlowJobResult
									workspaceId={job?.workspace_id}
									jobId={job?.id}
									filename={job.id}
									loading={job['running']}
									noBorder
									col
									result={job['result']}
									logs={job.logs ?? ''}
								/>
							{:else if selectedNode == 'start'}
								{#if job.args}
									<div class="p-2">
										<JobArgs args={job.args} />
									</div>
								{:else}
									<p class="p-2">No arguments</p>
								{/if}
							{:else if node}
								<div class="px-2 flex gap-2 min-w-0">
									<ModuleStatus type={node.type} scheduled_for={node['scheduled_for']} />
									{#if node.job_id}
										<div class="truncate"
											><div class=" text-primary whitespace-nowrap truncate">
												<span class="font-bold">Job Id</span>
												<a
													rel="noreferrer"
													target="_blank"
													href="/run/{node.job_id ?? ''}?workspace={job?.workspace_id}"
												>
													{truncateRev(node.job_id ?? '', 10) ?? ''}
												</a>
											</div>
										</div>
									{/if}
								</div>
								<div class="px-1 border-b border-black">
									<JobArgs args={node.args} />
								</div>

								<FlowJobResult
									workspaceId={job?.workspace_id}
									jobId={job?.id}
									loading={job['running'] == true}
									noBorder
									col
									result={node.result}
									logs={node.logs ?? ''}
								/>
							{:else}
								<p class="p-2 text-tertiary italic"
									>The execution of this node has no information attached to it. The job likely did
									not run yet</p
								>
							{/if}
						{:else}<p class="p-2 text-tertiary italic">Select a node to see its details here</p
							>{/if}
					</div>
				</div>
			</div>
		{/if}
	{/if}
{:else}
	Job loading...
{/if}

<style>
	.line {
		background: repeating-linear-gradient(to bottom, transparent 0 4px, #bbb 4px 8px) 50%/1px 100%
			no-repeat;
	}
</style>
