<script>
	import { tick } from 'svelte';
	import { debounce, escapeHTML, escapeRegExp, strHashHSL, datetime_datestr, datetime_timestr, isInViewport, filter_pattern, filter_test, filter_mark, filter_snippets_lines } from '$lib/util.js';
	import { get_api_labs } from '$lib/backend.js';

	export let data;

	let query = '', pattern = null, selection = null, all_reports = decorate(data.reports);
	let debounced_pattern = debounce((/*query*/) => (pattern = query ? filter_pattern(escapeHTML(query)) : null), 200);

	$: debounced_pattern(query);	// argument `query` is for reactivity hinting only

	function decorate(xs) {
		return xs.map(x => Object.assign({ _content: escapeHTML(Object.values(x).join('\x00')), _ts: new Date(x.time_collected) }, x));
	}

	function abstract(body) {
		let re = /^(\w.*?)[ \t]{2,}\S.*?[ \t]{2,}/gm, m, res = [];
		while(m = re.exec(body)) res.push(m[1]);
		re = /^\*[ ]+((?:.+?) REPORT) =>/gm;
		while(m = re.exec(body)) res.push(m[1]);
		return res;
	}

	async function loadmore(evt, factor = 1.5) {
		if(loadmore.loading) return;
		loadmore.loading = true;
		try {
			let reports = await get_api_labs({ mrn: data.mrn, omega: 'T-' + (data.offset + 1), alpha: 'T-' + (data.offset += (loadmore.limit = (factor*loadmore.limit)|0)) });
			Array.prototype.push.apply(all_reports, decorate(reports));
			all_reports = all_reports;	// reactivity hint
		} finally {
			loadmore.loading = false;
		}
	}
	loadmore.loading = false;
	loadmore.limit = 30;

	(async function loadinit(target = 16, requests = 4) {
		for(let i = 0; (i < requests) && (all_reports.length < target); ++i) await loadmore();
	})();

	const observer = new IntersectionObserver((entries) => { if((!query) && (entries[0].isIntersecting)) loadmore(null); }, { root: null, rootMargin: '0px', threshold: 0.5 });
	let bottom = null;
	$: {
		observer.disconnect();
		if(bottom) observer.observe(bottom);
	}

	let reportlist;
	async function scroll(selection) {
		if(selection) {
			await tick();
			const el = reportlist.querySelector('.active');
			if((el) && (!isInViewport(el, true))) el.scrollIntoView({ block: 'center' });
		} else {
			const items = reportlist.children;
			for(let i = 0, el; i < items.length; ++i) if(isInViewport(el = items[i])) {
				await tick();
				el.scrollIntoView({ block: 'start' });
				break;
			}
		}
	}
	$: if(reportlist) scroll(selection);
</script>

<svelte:head>
	<title>Labs</title>
</svelte:head>

{#if selection}
	<div class="halfpane rightpane">
		<nav class="navbar bg-body-secondary">
			<div class="container-fluid">
				<span class="navbar-brand">{datetime_datestr(selection._ts)}@{datetime_timestr(selection._ts)} {selection.accession} {selection.specimen}</span>
				<button type="button" class="btn btn-outline-light" on:click={() => selection = null}>❌</button>
			</div>
		</nav>
		<div class="container-fluid report">{@html pattern ? filter_mark(pattern, escapeHTML(selection.body)) : escapeHTML(selection.body)}</div>
	</div>
{/if}
<div class={selection ? 'halfpane leftpane' : ''}>
	<div class="card {selection ? '' : 'mb-3 shadow'}">
		<nav class="navbar bg-body-tertiary">
			<form class="container-fluid">
				<div class="input-group">
					<span class="input-group-text">Labs</span>
					<input type="text" class="form-control" placeholder="Filter..." bind:value={query}>
					{#if query}<button type="button" class="btn btn-outline-secondary" on:click={() => query = ''}>❌</button>{/if}
				</div>
			</form>
		</nav>
		<ul class="list-group list-group-flush" bind:this={reportlist}>
			{#if pattern}
				{#each all_reports as row}
					{#if filter_test(pattern, row._content)}
						{@const abs = abstract(row.body)}
						<li class="list-group-item" class:active={(selection) && (selection.uid == row.uid)} on:click={() => selection = selection !== row ? row : null}>
							<div class="singleline" style="font-weight: bold;">{datetime_datestr(row._ts)}@{datetime_timestr(row._ts)} {row.accession} {row.specimen}</div>
							{#if abs.length > 0}<div class="abstract singleline">{abs.join(', ')}</div>{/if}
							<div class="snippets">{#each filter_snippets_lines(pattern, escapeHTML(row.body), undefined, 3, 6) as match}<div>{@html match}</div>{/each}</div>
						</li>
					{/if}
				{/each}
			{:else}
				{#each all_reports as row}
					{@const abs = abstract(row.body)}
					<li class="list-group-item" class:active={(selection) && (selection.uid == row.uid)} on:click={() => selection = selection !== row ? row : null}>
						<div class="singleline" style="font-weight: bold;">{datetime_datestr(row._ts)}@{datetime_timestr(row._ts)} {row.accession} {row.specimen}</div>
						{#if abs.length > 0}<div class="abstract singleline">{abs.join(', ')}</div>{/if}
					</li>
				{/each}
			{/if}
			<li class="list-group-item" style="padding: 0;" bind:this={bottom}>{#if loadmore.loading}<button type="button" class="btn btn-primary w-100" disabled>Loading...</button>{:else}<button type="button" class="btn btn-primary w-100" on:click={loadmore}>Load more</button>{/if}</li>
		</ul>
	</div>
</div>

<style>
	:global(div.snippets mark, div.report mark) {
		padding: 0;
		font-weight: bold;
	}
	@media (prefers-reduced-motion: no-preference) {
		:root {
			scroll-behavior: auto;
		}
	}
	.navbar {
		position: sticky;
		z-index: 1020;
		top: 3.5rem;
	}
	.leftpane {
		display: none;
	}
	li.active {
		scroll-margin-top: 3.5rem;
	}
	div.singleline {
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
	.abstract {
		font-size: 0.8em;
	}
	.snippets {
		font-family: monospace;
	}
	.report {
		font-family: monospace;
		white-space: pre-wrap;
	}
	@media screen and (min-width: 720px) {
		.halfpane {
			position: absolute;
			top: 3.5rem;
			bottom: 0;
			width: 50%;
			overflow: auto;
		}
		.leftpane {
			display: block;
			width: 33%;
			left: 0;
			z-index: -1;
			direction: rtl;
		}
		.leftpane > * {
			direction: ltr;
		}
		.rightpane {
			width: 67%;
			right: 0;
			box-shadow: var(--bs-box-shadow);
		}
		.halfpane .navbar {
			top: 0;
		}
	}
</style>
