import { type InputHTMLAttributes, useMemo } from 'react';

import {
    formErrorClassName,
    formFieldClassName,
    formHelpClassName,
    formLabelClassName,
    formNumberInputFieldClassName,
    formNumberInputShellClassName,
    formNumberInputShellErrorClassName,
    formNumberInputStepButtonClassName,
} from '@admin/components/ui/field-styles';
import { cn } from '@admin/lib/utils';

type FormNumberInputProps = {
    id?: string;
    label: string;
    value: number | string;
    onChange: (value: number | string) => void;
    error?: string;
    helpText?: string;
    min?: number;
    max?: number;
    step?: number | string;
    placeholder?: string;
    required?: boolean;
    /** When true, pass the raw string value (e.g. decimal prices). Default: numeric. */
    valueAsString?: boolean;
    inputProps?: Omit<
        InputHTMLAttributes<HTMLInputElement>,
        'type' | 'value' | 'onChange' | 'min' | 'max' | 'step' | 'className'
    >;
};

function parseStep(step?: number | string): number {
    if (step === undefined) {
        return 1;
    }

    return typeof step === 'string' ? Number.parseFloat(step) : step;
}

function parseNumericValue(value: number | string): number {
    if (value === '' || value === null || value === undefined) {
        return 0;
    }

    const parsed = typeof value === 'string' ? Number.parseFloat(value) : value;

    return Number.isFinite(parsed) ? parsed : 0;
}

function clampValue(value: number, min?: number, max?: number): number {
    let next = value;

    if (min !== undefined) {
        next = Math.max(min, next);
    }

    if (max !== undefined) {
        next = Math.min(max, next);
    }

    return next;
}

function roundToStep(value: number, stepAmount: number): number {
    if (stepAmount >= 1) {
        return value;
    }

    const decimals = stepAmount.toString().split('.')[1]?.length ?? 2;
    const factor = 10 ** decimals;

    return Math.round(value * factor) / factor;
}

function formatOutputValue(value: number, valueAsString: boolean, stepAmount: number): number | string {
    if (valueAsString) {
        if (stepAmount < 1) {
            return value.toFixed(2);
        }

        return String(value);
    }

    return Number.isInteger(stepAmount) ? Math.trunc(value) : value;
}

function displayValue(value: number | string): string {
    if (value === '' || value === null || value === undefined) {
        return '';
    }

    return String(value);
}

export function FormNumberInput({
    id,
    label,
    value,
    onChange,
    error,
    helpText,
    min = 0,
    max,
    step,
    placeholder,
    required,
    valueAsString = false,
    inputProps,
}: FormNumberInputProps) {
    const inputId = id ?? label.toLowerCase().replace(/\s+/g, '-');
    const stepAmount = useMemo(() => parseStep(step), [step]);
    const numericValue = useMemo(() => clampValue(parseNumericValue(value), min, max), [value, min, max]);
    const decrementDisabled = numericValue <= (min ?? 0);
    const incrementDisabled = max !== undefined && numericValue >= max;

    const applyValue = (next: number) => {
        const clamped = clampValue(roundToStep(next, stepAmount), min, max);
        onChange(formatOutputValue(clamped, valueAsString, stepAmount));
    };

    const handleInputChange = (raw: string) => {
        if (raw === '') {
            onChange(valueAsString ? '' : min ?? 0);

            return;
        }

        if (valueAsString && stepAmount < 1) {
            if (/^\d*\.?\d{0,2}$/.test(raw)) {
                onChange(raw);
            }

            return;
        }

        if (/^\d+$/.test(raw)) {
            applyValue(Number.parseInt(raw, 10));
        }
    };

    return (
        <div className={cn(formFieldClassName, 'mb-4')}>
            <label htmlFor={inputId} className={formLabelClassName}>
                {label}
            </label>

            <div
                className={error ? formNumberInputShellErrorClassName : formNumberInputShellClassName}
                data-hs-input-number
            >
                <div className="flex w-full items-center justify-between gap-x-5">
                    <div className="grow">
                        <input
                            id={inputId}
                            type="text"
                            inputMode={stepAmount < 1 ? 'decimal' : 'numeric'}
                            className={cn(formNumberInputFieldClassName, inputProps?.className)}
                            value={displayValue(value)}
                            placeholder={placeholder}
                            required={required}
                            aria-invalid={Boolean(error)}
                            aria-describedby={
                                error ? `${inputId}-error` : helpText ? `${inputId}-help` : undefined
                            }
                            data-hs-input-number-input
                            onChange={(event) => handleInputChange(event.target.value)}
                            {...inputProps}
                        />
                    </div>
                    <div className="flex items-center justify-end gap-x-1.5">
                        <button
                            type="button"
                            className={formNumberInputStepButtonClassName}
                            data-hs-input-number-decrement
                            disabled={decrementDisabled}
                            aria-label={`Decrease ${label}`}
                            onClick={() => applyValue(numericValue - stepAmount)}
                        >
                            <i className="ri-subtract-line" />
                        </button>
                        <button
                            type="button"
                            className={formNumberInputStepButtonClassName}
                            data-hs-input-number-increment
                            disabled={incrementDisabled}
                            aria-label={`Increase ${label}`}
                            onClick={() => applyValue(numericValue + stepAmount)}
                        >
                            <i className="ri-add-line" />
                        </button>
                    </div>
                </div>
            </div>

            {error && (
                <span id={`${inputId}-error`} className={formErrorClassName}>
                    {error}
                </span>
            )}
            {helpText && !error && (
                <p id={`${inputId}-help`} className={formHelpClassName}>
                    {helpText}
                </p>
            )}
        </div>
    );
}
