<!--
  RegexTestBox — input a sample log line, instantly see whether the
  current pattern matches and what subgroups it captures. Used on
  the log-scan-rules new and edit pages so operators can iterate on
  a regex without saving + waiting for a real container line.

  Pure client-side evaluation. JavaScript regex flavour differs from
  Go's RE2 in obscure cases — close enough for an iteration aid; the
  server-side /test endpoint is authoritative.
-->
<script lang="ts">
	import { t } from '$lib/i18n';

	interface Props {
		pattern: string;
		sample?: string;
	}

	let { pattern, sample = $bindable('') }: Props = $props();

	type Result =
		| { state: 'empty' }
		| { state: 'invalid-pattern'; error: string }
		| { state: 'no-match' }
		| { state: 'match'; full: string; captures: Array<{ name: string; value: string }> };

	const result = $derived.by((): Result => {
		if (!pattern || !sample) return { state: 'empty' };
		let re: RegExp;
		try {
			re = new RegExp(pattern);
		} catch (e) {
			return { state: 'invalid-pattern', error: e instanceof Error ? e.message : 'bad regex' };
		}
		const m = re.exec(sample);
		if (!m) return { state: 'no-match' };
		const captures = m.slice(1).map((value, i) => ({ name: `$${i + 1}`, value: value ?? '' }));
		return { state: 'match', full: m[0], captures };
	});
</script>

<div class="rtb">
	<label for="rtb-sample" class="sub-label">{$t('observability.regex.sampleLabel')}</label>
	<textarea
		id="rtb-sample"
		class="rtb-input"
		bind:value={sample}
		rows="3"
		spellcheck="false"
		placeholder={$t('observability.regex.placeholder')}
	></textarea>

	<div aria-live="polite" aria-atomic="true" class="rtb-feedback">
		{#if result.state === 'empty'}
			<div class="status muted">{$t('observability.regex.promptType')}</div>
		{:else if result.state === 'invalid-pattern'}
			<div class="status fail">
				<span class="tag">{$t('observability.regex.invalid')}</span>
				<span class="msg">{result.error}</span>
			</div>
		{:else if result.state === 'no-match'}
			<div class="status off">
				<span class="tag">{$t('observability.regex.noMatch')}</span>
				<span class="msg muted">{$t('observability.regex.noMatchHint')}</span>
			</div>
		{:else}
			<div class="status ok">
				<span class="tag">{$t('observability.regex.match')}</span>
				<span class="full">{result.full}</span>
			</div>
			{#if result.captures.length > 0}
				<div class="captures">
					<span class="sub-label">{$t('observability.regex.captures')}</span>
					<dl>
						{#each result.captures as c}
							<div class="cap">
								<dt>{c.name}</dt>
								<dd class="mono">{c.value || '∅'}</dd>
							</div>
						{/each}
					</dl>
				</div>
			{/if}
		{/if}
	</div>
</div>

<style>
	.rtb {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}
	.sub-label {
		font-family: var(--forge-mono);
		font-size: 0.62rem;
		font-weight: 600;
		letter-spacing: 0.14em;
		text-transform: uppercase;
		color: var(--text-secondary);
	}
	.rtb-input {
		width: 100%;
		background: var(--surface-input);
		border: 1px solid var(--border-input);
		border-radius: var(--radius-lg);
		padding: 0.55rem 0.75rem;
		color: var(--text-primary);
		font-family: var(--forge-mono);
		font-size: 0.82rem;
		line-height: 1.5;
		outline: none;
		resize: vertical;
		min-height: 4.5rem;
		transition: border-color 120ms ease, box-shadow 120ms ease;
	}
	.rtb-input:focus {
		border-color: var(--border-focus);
		box-shadow: 0 0 0 3px var(--forge-accent-soft);
	}
	.rtb-feedback {
		display: flex;
		flex-direction: column;
		gap: 0.5rem;
	}
	.status {
		display: flex;
		flex-wrap: wrap;
		align-items: center;
		gap: 0.55rem;
		padding: 0.55rem 0.7rem;
		border-radius: var(--radius-lg);
		font-size: 0.85rem;
		border: 1px solid var(--border-primary);
		background: var(--surface-card-hover);
		min-width: 0;
	}
	.status.ok {
		border-color: var(--color-success);
		background: color-mix(in srgb, var(--color-success) 8%, var(--surface-card));
	}
	.status.fail {
		border-color: var(--color-danger);
		background: color-mix(in srgb, var(--color-danger) 8%, var(--surface-card));
	}
	.status.off,
	.status.muted {
		color: var(--text-tertiary);
		font-style: italic;
	}
	.msg {
		min-width: 0;
		flex: 1 1 auto;
		word-break: break-word;
	}
	.tag {
		font-family: var(--forge-mono);
		font-weight: 700;
		font-size: 0.6rem;
		letter-spacing: 0.16em;
		padding: 0.12rem 0.4rem;
		background: var(--text-primary);
		color: var(--surface-card);
		border-radius: var(--radius-sm);
		flex: 0 0 auto;
	}
	.status.ok .tag {
		background: var(--color-success);
		color: var(--surface-card);
	}
	.status.fail .tag {
		background: var(--color-danger);
		color: var(--surface-card);
	}
	.full {
		font-family: var(--forge-mono);
		font-size: 0.82rem;
		color: var(--text-primary);
		overflow: hidden;
		text-overflow: ellipsis;
		white-space: nowrap;
		min-width: 0;
		flex: 1 1 auto;
	}
	.muted {
		color: var(--text-tertiary);
	}
	.captures {
		display: flex;
		flex-direction: column;
		gap: 0.35rem;
	}
	.captures dl {
		display: flex;
		flex-wrap: wrap;
		gap: 0.4rem;
		margin: 0;
	}
	.cap {
		display: inline-flex;
		align-items: center;
		gap: 0.35rem;
		padding: 0.2rem 0.5rem;
		background: var(--surface-card);
		border: 1px solid var(--border-primary);
		border-radius: var(--radius-md);
	}
	.cap dt {
		font-family: var(--forge-mono);
		font-size: 0.65rem;
		font-weight: 700;
		letter-spacing: 0.1em;
		color: var(--text-secondary);
		margin: 0;
	}
	.cap dd {
		margin: 0;
		font-size: 0.8rem;
		color: var(--text-primary);
	}
	.mono {
		font-family: var(--forge-mono);
	}
</style>
