<script lang="ts">
  const clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));

  export let value: number | null | undefined = null;
  export let min = 0;
  export let max = 1;
  export let decimals = 1;
  export let showLabel = false;
  export let label: string | null = null;
  export let emptyLabel = '—';
  export let size: 'sm' | 'md' | 'lg' = 'md';
  export let className = '';

  const { class: restClass = '', ...restProps } = $$restProps;

  const effectiveRange = () => {
    const span = max - min;
    return span === 0 ? 1 : Math.abs(span);
  };

  const normaliseValue = (input: number) => min + clamp(input - min, 0, effectiveRange());

  const percentFromValue = (input: number | null | undefined) => {
    if (typeof input !== 'number' || !Number.isFinite(input)) return null;
    const bounded = clamp((input - min) / effectiveRange(), 0, 1);
    return bounded * 100;
  };

  $: percent = percentFromValue(value);
  $: hasValue = percent !== null;
  $: fill = hasValue ? `${percent!.toFixed(2)}%` : '0%';
  $: displayLabel = label ?? (hasValue ? `${percent!.toFixed(decimals)}%` : emptyLabel);
  $: ariaValueNow = hasValue ? normaliseValue(value as number) : undefined;
  $: ariaValueText = hasValue ? `${percent!.toFixed(decimals)} percent` : 'Not available';
  $: classes = [`score-meter`, `score-meter--${size}`, restClass, className].filter(Boolean).join(' ');
</script>

<div
  {...restProps}
  class={classes}
  class:score-meter--empty={!hasValue}
  data-testid="score-meter"
>
  <div
    class="score-meter__track"
    role="meter"
    aria-valuemin={min}
    aria-valuemax={max}
    aria-valuenow={ariaValueNow}
    aria-valuetext={ariaValueText}
    style={`--score-meter-fill:${fill}`}
  ></div>

  {#if showLabel}
    <span class="score-meter__label">{displayLabel}</span>
  {/if}
</div>

<style>
  .score-meter {
    --score-meter-height: 0.5rem;
    --score-meter-track: rgba(34, 211, 238, 0.15);
    --score-meter-gradient: linear-gradient(90deg, var(--color-neon-fuchsia), var(--color-neon));
    --score-meter-label-color: rgba(148, 163, 184, 0.92);
    display: inline-flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    min-width: 0;
  }

  .score-meter--sm {
    --score-meter-height: 0.45rem;
  }

  .score-meter--lg {
    --score-meter-height: 0.6rem;
  }

  .score-meter__track {
    position: relative;
    flex: 1;
    min-width: 60px;
    height: var(--score-meter-height);
    border-radius: 999px;
    background-color: var(--score-meter-track);
    overflow: hidden;
  }

  .score-meter__track::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: var(--score-meter-gradient);
    -webkit-mask-image: linear-gradient(
      90deg,
      #000 0 var(--score-meter-fill, 0%),
      transparent var(--score-meter-fill, 0%) 100%
    );
    mask-image: linear-gradient(90deg, #000 0 var(--score-meter-fill, 0%), transparent var(--score-meter-fill, 0%) 100%);
    clip-path: inset(0 calc(100% - var(--score-meter-fill, 0%)) 0 0 round 999px);
    transition: -webkit-mask-image 0.3s ease, mask-image 0.3s ease, clip-path 0.3s ease;
  }

  .score-meter__label {
    font-size: 0.85rem;
    color: var(--score-meter-label-color);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .score-meter--empty .score-meter__label {
    opacity: 0.6;
  }
</style>

