export function parameterSliderValueSpec(slider) {
  const min = Number.parseFloat(slider?.min);
  const max = Number.parseFloat(slider?.max);
  const step = Number.parseFloat(slider?.step);
  return {
    min: Number.isFinite(min) ? min : 0,
    max: Number.isFinite(max) ? max : 100,
    step: Number.isFinite(step) && step > 0 ? step : undefined,
  };
}

export function parameterSliderDisplayName(slider) {
  const row = slider?.closest?.('.param-row');
  const rowLabel = row?.querySelector?.('.param-label')?.textContent?.trim();
  const axisRow = slider?.closest?.('.xyz-slider-row');
  const axis = axisRow?.querySelector?.('.xyz-axis')?.textContent?.trim();
  if (rowLabel && axis) return `${rowLabel} ${axis}`;
  if (rowLabel) return rowLabel;
  if (slider?.getAttribute?.('aria-label')) return slider.getAttribute('aria-label');
  return String(slider?.id || 'Parameter')
    .replace(/_/g, ' ')
    .replace(/\b\w/g, character => character.toUpperCase());
}

export function applyParameterSliderValue(slider, value, EventConstructor) {
  if (!slider) return Number.NaN;
  slider.value = String(value);
  const appliedValue = Number.parseFloat(slider.value);
  const EventClass = EventConstructor
    || slider.ownerDocument?.defaultView?.Event
    || globalThis.Event;
  slider.dispatchEvent(new EventClass('input', { bubbles: true }));
  slider.dispatchEvent(new EventClass('change', { bubbles: true }));
  return appliedValue;
}

function parameterSliderFromTarget(root, target) {
  const selector = 'input[type="range"][data-parameter-slider="true"]';
  let slider = target?.matches?.(selector) ? target : null;
  if (!slider) {
    const wrapper = target?.closest?.('.slider-wrapper');
    slider = wrapper?.querySelector?.(selector) || null;
  }
  if (!slider || slider.disabled || !root?.contains?.(slider)) return null;
  return slider;
}

export function createParameterSliderValueController({
  root,
  dialogController,
  formatValue,
  parseValue,
  beginEdit = () => {},
  endEdit = () => {},
  onApplied = () => {},
}) {
  const onDoubleClick = event => {
    const slider = parameterSliderFromTarget(root, event.target);
    if (!slider) return;

    event.preventDefault?.();
    event.stopPropagation?.();
    const valueSpec = parameterSliderValueSpec(slider);
    const displayName = parameterSliderDisplayName(slider);
    const currentValue = Number.parseFloat(slider.value);
    const formattedValue = formatValue(currentValue, valueSpec);

    dialogController.show({
      id: 'parameterSliderValueDialog',
      title: 'Set parameter value',
      label: `${displayName} (${formatValue(valueSpec.min, valueSpec)}–${formatValue(valueSpec.max, valueSpec)})`,
      value: formattedValue,
      placeholder: formattedValue,
      confirmLabel: 'Set value',
      emptyMessage: 'Enter a parameter value.',
      inputMode: 'decimal',
      min: valueSpec.min,
      max: valueSpec.max,
      step: valueSpec.step,
      validate: nextValue => {
        const parsed = parseValue(nextValue, valueSpec);
        return parsed.valid ? null : parsed.error;
      },
      onSubmit: nextValue => {
        const parsed = parseValue(nextValue, valueSpec);
        if (!parsed.valid) return;
        if (Math.abs(parsed.value - currentValue) < 0.0000001) return;

        beginEdit();
        let appliedValue;
        try {
          appliedValue = applyParameterSliderValue(slider, parsed.value);
        } finally {
          endEdit();
        }
        if (Number.isFinite(appliedValue)) {
          onApplied({
            slider,
            value: appliedValue,
            valueSpec,
            displayName,
          });
        }
      },
    });
  };

  root?.addEventListener?.('dblclick', onDoubleClick);
  return {
    destroy() {
      root?.removeEventListener?.('dblclick', onDoubleClick);
    },
    onDoubleClick,
  };
}
