<script lang="ts" module>
	import { isTriggerOrScheduleKind } from 'windmill-utils-internal'

	export type OnBehalfOfChoice = 'target' | 'me' | 'custom' | undefined

	export interface OnBehalfOfDetails {
		email: string
		permissionedAs: string
	}

	/**
	 * Check if an item needs on_behalf_of selection.
	 * Shows the selector when the source item has an on_behalf_of value set.
	 */
	export function needsOnBehalfOfSelection(kind: string, sourceValue: string | undefined): boolean {
		const supported =
			kind === 'flow' ||
			kind === 'script' ||
			kind === 'app' ||
			kind === 'raw_app' ||
			kind === 'trigger' ||
			isTriggerOrScheduleKind(kind)
		if (!supported) return false
		return !!sourceValue
	}
</script>

<script lang="ts">
	import { Check, UserCog, Users, ExternalLink } from 'lucide-svelte'
	import MeltPopover from './meltComponents/Popover.svelte'
	import Modal from './common/modal/Modal.svelte'
	import { userStore } from '$lib/stores'
	import { UserService, type User } from '$lib/gen'
	import TextInput from './text_input/TextInput.svelte'

	interface Props {
		targetWorkspace: string
		/** The target value: email for flows/scripts, permissioned_as (u/username) for triggers */
		targetValue: string | undefined
		selected: OnBehalfOfChoice
		/**
		 * Called when the user picks a choice.
		 * For 'custom', `details` contains both `email` and `permissionedAs` (u/{username}).
		 * Callers pick whichever format they need.
		 */
		onSelect: (choice: OnBehalfOfChoice, details?: OnBehalfOfDetails) => void
		kind: string
		canPreserve: boolean
		/** The value of the custom-selected user (for display) */
		customValue?: string | undefined
		/** When false, labels say "current" instead of "target" and modal text refers to "this workspace" */
		isDeployment?: boolean
		/**
		 * Folder default permissioned_as for this item path — resolved by the parent via
		 * `useFolderDefaultPermissionedAs`. When set and no explicit choice has been made,
		 * the selector preselects `'custom'` with this value and shows a "folder default"
		 * badge so the user sees where the value came from.
		 */
		folderDefault?: string | undefined
	}

	let {
		targetWorkspace,
		targetValue,
		selected,
		onSelect,
		kind,
		canPreserve,
		customValue,
		isDeployment = true,
		folderDefault = undefined
	}: Props = $props()

	const isTrigger = $derived(kind === 'trigger' || isTriggerOrScheduleKind(kind))

	let label = $derived(
		isTrigger
			? 'Set the user this will be permissioned as:'
			: 'Set the user this will be run on behalf of:'
	)

	let users = $state<User[]>([])
	let usersLoaded = $state(false)
	let modalOpen = $state(false)
	let searchQuery = $state('')

	async function loadUsers() {
		if (usersLoaded) return
		try {
			users = await UserService.listUsers({ workspace: targetWorkspace })
		} catch {
			users = []
		}
		usersLoaded = true
	}

	// Fetch users eagerly so we can resolve usernames for display
	loadUsers()

	/** Resolve a value to a display name, always showing u/username format */
	function resolveDisplayName(value: string | undefined): string | undefined {
		if (!value) return undefined
		if (value.startsWith('u/') || value.startsWith('g/')) return value
		const username = users.find((u) => u.email === value)?.username
		return username ? `u/${username}` : value
	}

	let targetDisplayName = $derived(resolveDisplayName(targetValue))
	let customDisplayName = $derived(resolveDisplayName(customValue))
	let myDisplayName = $derived($userStore?.username ? `u/${$userStore.username}` : undefined)

	let activeUsers = $derived(users.filter((u) => !u.disabled))
	let filteredUsers = $derived(
		searchQuery
			? activeUsers.filter((u) => {
					const q = searchQuery.toLowerCase()
					return (
						u.username.toLowerCase().includes(q) ||
						u.email.toLowerCase().includes(q) ||
						(u.name?.toLowerCase()?.includes(q) ?? false)
					)
				})
			: activeUsers
	)

	// Preselect "target" when available and user has permission to preserve.
	// When no target is set but a folder default is, preselect that as a custom value.
	// We track `users.length` so the effect re-runs once the async user list loads.
	$effect(() => {
		if (selected !== undefined || !canPreserve) return
		if (targetValue) {
			onSelect('target')
		} else if (folderDefault && users.length > 0) {
			const username = folderDefault.startsWith('u/') ? folderDefault.slice(2) : folderDefault
			const email = users.find((u) => u.username === username)?.email ?? folderDefault
			onSelect('custom', { email, permissionedAs: folderDefault })
		}
	})

	const isFolderDefaultSelected = $derived(
		selected === 'custom' && folderDefault !== undefined && customValue === folderDefault
	)

	function openModal() {
		loadUsers()
		searchQuery = ''
		modalOpen = true
	}

	function selectUser(user: User) {
		onSelect('custom', { email: user.email, permissionedAs: `u/${user.username}` })
		modalOpen = false
	}

	let selectedDisplayName = $derived.by(() => {
		if (selected === 'target') return targetDisplayName
		if (selected === 'me') return myDisplayName
		if (selected === 'custom') return customDisplayName
		return undefined
	})
