import { NumberField as NumberFieldType, SchemaFieldProps, Suggestion } from '@vev/utils';
import React, { useEffect, useRef, useState } from 'react';
import { SilkeCssNumberField } from '../../silke-css-number-field';
import { SilkeBox } from '../../silke-box';
import { SilkeSlider } from '../../silke-slider';
import { SilkeText, SilkeTextSmall } from '../../silke-text';
import { SilkeIconTooltip } from '../../silke-tooltip';
import { SilkeIcon } from '../../silke-icon';
import { SilkeButton } from '../../silke-button';
import { SilkePopover } from '../../silke-popover';

import { getTitle } from './utils/schema-util';
import { SilkeTextFieldOutline } from '../../silke-text-field/silke-text-field-outline';
import { SilkeTextFieldItem } from '../../silke-text-field';

const TEXT_FIELD_WIDTH = 36;

const NumberField = (
  props: SchemaFieldProps<Omit<NumberFieldType, 'format'>> & {
    format?: string;
    noLabel?: boolean;
  },
) => {
  const textFieldRef = useRef(null);
  const dropdownRef = useRef<HTMLDivElement>(null);
  const [showSuggestions, setShowSuggestions] = useState(false);
  const [errorMessage, setErrorMessage] = useState<string>();
  const {
    value,
    onChange,
    schema,
    disabled,
    schema: {
      options: {
        min = Number.NEGATIVE_INFINITY,
        max = Number.POSITIVE_INFINITY,
        display = 'input',
      } = {},
    } = {},
    ...rest
  } = props || {};
  const scale = props.schema?.options?.scale || 1;
  const formats = schema.options?.format
    ? [schema.options.format]
    : schema.options?.formats || ['None'];
  const isNoneFormat = formats.length === 1 && formats[0] === 'None';
  const precision = schema.options?.precision ?? (isNoneFormat ? 0 : undefined);
  let format = props.schema?.options?.format || props.schema?.options?.formats?.[0] || '';
  if (format === 'None') format = '';

  // Need to round number to avoid floating point precision issues
  const scaledValue = value ? Math.round(value * scale * 1000) / 1000 : value;

  useEffect(() => {
    if (scaledValue) {
      if (scaledValue < min) return setErrorMessage(`Value must be greater than ${min}`);
      if (scaledValue > max) return setErrorMessage(`Value must be less than ${max}`);
    }
    setErrorMessage(undefined);
  }, [scaledValue, min, max]);

  const round = (value: number) => {
    if (value < 1) return +value.toFixed(2);
    if (value > 99) return Math.round(value);
    return +value.toFixed(1);
  };

  const displayValue = scale !== 1 ? `${scaledValue ?? 0}${format}` : `${value ?? '0'}${format}`;

  const suggestion = schema.options?.suggestions?.find((s: Suggestion) => {
    return s.value === `${value}`;
  });

  if (schema.options?.suggestions && schema.options?.suggestions.length) {
    if (suggestion) {
      return (
        <SilkeBox column>
          <SilkeBox vAlign="center" style={{ fontSize: '12px' }}>
            <SilkeTextSmall>{getTitle(schema)}</SilkeTextSmall>
            {schema.description && <SilkeIconTooltip tooltip={schema.description} icon="tooltip" />}
          </SilkeBox>
          <SilkeBox gap="s" hPad="s" vPad="xs" align bg="surface-2" flex rounded="tiny">
            <SilkeIcon icon="interaction.icon" />
            <SilkeText size="small">{suggestion.label}</SilkeText>
            <SilkeButton
              icon="close"
              kind="ghost"
              size="s"
              onClick={() => {
                onChange(null);
              }}
            />
          </SilkeBox>
        </SilkeBox>
      );
    }

    return (
      <SilkeBox flex ref={textFieldRef}>
        <SilkeCssNumberField
          {...rest}
          formats={formats}
          precision={precision}
          value={displayValue}
          error={errorMessage}
          disabled={disabled}
          size="s"
          label={props?.noLabel ? '' : getTitle(schema)}
          inline={!!props.noLabel}
          tooltip={schema.description}
          min={min === Number.NEGATIVE_INFINITY ? undefined : min}
          max={max === Number.POSITIVE_INFINITY ? undefined : max}
          onFocus={() => {
            setShowSuggestions(true);
          }}
          onBlur={(e) => {
            if (!dropdownRef.current?.contains(e.relatedTarget as Node)) {
              setShowSuggestions(false);
            }
          }}
          onChange={(v: string) => {
            const num = parseFloat(v);
            if (!isNaN(num)) {
              onChange(scale !== 1 ? num / scale : num);
            }
          }}
        />
        {showSuggestions && (
          <SilkePopover
            anchorOrigin="bottom-center"
            targetOrigin="top-center"
            offsetY={1}
            anchor={textFieldRef}
            matchAnchorWidth
          >
            <SilkeBox
              ref={dropdownRef}
              rounded="tiny"
              cursor="pointer"
              column
              pad="xs"
              bg="neutral-20"
            >
              {schema.options?.suggestions.map((suggestion: Suggestion) => {
                return (
                  <SilkeBox
                    pad="xs"
                    rounded="tiny"
                    bgHover="neutral-30"
                    flex
                    gap="s"
                    height="s"
                    vAlign
                    tabIndex={-1}
                    onClick={() => {
                      setShowSuggestions(false);
                      (onChange as unknown as (v: string) => void)(suggestion.value);
                    }}
                  >
                    {suggestion.icon && (suggestion.icon as React.ReactNode)}
                    <SilkeTextSmall>{suggestion.label}</SilkeTextSmall>
                  </SilkeBox>
                );
              })}
            </SilkeBox>
          </SilkePopover>
        )}
      </SilkeBox>
    );
  }

  if (display === 'slider') {
    const sliderValue = scaledValue ?? 0;
    const sliderFormats = schema.options?.formats || (format ? [format] : ['None']);
    const isNoneFmt = sliderFormats.length === 1 && sliderFormats[0] === 'None';
    const sliderPrecision = schema.options?.precision ?? (isNoneFmt ? 0 : undefined);
    const sliderDisplayValue = scale !== 1 ? `${sliderValue}${format}` : `${value ?? ''}`;

    return (
      <SilkeBox flex>
        <SilkeTextFieldOutline
          label={getTitle(schema)}
          tooltip={schema.description}
          size="s"
          kind="ghost"
          flex
          disabled={disabled}
        >
          <SilkeBox flex vAlign="center" hPad="s">
            <SilkeSlider
              value={(sliderValue - min) / (max - min)}
              onChange={(percentage) => {
                const val = (percentage * (max - min) + min) / scale;
                onChange(round(val));
              }}
            />
          </SilkeBox>
          <SilkeTextFieldItem hPad="xs">
            <SilkeCssNumberField
              size="s"
              width={TEXT_FIELD_WIDTH}
              formats={sliderFormats}
              precision={sliderPrecision}
              value={sliderDisplayValue}
              disabled={disabled}
              min={min === Number.NEGATIVE_INFINITY ? undefined : min}
              max={max === Number.POSITIVE_INFINITY ? undefined : max}
              onChange={(v: string) => {
                const num = parseFloat(v);
                if (!isNaN(num)) {
                  onChange(scale !== 1 ? num / scale : num);
                }
              }}
              style={{ width: 52 }}
            />
          </SilkeTextFieldItem>
        </SilkeTextFieldOutline>
      </SilkeBox>
    );
  }

  if (display === 'range-slider') {
    // min/max are in display (scaled) space, rangeValue holds stored values
    const storedMin = min / scale;
    const storedMax = max / scale;
    const rangeValue = Array.isArray(value) ? value : [storedMin, storedMax];
    const scaledMin = Math.round((rangeValue[0] ?? storedMin) * scale * 1000) / 1000;
    const scaledMax = Math.round((rangeValue[1] ?? storedMax) * scale * 1000) / 1000;
    const normalizedMin = (scaledMin - min) / (max - min);
    const normalizedMax = (scaledMax - min) / (max - min);
    // Range slider produces [number, number] but onChange is typed for single number
    const onRangeChange = onChange as unknown as (v: number[]) => void;
    const rangeFormats = schema.options?.formats || (format ? [format] : ['None']);
    const isNoneFmt = rangeFormats.length === 1 && rangeFormats[0] === 'None';
    const rangePrecision = schema.options?.precision ?? (isNoneFmt ? 0 : undefined);

    return (
      <SilkeBox flex>
        <SilkeTextFieldOutline
          label={getTitle(schema)}
          tooltip={schema.description}
          size="s"
          flex
          kind="ghost"
          disabled={disabled}
        >
          <SilkeTextFieldItem hPad="xs">
            <SilkeCssNumberField
              size="s"
              formats={rangeFormats}
              width={TEXT_FIELD_WIDTH}
              precision={rangePrecision}
              value={`${scaledMin}${format}`}
              disabled={disabled}
              min={min === Number.NEGATIVE_INFINITY ? undefined : min}
              max={max === Number.POSITIVE_INFINITY ? undefined : max}
              onChange={(v: string) => {
                const num = parseFloat(v);
                if (!isNaN(num)) {
                  const stored = scale !== 1 ? num / scale : num;
                  onRangeChange([stored, rangeValue[1]]);
                }
              }}
              style={{ width: 44 }}
            />
          </SilkeTextFieldItem>
          <SilkeBox flex vAlign="center" hPad="xs">
            <SilkeSlider
              value={[normalizedMin, normalizedMax]}
              onChange={(percentages) => {
                if (Array.isArray(percentages)) {
                  const newMin = round((percentages[0] * (max - min) + min) / scale);
                  const newMax = round((percentages[1] * (max - min) + min) / scale);
                  onRangeChange([newMin, newMax]);
                }
              }}
            />
          </SilkeBox>
          <SilkeTextFieldItem hPad="xs">
            <SilkeCssNumberField
              size="s"
              width={TEXT_FIELD_WIDTH}
              formats={rangeFormats}
              precision={rangePrecision}
              value={`${scaledMax}${format}`}
              disabled={disabled}
              min={min === Number.NEGATIVE_INFINITY ? undefined : min}
              max={max === Number.POSITIVE_INFINITY ? undefined : max}
              onChange={(v: string) => {
                const num = parseFloat(v);
                if (!isNaN(num)) {
                  const stored = scale !== 1 ? num / scale : num;
                  onRangeChange([rangeValue[0], stored]);
                }
              }}
              style={{ width: 44 }}
            />
          </SilkeTextFieldItem>
        </SilkeTextFieldOutline>
      </SilkeBox>
    );
  }

  return (
    <SilkeBox flex>
      <SilkeCssNumberField
        {...rest}
        formats={formats}
        precision={precision}
        value={displayValue}
        error={errorMessage}
        disabled={disabled}
        size="s"
        label={props?.noLabel ? '' : getTitle(schema)}
        inline={!!props.noLabel}
        tooltip={schema.description}
        min={min === Number.NEGATIVE_INFINITY ? undefined : min}
        max={max === Number.POSITIVE_INFINITY ? undefined : max}
        onChange={(v: string) => {
          const num = parseFloat(v);
          // If value is purely numeric (format is None), store as number
          if (!isNaN(num) && v === `${num}`) {
            onChange(scale !== 1 ? num / scale : num);
          } else if (!isNaN(num) && format && scale !== 1) {
            onChange(num / scale);
          } else if (!isNaN(num) && !isNoneFormat) {
            // Format has a unit (e.g. 's', '%') — strip the unit and store as number
            onChange(scale !== 1 ? num / scale : num);
          } else {
            // Value includes a unit suffix (e.g. '20px') — store as-is
            (onChange as unknown as (v: string) => void)(v);
          }
        }}
      />
    </SilkeBox>
  );
};

export default NumberField;
