<script lang="ts">
	import { untrack } from 'svelte'
	import TableCustom from '$lib/components/TableCustom.svelte'
	import { displayDate } from '$lib/utils'
	import { UserService, type TruncatedToken } from '$lib/gen'
	import { sendUserToast } from '$lib/toast'
	import CreateToken from './CreateToken.svelte'
	import EditTokenScopesModal from './EditTokenScopesModal.svelte'
	import Button from '../common/button/Button.svelte'
	import Badge from '../common/badge/Badge.svelte'
	import Alert from '../common/alert/Alert.svelte'
	import { Pen, Trash } from 'lucide-svelte'

	// --- Props ---
	interface Props {
		showMcpMode?: boolean
		openWithMcpMode?: boolean
		defaultNewTokenLabel?: string
		defaultNewTokenWorkspace?: string
		scopes?: string[]
		onTokenCreated: (token: string) => void
	}

	let {
		showMcpMode = false,
		openWithMcpMode = false,
		defaultNewTokenLabel,
		defaultNewTokenWorkspace,
		scopes,
		onTokenCreated
	}: Props = $props()

	// --- Local State ---
	let tokens = $state<TruncatedToken[]>([])
	let tokenPage = $state(1)
	let newTokenLabel = $state<string | undefined>(untrack(() => defaultNewTokenLabel))
	let editingToken = $state<
		| {
				prefix: string
				label: string | undefined
				scopes: string[] | undefined
				workspaceId: string | undefined
		  }
		| undefined
	>(undefined)
	let editModalOpen = $state(false)

	$effect(() => {
		listTokens()
	})

	// Mirror of the canonical `is_user_token` in backend/windmill-common/src/auth.rs.
	// When updating this filter, also update that function and the SQL `WHERE`
	// mirror in `update_token_label` (backend/windmill-api-users/src/users.rs).
	function isUserToken(label: string | undefined): boolean {
		if (!label) return true
		return (
			label !== 'session' &&
			!label.toLowerCase().startsWith('ephemeral') &&
			label !== 'debugger-token' &&
			!label.startsWith('mcp-oauth-')
		)
	}

	function daysUntilExpiration(expiration: string | undefined): number | null {
		if (!expiration) return null
		const today = new Date()
		today.setHours(0, 0, 0, 0)
		const exp = new Date(expiration)
		exp.setHours(0, 0, 0, 0)
		return Math.round((exp.getTime() - today.getTime()) / 86400000)
	}

	function expirationBadge(
		expiration: string | undefined,
		label: string | undefined
	): {
		color: 'red' | 'orange' | 'yellow' | 'gray'
		text: string
	} | null {
		if (!isUserToken(label)) return null
		const days = daysUntilExpiration(expiration)
		if (days === null) return null
		if (days < 0) return { color: 'red', text: 'Expired' }
		if (days === 0) return { color: 'red', text: 'Expires today' }
		if (days === 1) return { color: 'orange', text: 'Expires tomorrow' }
		if (days <= 7) return { color: 'orange', text: `Expires in ${days}d` }
		if (days <= 30) return { color: 'yellow', text: `Expires in ${days}d` }
		return null
	}

	let expiringSoonCount = $derived(
		tokens.filter((t) => {
			if (!isUserToken(t.label)) return false
			const days = daysUntilExpiration(t.expiration)
			return days !== null && days >= 0 && days <= 7
		}).length
	)

	function handleTokenCreated(token: string) {
		onTokenCreated(token)
		listTokens()
	}

	async function handleDeleteClick(tokenPrefix: string) {
		await UserService.deleteToken({ tokenPrefix })
		sendUserToast('Successfully deleted token')
		listTokens()
	}

	function handleEditClick(
		tokenPrefix: string,
		tokenLabel: string | undefined,
		tokenScopes: string[] | undefined,
		tokenWorkspaceId: string | undefined
	) {
		editingToken = {
			prefix: tokenPrefix,
			label: tokenLabel,
			scopes: tokenScopes,
			workspaceId: tokenWorkspaceId
		}
		editModalOpen = true
	}

	async function listTokens(): Promise<void> {
		tokens = await UserService.listTokens({
			excludeEphemeral: true,
			page: tokenPage,
			perPage: 100
		})
	}

	function handleNextPage() {
		tokenPage += 1
		listTokens()
	}

	function handlePreviousPage() {
		tokenPage -= 1
		listTokens()
	}
