<script lang="ts" generics="T extends string">
    interface Option {
        label: string;
        value: T;
    }

    let {
        options,
        value,
        onchange,
        ariaLabel,
        testid
    }: {
        options: Option[];
        value: T;
        onchange: (next: T) => void;
        ariaLabel: string;
        testid?: string;
    } = $props();

    // Roving-tabindex refs so arrow keys can move focus across the group.
    let buttons = $state<HTMLButtonElement[]>([]);

    function select(index: number) {
        const opt = options[index];
        if (!opt) return;
        if (opt.value !== value) onchange(opt.value);
        // Selection follows focus, per the WAI-ARIA radiogroup pattern.
        buttons[index]?.focus();
    }

    // ArrowLeft/Up and ArrowRight/Down move (and wrap) through the options;
    // Home/End jump to the ends. Without this a control announced as a
    // `radiogroup` would not respond to the keys AT users expect.
    function onkeydown(event: KeyboardEvent) {
        // Navigate relative to the focused button, not the selected `value`:
        // this control is parent-controlled, so `value` only updates after a
        // re-render — anchoring on focus keeps rapid keypresses from sticking.
        // Falls back to `value` when focus is elsewhere (e.g. event delegated
        // from outside a child, as in unit tests).
        const focused = buttons.findIndex((b) => b === document.activeElement);
        const current = focused >= 0 ? focused : options.findIndex((o) => o.value === value);
        switch (event.key) {
            case 'ArrowRight':
            case 'ArrowDown':
                event.preventDefault();
                select((current + 1) % options.length);
                break;
            case 'ArrowLeft':
            case 'ArrowUp':
                event.preventDefault();
                select((current - 1 + options.length) % options.length);
                break;
            case 'Home':
                event.preventDefault();
                select(0);
                break;
            case 'End':
                event.preventDefault();
                select(options.length - 1);
                break;
        }
    }
</script>

<!-- Keydown is delegated here; focus lives on the child radios via roving
     tabindex, so the group itself is intentionally not a focus target. -->
<!-- svelte-ignore a11y_interactive_supports_focus -->
<div
    class="segmented"
    role="radiogroup"
    aria-label={ariaLabel}
    data-testid={testid}
    {onkeydown}
>
    {#each options as opt, i (opt.value)}
        <button
            bind:this={buttons[i]}
            type="button"
            role="radio"
            aria-checked={opt.value === value}
            tabindex={opt.value === value ? 0 : -1}
            class="seg"
            class:active={opt.value === value}
            onclick={() => {
                if (opt.value !== value) onchange(opt.value);
            }}
            data-testid={testid ? `${testid}-${opt.value}` : undefined}
        >
            {opt.label}
        </button>
    {/each}
</div>

<style>
    .segmented {
        display: inline-flex;
        background: var(--surface);
        border: 1px solid var(--border);
        border-radius: var(--radius-md);
        padding: 2px;
        gap: 2px;
    }

    .seg {
        height: 32px;
        padding: 0 var(--space-3);
        border: none;
        border-radius: var(--radius-sm);
        background: transparent;
        color: var(--text-muted);
        font-size: var(--font-sm);
        font-weight: var(--weight-medium);
        cursor: pointer;
        transition:
            background var(--transition),
            color var(--transition);
    }

    .seg:hover:not(.active) {
        color: var(--text);
    }

    .seg.active {
        background: var(--surface-elevated);
        color: var(--text);
        box-shadow: var(--shadow-sm);
    }

    /* This control doubles as the primary sort toggle (catalog) and the
       Library tab switcher on mobile, so its options must meet the touch
       floor on phones. */
    @media (max-width: 640px) {
        .seg {
            min-height: var(--tap-min);
        }
    }
</style>