</script>

<MeltPopover placement="bottom" on:openChange={(e) => e.detail && loadUsers()}>
	{#snippet trigger()}
		<span class="inline-flex items-center gap-1">
			<UserCog class="w-4 h-4 {selected ? 'text-green-500' : 'text-yellow-500'}" />
			{#if selectedDisplayName}
				<span class="text-xs truncate max-w-24">{selectedDisplayName}</span>
			{/if}
			{#if isFolderDefaultSelected}
				<span
					class="text-[10px] px-1 py-0.5 rounded bg-blue-50 dark:bg-blue-950/50 text-blue-700 dark:text-blue-300 border border-blue-200 dark:border-blue-900"
					title="From this folder's default_permissioned_as rule"
				>
					folder default
				</span>
			{/if}
		</span>
	{/snippet}
	{#snippet content({ close: closePopover })}
		<div class="p-3 flex flex-col gap-2 min-w-48">
			<div class="text-xs font-medium text-secondary mb-1">{label}</div>
			<!-- Target option -->
			{#if targetValue}
				<button
					class="flex items-center gap-2 px-2 py-1.5 rounded text-left text-xs hover:bg-surface-hover {!canPreserve
						? 'opacity-50 cursor-not-allowed'
						: ''}"
					disabled={!canPreserve}
					onclick={() => onSelect('target')}
				>
					<Check class="w-3 h-3 {selected === 'target' ? 'opacity-100' : 'opacity-0'}" />
					<span class="truncate max-w-40">{targetDisplayName}</span>
					<span class="text-xs text-tertiary">{isDeployment ? '(target)' : '(current)'}</span>
				</button>
			{/if}
			<!-- Me option -->
			<button
				class="flex items-center gap-2 px-2 py-1.5 rounded text-left text-xs hover:bg-surface-hover"
				onclick={() => onSelect('me')}
			>
				<Check class="w-3 h-3 {selected === 'me' ? 'opacity-100' : 'opacity-0'}" />
				<span class="truncate max-w-40">{myDisplayName}</span>
				<span class="text-xs text-tertiary">(me)</span>
			</button>
			<!-- Custom / Pick from workspace -->
			<button
				class="flex items-center gap-2 px-2 py-1.5 rounded text-left text-xs hover:bg-surface-hover {!canPreserve
					? 'opacity-50 cursor-not-allowed'
					: ''}"
				disabled={!canPreserve}
				onclick={() => {
					closePopover()
					openModal()
				}}
			>
				{#if selected === 'custom' && customDisplayName}
					<Check class="w-3 h-3 opacity-100" />
					<span class="truncate max-w-40">{customDisplayName}</span>
					<span class="text-xs text-tertiary">(custom)</span>
				{:else}
					<Check class="w-3 h-3 opacity-0" />
					<Users class="w-3 h-3 text-tertiary" />
					<span>Pick from workspace&hellip;</span>
				{/if}
			</button>
		</div>
	{/snippet}
</MeltPopover>

<!-- User selection modal -->
<Modal title="Select a user" bind:open={modalOpen} kind="X">
	<div class="flex flex-col gap-4">
		<div class="text-xs text-secondary">
			{#if isTrigger}
				Choose the user this trigger will be permissioned as {isDeployment
					? 'in the target workspace'
					: 'in this workspace'}. The selected user's permissions will be used when the trigger
				fires.
			{:else}
				Choose the user this {kind} will run on behalf of {isDeployment
					? 'in the target workspace'
					: 'in this workspace'}. The selected user's permissions will be used when executing.
			{/if}
			<a
				href="https://www.windmill.dev/docs/core_concepts/roles_and_permissions"
				target="_blank"
				rel="noopener noreferrer"
				class="text-blue-500 hover:underline inline-flex items-center gap-0.5"
			>
				Learn more
				<ExternalLink class="w-3 h-3" />
			</a>
		</div>

		<TextInput bind:value={searchQuery} inputProps={{ placeholder: 'Search users...' }} />

		<div class="max-h-60 overflow-y-auto border rounded">
			{#each filteredUsers as user (user.email)}
				<button
					class="w-full flex items-center gap-3 px-3 py-2 text-left text-sm hover:bg-surface-hover border-b last:border-b-0"
					onclick={() => selectUser(user)}
				>
					<div class="flex flex-col min-w-0">
						<span class="font-medium truncate">u/{user.username}</span>
						<span class="text-xs text-tertiary truncate">{user.email}</span>
					</div>
					{#if selected === 'custom' && (customValue === `u/${user.username}` || customValue === user.email)}
						<Check class="w-4 h-4 text-green-500 ml-auto flex-shrink-0" />
					{/if}
				</button>
			{:else}
				<div class="px-3 py-4 text-sm text-tertiary text-center">
					{#if !usersLoaded}
						Loading users&hellip;
					{:else}
						No users found
					{/if}
				</div>
			{/each}
		</div>
	</div>
</Modal>