</script>

<div class="flex flex-col p-4 border border-border-light rounded-md">
	<h2 class="text-emphasis text-sm font-semibold mb-1">Tokens</h2>
	<div class="text-xs text-secondary mb-2">
		Authenticate to the Windmill API with access tokens.
	</div>
	{#if expiringSoonCount > 0}
		<div class="mb-2">
			<Alert
				type="warning"
				title="{expiringSoonCount} token{expiringSoonCount > 1 ? 's' : ''} expiring within 7 days"
				size="xs"
			/>
		</div>
	{/if}
	<CreateToken
		{showMcpMode}
		{openWithMcpMode}
		bind:newTokenLabel
		{defaultNewTokenWorkspace}
		{scopes}
		onTokenCreated={handleTokenCreated}
	/>
	<div class="overflow-auto grow min-h-64 max-h-2/3">
		<TableCustom>
			{#snippet headerRow()}
				<tr>
					<th>Prefix</th>
					<th>Label</th>
					<th>Expiration</th>
					<th>Scopes</th>
					<th></th>
				</tr>
			{/snippet}
			{#snippet body()}
				<tbody>
					{#if tokens && tokens.length > 0}
						{#each tokens as { token_prefix, expiration, label, scopes, workspace_id, read_only } (token_prefix)}
							{@const badge = expirationBadge(expiration, label)}
							<tr>
								<td class="w-32 text-xs text-primary">{token_prefix}****</td>
								<td class="min-w-0 max-w-32 truncate text-xs text-primary">{label ?? ''}</td>
								<td class="w-40 whitespace-nowrap text-xs text-secondary">
									<div class="flex items-center gap-1.5">
										{displayDate(expiration ?? '')}
										{#if badge}
											<Badge color={badge.color} small>{badge.text}</Badge>
										{/if}
									</div>
								</td>
								<td
									class="min-w-0 max-w-48 truncate text-xs text-secondary"
									title={scopes?.join(', ') ?? ''}
								>
									<div class="flex items-center gap-1.5 truncate">
										{#if read_only}
											<Badge color="blue" small>Read-only</Badge>
										{/if}
										<span class="truncate">{scopes?.join(', ') ?? ''}</span>
									</div>
								</td>
								<td class="w-24 text-center">
									<div class="flex items-center justify-center gap-1">
										<Button
											variant="subtle"
											title="Edit token"
											on:click={() =>
												handleEditClick(
													token_prefix,
													label ?? undefined,
													scopes ?? undefined,
													workspace_id ?? undefined
												)}
											size="xs"
											startIcon={{ icon: Pen }}
											iconOnly
										/>
										<Button
											variant="subtle"
											destructive
											on:click={() => handleDeleteClick(token_prefix)}
											size="xs"
											startIcon={{ icon: Trash }}
											iconOnly
										/>
									</div>
								</td>
							</tr>
						{/each}
					{:else if tokens && tokens.length === 0}
						<tr class="px-6">
							<td class="text-secondary italic text-2xs"> There are no tokens yet</td>
						</tr>
					{:else}
						<tr><td class="text-secondary text-xs">Loading...</td></tr>
					{/if}
				</tbody>
			{/snippet}
		</TableCustom>
		<div class="flex flex-row-reverse gap-2 w-full mt-2">
			{#if tokens?.length == 100}
				<Button variant="subtle" size="xs" on:click={handleNextPage}>Next</Button>
			{/if}
			{#if tokenPage > 1}
				<Button variant="subtle" size="xs" on:click={handlePreviousPage}>Previous</Button>
			{/if}
		</div>
	</div>
</div>

<EditTokenScopesModal
	bind:open={editModalOpen}
	tokenPrefix={editingToken?.prefix}
	initialLabel={editingToken?.label}
	labelEditable={isUserToken(editingToken?.label)}
	initialScopes={editingToken?.scopes}
	tokenWorkspaceId={editingToken?.workspaceId}
	onSaved={listTokens}
/>
