<script lang="ts">
	import UpObjectDisplay from './display/UpObject.svelte';
	import Selector, { type SelectorValue } from './utils/Selector.svelte';
	import IconButton from './utils/IconButton.svelte';
	import api from '$lib/api';
	import { i18n } from '../i18n';
	import { type UpObject, type UpEntry, Query } from '@upnd/upend';
	import { Any } from '@upnd/upend/query';
	import type { Readable } from 'svelte/store';
	import { ATTR_OF } from '@upnd/upend/constants';
	import { createEventDispatcher } from 'svelte';
	import LabelBorder from './utils/LabelBorder.svelte';
	import Icon from '$lib/components/utils/Icon.svelte';

	const dispatch = createEventDispatcher();

	export let entity: Readable<UpObject | undefined>;

	let adding = false;
	let typeSelector: Selector;

	$: if (adding && typeSelector) typeSelector.focus();

	let types: Array<{ address: string; entry: UpEntry; required: UpEntry | undefined }> = [];
	$: updateTypes($entity?.attr[`~${ATTR_OF}`] || []);

	async function updateTypes(entries: UpEntry[]) {
		types = [];
		const query = await api.query(
			Query.matches(
				entries.flatMap((e) => [`@${e.address}`, `@${e.entity}`]),
				Any,
				Any
			)
		);

		types = entries
			.map((entry) => ({
				address: entry.entity,
				entry,
				required: query.getObject(entry.address)?.attr['TYPE_REQUIRED']?.[0]
			}))
			.sort((a, b) => {
				const aLabel = query.getObject(a.address)?.identify().join('/');
				const bLabel = query.getObject(b.address)?.identify().join('/');
				if (aLabel && bLabel) {
					return aLabel.localeCompare(bLabel);
				}
				return a.address.localeCompare(b.address);
			});
	}

	async function add(ev: CustomEvent<SelectorValue>) {
		if (!$entity || ev.detail.t !== 'Attribute') {
			return;
		}

		await api.putEntry({
			entity: {
				t: 'Attribute',
				c: ev.detail.name
			},
			attribute: ATTR_OF,
			value: { t: 'Address', c: $entity.address }
		});
		dispatch('change');
	}

	async function remove(entry: UpEntry) {
		if (!$entity) return;

		let really = confirm(
			$i18n.t('Really remove "{{attributeName}}" from "{{typeName}}"?', {
				attributeName: (await api.addressToComponents(entry.entity)).c,
				typeName: $entity.identify().join('/')
			}) || ''
		);

		if (really) {
			await api.deleteEntry(entry.address);
			dispatch('change');
		}
	}

	async function setRequired(entry: UpEntry, required: boolean) {
		if (required) {
			await api.putEntry({
				entity: entry.address,
				attribute: 'TYPE_REQUIRED',
				value: { t: 'Null', c: null }
			});
		} else {
			const requiredAddress = types.find((t) => t.entry === entry)?.required?.address;
			if (requiredAddress) {
				await api.deleteEntry(requiredAddress);
			}
		}
		dispatch('change');
	}
</script>

{#if types.length || $entity?.attr['~IN']?.length}
	<LabelBorder hide={types.length === 0}>
		<span slot="header"><Icon plain name="list-check" /> {$i18n.t('Type Attributes')}</span>
		{#if adding}
			<div class="selector">
				<Selector
					bind:this={typeSelector}
					types={['Attribute', 'NewAttribute']}
					on:input={add}
					placeholder={$i18n.t('Assign an attribute to this type...') || ''}
					on:focus={(ev) => {
						if (!ev.detail) adding = false;
					}}
				/>
			</div>
		{/if}
		<div class="body">
			<div class="attributes">
				{#each types as type}
					<div class="label">
						<UpObjectDisplay address={type.address} link />
					</div>
					<button
						class:required={type.required}
						on:click={() => setRequired(type.entry, !type.required)}
					>
						{#if type.required}
							<Icon plain name="lock" /> {$i18n.t('Required')}
						{:else}
							<Icon plain name="lock-open" /> {$i18n.t('Optional')}
						{/if}
					</button>
					<div class="controls">
						<IconButton name="x-circle" on:click={() => remove(type.entry)} />
					</div>
				{:else}
					<div class="no-attributes">
						{$i18n.t('No attributes assigned to this type.')}
					</div>
				{/each}
				<div class="add-button">
					<IconButton outline small name="plus-circle" on:click={() => (adding = true)} />
				</div>
			</div>
		</div>
	</LabelBorder>
{/if}

<style lang="scss">
	.body {
		display: flex;
		align-items: start;

		.attributes {
			flex-grow: 1;
		}
	}

	.attributes {
		display: grid;
		grid-template-columns: repeat(3, minmax(0, auto));
		gap: 0.25em 1em;
	}

	.controls {
		display: flex;
		align-items: center;
		justify-content: flex-end;
	}

	button {
		border: none;
		background: none;
		cursor: pointer;
		padding: 0;
		margin: 0;
		color: var(--color-text);
		opacity: 0.8;
		transition: opacity 0.2s ease-in-out;
		&.required {
			opacity: 1;
		}
		&:hover {
			opacity: 1;
		}
	}

	.add-button {
		grid-column: 1 / span 3;
		display: flex;
		flex-direction: column;
	}

	.selector {
		width: 100%;
		margin-bottom: 0.5rem;
	}

	.no-attributes {
		opacity: 0.66;
	}
</style>
