<script lang="ts" module>
	export interface BannerAPI {
		/** opens the banner */
		open: () => void;
		/** closes the banner */
		close: () => void;
		/** returns whether the banner is open */
		isOpen: () => boolean;
		/** disables banner controls */
		disable: () => void;
		/** enables banner controls */
		enable: () => void;
		/** returns whether the banner controls are disabled */
		isDisabled: () => boolean;
		/** freezes banner state (cannot be dismissed) */
		freeze: () => void;
		/** unfreezes banner state */
		unfreeze: () => void;
		/** returns whether the banner is frozen */
		isFrozen: () => boolean;
	}

	type BannerControlButton = {
		label?: string;
		class?: ClassValue;
		/** if framed is false, FramelessButton is used instead of Button */
		framed?: boolean;
		action?: (banner: BannerAPI) => void;
	};

	export type BannerControls = {
		accept?: BannerControlButton | null;
		decline?: BannerControlButton | null;
		moreInfo?:
			| (Omit<BannerControlButton, 'framed'> & {
					type?: 'link' | 'framed' | 'frameless';
					href?: string;
			  })
			| null;
		dismiss?: Omit<BannerControlButton, 'framed'> | null;
	};

	const defaultBannerControls: BannerControls = {
		accept: {
			label: 'Accept',
			framed: true,
			action: (banner) => banner.close()
		},
		decline: {
			label: 'Decline',
			framed: false,
			action: (banner) => banner.close()
		},
		dismiss: {
			action: (banner) => banner.close()
		}
	};

	const mergeBannerControls = (
		defaults: BannerControls,
		overrides: BannerControls | null | undefined
	): BannerControls => {
		if (!overrides) return defaults;

		return {
			accept: overrides.accept ?? defaults.accept,
			decline: overrides.decline ?? defaults.decline,
			moreInfo: overrides.moreInfo ?? defaults.moreInfo,
			dismiss: overrides.dismiss ?? defaults.dismiss
		};
	};
</script>

<script lang="ts">
	import type { Snippet } from 'svelte';
	import type { ClassValue } from 'svelte/elements';
	import Link from './Link.svelte';
	import Button from './Button.svelte';
	import FramelessButton from './FramelessButton.svelte';
	import { X } from 'phosphor-svelte';

	/**
	 * Banner provides a simple component to display a banner message with
	 * additional tooling for easy control integration. Controls tools are
	 * geared toward cookie consent, announcements, and similar use cases.
	 */

	interface Props {
		title?: string;
		open?: boolean;
		disabled?: boolean;
		frozen?: boolean;
		position?: 'top' | 'bottom';
		controls?: BannerControls | null;
		/** if true, frameless buttons and links are inverted (default: true) */
		invertFrameless?: boolean;
		class?: ClassValue | null;
		onaccept?: (banner: BannerAPI) => void;
		ondecline?: (banner: BannerAPI) => void;
		children?: Snippet;
	}

	let {
		title,
		open = $bindable(false),
		disabled = $bindable(false),
		frozen = $bindable(false),
		position,
		controls: rawControls = defaultBannerControls,
		invertFrameless = true,
		class: classValue,
		onaccept,
		ondecline,
		children
	}: Props = $props();

	const controls = $derived(mergeBannerControls(defaultBannerControls, rawControls));

	const api: BannerAPI = {
		open: () => (open = true),
		close: () => (open = false),
		isOpen: () => open,
		disable: () => (disabled = true),
		enable: () => (disabled = false),
		isDisabled: () => disabled,
		freeze: () => (frozen = true),
		unfreeze: () => (frozen = false),
		isFrozen: () => frozen
	};

	const handleAccept = () => {
		controls.accept?.action?.(api);
		onaccept?.(api);
	};
	const handleDecline = () => {
		controls.decline?.action?.(api);
		ondecline?.(api);
	};
</script>

{#if open}
	<div
		class={[
			'fixed left-0 z-50 w-screen px-8 py-6',
			position === 'top' ? 'top-0' : 'bottom-0',
			'bg-sui-secondary-800 text-sui-background',
			classValue
		]}
	>
		{#if title || controls?.dismiss}
			<div class="mb-2 flex items-center justify-between gap-4">
				{#if title}
					<h3 class="mb-2 text-lg font-semibold">{title}</h3>
				{/if}

				{#if controls?.dismiss}
					<FramelessButton
						inverted={invertFrameless}
						onclick={() => controls.dismiss?.action?.(api)}
						class="ml-auto"
					>
						{#if controls.dismiss?.label}
							{controls.dismiss.label}
						{/if}
						<X class="mt-0.5" size="1.2rem" weight="bold" />
					</FramelessButton>
				{/if}
			</div>
		{/if}

		{@render children?.()}

		{#if controls !== null}
			<div class="mt-4 flex flex-wrap justify-between gap-4">
				<!-- More info button/link -->
				{#if controls.moreInfo}
					{#if controls.moreInfo.type === 'link' && controls.moreInfo.href}
						<Link
							href={controls.moreInfo.href}
							class={controls.moreInfo.class}
							inverted={invertFrameless}
						>
							{controls.moreInfo.label || 'More Info'}
						</Link>
					{:else if controls.moreInfo.type === 'framed'}
						<Button
							class={controls.moreInfo.class}
							onclick={() => controls.moreInfo?.action?.(api)}
						>
							{controls.moreInfo.label || 'More Info'}
						</Button>
					{:else}
						<FramelessButton
							class={controls.moreInfo.class}
							onclick={() => controls.moreInfo?.action?.(api)}
							inverted={invertFrameless}
						>
							{controls.moreInfo.label || 'More Info'}
						</FramelessButton>
					{/if}
				{/if}

				<div class="ml-auto flex justify-end gap-4">
					<!-- Decline button -->
					{@render buttonControl(controls.decline, handleDecline, false)}
					<!-- Accept button -->
					{@render buttonControl(controls.accept, handleAccept, true)}
				</div>
			</div>
		{/if}
	</div>
{/if}

{#snippet buttonControl(
	button: BannerControlButton | undefined | null,
	handleClick: (banner: BannerAPI) => void,
	framedDefault: boolean
)}
	{#if button}
		{#if button.framed || framedDefault}
			<Button class={button.class} onclick={() => handleClick(api)}>
				{button.label || 'Button'}
			</Button>
		{:else}
			<FramelessButton
				class={button.class}
				onclick={() => handleClick(api)}
				inverted={invertFrameless}
			>
				{button.label || 'Button'}
			</FramelessButton>
		{/if}
	{/if}
{/snippet}
