import cx from 'clsx';
import { isNotNil } from 'es-toolkit';
import {
  Component,
  createEffect,
  createMemo,
  createSignal,
  Index,
  JSX,
  mergeProps,
  onMount,
  Show,
} from 'solid-js';

interface Option {
  label: string | JSX.Element;
  value: string | number | symbol | boolean;
}

interface SegmentsProps {
  name?: string;
  options: Option[];
  value?: Option['value'];
  defaultValue?: Option['value'];
  direction?: 'horizontal' | 'vertical';
  disabled?: boolean;
  onChange?: (value: Option['value'] | undefined) => void;
}

const Segments: Component<SegmentsProps> = (props) => {
  const mProps = mergeProps<SegmentsProps[]>(
    {
      options: [],
      disabled: false,
      direction: 'horizontal',
    },
    props,
  );
  const optionRefs: HTMLDivElement[] = [];

  const originalValue = createMemo(() => mProps.value);
  const [selected, setSelected] = createSignal<Option['value'] | undefined>(undefined);
  const [indicatorTop, setIndicatorTop] = createSignal(0);
  const [indicatorLeft, setIndicatorLeft] = createSignal(0);
  const [indicatorHeight, setIndicatorHeight] = createSignal(0);
  const [indicatorWidth, setIndicatorWidth] = createSignal(0);

  onMount(() => {
    if (isNotNil(mProps.defaultValue)) {
      setSelected(mProps.defaultValue);
    }
  });
  createEffect(() => {
    if (isNotNil(originalValue()) && originalValue() !== selected()) {
      setSelected(originalValue());
    }
  });
  createEffect(() => {
    const selectedIndex = mProps.options?.findIndex((option) => option.value === selected());
    if (isNotNil(selectedIndex) && optionRefs.length > 0 && optionRefs.length > selectedIndex) {
      const optionElement = optionRefs[selectedIndex];
      if (isNotNil(optionElement)) {
        setIndicatorTop(optionElement.offsetTop);
        setIndicatorLeft(optionElement.offsetLeft);
        setIndicatorHeight(optionElement.offsetHeight);
        setIndicatorWidth(optionElement.offsetWidth);
      }
    }
  });

  const handleSelect = (value: Option['value']) => {
    if (mProps.disabled) {
      return;
    }
    setSelected(value);
    mProps.onChange?.(value);
  };

  return (
    <div class="inline-block overflow-hidden rounded-sm p-1 select-none bg-neutral">
      <div
        class={cx(
          'relative flex',
          mProps.direction === 'horizontal' ? 'flex-row items-center' : 'flex-col',
        )}>
        <Index each={mProps.options}>
          {(option, index) => (
            <div
              ref={(el) => (optionRefs[index] = el)}
              aria-disabled={mProps.disabled}
              class={cx(
                'z-[5] cursor-pointer rounded-sm px-2 py-1',
                selected() === option().value
                  ? 'not-aria-disabled:text-on-primary-surface aria-disabled:text-primary-disabled hover:not-aria-disabled:bg-primary-surface-hover/45'
                  : 'not-aria-disabled:text-on-surface aria-disabled:text-surface-disabled  hover:not-aria-disabled:bg-surface/35',
              )}
              onClick={() => handleSelect(option().value)}>
              {option().label}
            </div>
          )}
        </Index>
        <Show when={isNotNil(selected())}>
          <div
            class="pointer-events-none absolute z-[2] rounded-sm bg-primary-surface transition-[top,left,height,width] duration-200 ease-in-out"
            style={{
              top: `${indicatorTop()}px`,
              left: `${indicatorLeft()}px`,
              height: `${indicatorHeight()}px`,
              width: `${indicatorWidth()}px`,
            }}
          />
        </Show>
      </div>
      <Show when={isNotNil(mProps.name)}>
        <input type="hidden" name={mProps.name} value={selected()} />
      </Show>
    </div>
  );
};

export default Segments;
