<script lang="ts">
  interface Props {
    min: number;
    max: number;
    step: number;
    value: number;
    labels?: { value: number; label: string }[];
    onChange: (value: number) => void;
  }

  let { min, max, step, value, labels, onChange }: Props = $props();

  let trackRef: HTMLDivElement | undefined = $state();
  let dragging = $state(false);

  let fillPercent = $derived(((value - min) / (max - min)) * 100);

  let currentLabel = $derived.by(() => {
    if (!labels) return null;
    let closest = labels[0];
    let dist = Math.abs(value - closest.value);
    for (const l of labels) {
      const d = Math.abs(value - l.value);
      if (d < dist) { closest = l; dist = d; }
    }
    return closest?.label ?? null;
  });

  function valueFromEvent(e: MouseEvent | PointerEvent) {
    if (!trackRef) return value;
    const rect = trackRef.getBoundingClientRect();
    let pct = (e.clientX - rect.left) / rect.width;
    pct = Math.max(0, Math.min(1, pct));
    let raw = min + pct * (max - min);

    if (labels && labels.length > 0) {
      let closest = labels[0].value;
      let dist = Math.abs(raw - closest);
      for (const l of labels) {
        const d = Math.abs(raw - l.value);
        if (d < dist) { closest = l.value; dist = d; }
      }
      const snapThreshold = (max - min) * 0.08;
      if (dist < snapThreshold) raw = closest;
    }

    raw = Math.round(raw / step) * step;
    return Math.max(min, Math.min(max, raw));
  }

  function handlePointerDown(e: PointerEvent) {
    dragging = true;
    (e.target as HTMLElement).setPointerCapture(e.pointerId);
    onChange(valueFromEvent(e));
  }

  function handlePointerMove(e: PointerEvent) {
    if (!dragging) return;
    onChange(valueFromEvent(e));
  }

  function handlePointerUp() {
    dragging = false;
  }
</script>

<div class="slider-wrap">
  {#if currentLabel}
    <div class="slider-value">{currentLabel}</div>
  {/if}
  <div
    bind:this={trackRef}
    class="slider-track"
    role="slider"
    aria-valuemin={min}
    aria-valuemax={max}
    aria-valuenow={value}
    tabindex="0"
    onpointerdown={handlePointerDown}
    onpointermove={handlePointerMove}
    onpointerup={handlePointerUp}
  >
    <div class="slider-fill" style="width: {fillPercent}%"></div>
    <div
      class="slider-thumb"
      style="left: {fillPercent}%; transition: {dragging ? 'none' : 'left var(--transition-fast)'};"
    ></div>
  </div>
  {#if labels}
    <div class="slider-labels">
      {#each labels as stop, i}
        {@const pct = ((stop.value - min) / (max - min)) * 100}
        <span
          class="slider-label"
          style="left: {pct}%; {i === 0 ? 'transform: none; text-align: left' : i === labels.length - 1 ? 'transform: translateX(-100%); text-align: right' : 'transform: translateX(-50%); text-align: center'}"
        >
          {stop.label}
        </span>
      {/each}
    </div>
  {/if}
</div>

<style>
  .slider-wrap {
    display: flex;
    flex-direction: column;
    gap: 4px;
  }
  .slider-value {
    font-family: var(--font-body);
    font-size: var(--text-xs);
    font-weight: 600;
    color: var(--color-accent-compress);
    text-align: right;
  }
  .slider-track {
    position: relative;
    height: 8px;
    border-radius: 4px;
    background: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    cursor: pointer;
  }
  .slider-fill {
    position: absolute;
    top: 0;
    left: 0;
    height: 100%;
    border-radius: 4px;
    background: var(--color-accent-compress);
  }
  .slider-thumb {
    position: absolute;
    top: 50%;
    transform: translate(-50%, -50%);
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: white;
    border: 2px solid var(--color-accent-compress);
    box-shadow: 0 1px 4px rgba(0,0,0,0.2);
  }
  .slider-labels {
    position: relative;
    height: 16px;
    margin-top: 2px;
  }
  .slider-label {
    position: absolute;
    font-family: var(--font-body);
    font-size: 10px;
    color: var(--color-text-disabled);
    white-space: nowrap;
  }
</style>
