<svelte:head>
	<title>ObjectId Tools</title>
</svelte:head>

<script lang="js">
	/**
	 * @typedef {{
	 * 	timestamp:number,
	 * 	date:string,
	 * 	dateLocal:string,
	 * 	randomPart:string,
	 * 	counterPart:string}} ParsedObjectId
	 */

	// ObjectId: 4 bytes timestamp | 5 bytes random | 3 bytes counter
	function generateObjectId() {
		const timestamp = Math.floor(Date.now() / 1000);
		const tsHex = timestamp.toString(16).padStart(8, '0');

		const random = Array.from({ length: 10 }, () =>
			Math.floor(Math.random() * 16).toString(16)
		).join('');

		const counter = Math.floor(Math.random() * 0xffffff);
		const counterHex = counter.toString(16).padStart(6, '0');

		return tsHex + random + counterHex;
	}

	/**
	 * @param {string} id
	 * @returns {ParsedObjectId | { error: string }}
	 */
	function parseObjectId(id) {
		if (!/^[0-9a-fA-F]{24}$/.test(id)) {
			return { error: 'Invalid ObjectId: must be 24 hex characters' };
		}
		const tsHex = id.slice(0, 8);
		const randomPart = id.slice(8, 18);
		const counterPart = id.slice(18, 24);
		const timestampSec = parseInt(tsHex, 16);
		const date = new Date(timestampSec * 1000);

		return {
			timestamp: timestampSec,
			date: date.toISOString(),
			dateLocal: date.toLocaleString(),
			randomPart,
			counterPart
		};
	}

	let generated = $state(generateObjectId());
	let parseInput = $state('');

	const parseResult = $derived(parseInput.trim().length === 24 ? parseObjectId(parseInput.trim()) : null);
	const parsed = $derived(parseResult && !('error' in parseResult) ? parseResult : null);
	const parseError = $derived(parseResult && 'error' in parseResult ? parseResult.error : '');

	function onGenerate() {
		generated = generateObjectId();
	}

	import { copy as copyToClipboard } from '$lib/clipboard.js';
</script>

<h1>ObjectId</h1>

<h2>Generate</h2>
<p>
	<code>
		<span class="ts" title="timestamp">{generated.slice(0, 8)}</span><span
			class="rnd" title="random">{generated.slice(8, 18)}</span><span
			class="cnt" title="counter">{generated.slice(18, 24)}</span>
	</code>
	&nbsp;
	<button onclick={onGenerate}>New</button>
	<button onclick={() => copyToClipboard(generated)}>Copy</button>
</p>
<p class="legend">
	<span class="ts">■ timestamp</span>
	<span class="rnd">■ random</span>
	<span class="cnt">■ counter</span>
	&nbsp;-&nbsp;
	Generated: {new Date(parseInt(generated.slice(0, 8), 16) * 1000).toLocaleString()}
</p>

<h2>Parse</h2>
<p>
	<input
		type="text"
		size="26"
		placeholder="507f1f77bcf86cd799439011"
		bind:value={parseInput}
		spellcheck="false"
	/>
</p>

{#if parseError}
	<p class="error">{parseError}</p>
{/if}

{#if parsed}
	<table>
		<tbody>
			<tr><td>Timestamp (unix)</td><td><code>{parsed.timestamp}</code></td><td><button onclick={() => copyToClipboard(String(parsed.timestamp))}>copy</button></td></tr>
			<tr><td>Date (UTC)</td><td><code>{parsed.date}</code></td><td><button onclick={() => copyToClipboard(parsed.date)}>copy</button></td></tr>
			<tr><td>Date (local)</td><td><code>{parsed.dateLocal}</code></td><td><button onclick={() => copyToClipboard(parsed.dateLocal)}>copy</button></td></tr>
			<tr><td>Random part</td><td><code class="rnd">{parsed.randomPart}</code></td><td><button onclick={() => copyToClipboard(parsed.randomPart)}>copy</button></td></tr>
			<tr><td>Counter</td><td><code class="cnt">{parsed.counterPart}</code> ({parseInt(parsed.counterPart, 16)})</td><td><button onclick={() => copyToClipboard(parsed.counterPart)}>copy</button></td></tr>
		</tbody>
	</table>
{/if}

<style>
	.ts  { color: var(--red); }
	.rnd { color: var(--blue); }
	.cnt { color: var(--green); }

	.legend {
		font-size: 12px;
		color: var(--fg-dim);
	}

	.error {
		color: var(--err);
	}
</style>
