import {
    ComponentPropsWithoutRef,
    ReactNode,
    Children,
    isValidElement,
    cloneElement,
} from 'react';

import { InputOptions } from '../../utility';

import { styled } from '../../stitches.config';

import { useId } from '../../hooks';

import { useForm, FormValue } from './FormContext';
import { FormLabel, FormLabelProps } from './FormLabel';
import { FormDescription, FormDescriptionProps } from './FormDescription';
import { FormError, FormErrorProps } from './FormError';

const StyledField = styled('div', {
    width: '$space$full',
    variants: {
        layout: {
            horizontal: {
                display: 'flex',
            },
            vertical: {
                display: 'block',
            },
        },
    },
    defaultVariants: {
        layout: 'vertical',
    },
});

const StyledFieldLabel = styled(FormLabel, {
    width: '$space$full',
    maxWidth: '$space$72',
    variants: {
        layout: {
            horizontal: {
                display: 'inline-flex',
                margin: '$1 $2 0 0',
            },
            vertical: {
                margin: '0 0 $1 0',
            },
        },
    },
    defaultVariants: {
        layout: 'vertical',
    },
});

const StyledFieldContainer = styled('div', {
    width: '$space$full',
    variants: {
        layout: {
            horizontal: {
                flex: '1 1 0',
            },
            vertical: {},
        },
    },
    defaultVariants: {
        layout: 'vertical',
    },
});

const StyledFieldContent = styled('div', {
    lineHeight: '1rem',
    width: '$space$full',
});

const StyledContainer = styled('div', {
    width: '100%',
    variants: {
        spacing: {
            none: {
                minHeight: 'initial',
            },
            setHeight: {
                minHeight: '2rem',
            },
        },
    },
});

const StyledFieldDescription = styled(FormDescription, {
    margin: '$1 0 0 0',
    width: '$space$full',
});

const StyledFieldErrorContainer = styled(FormError, {
    margin: '$1 0 0 0',
    width: '$space$full',
});

export type FormFieldOptions = Partial<Pick<InputOptions<unknown>, 'id'>>;

export interface FormFieldProps
    extends FormFieldOptions,
        Omit<
            ComponentPropsWithoutRef<'div'>,
            'value' | 'defaultValue' | 'defaultChecked' | 'onChange'
        > {
    /** Layout of the label */
    layout?: FormValue['layout'];

    /** Determines how spacing around the Field will be modelled */
    spacing?: FormValue['spacing'];

    /** Content to render in the label */
    label?: ReactNode;

    /** Props to pass to the label */
    labelProps?: FormLabelProps;

    /** Content to render in the description */
    description?: ReactNode;

    /** Props to pass to the description */
    descriptionProps?: FormDescriptionProps;

    /** Content to render in the error */
    error?: ReactNode;

    /** Props to pass to the error */
    errorProps?: FormErrorProps;

    /** Children to render */
    children:
        | ReactNode
        | ((props: {
              getFieldProps: <T>(props?: T) => Record<string, unknown>;
              getLabelProps: <T extends FormLabelProps>(
                  props?: T,
              ) => Record<string, unknown>;
              getDescriptionProps: <T extends FormDescriptionProps>(
                  props?: T,
              ) => Record<string, unknown>;
              getErrorProps: <T extends FormErrorProps>(
                  props?: T,
              ) => Record<string, unknown>;
          }) => JSX.Element);
}

export const FormField = (props: FormFieldProps): JSX.Element => {
    const {
        children,
        id,
        layout,
        spacing,
        label,
        labelProps,
        description,
        descriptionProps,
        error,
        errorProps,
        ...otherProps
    } = props;

    // get the form field
    const form = useForm({ layout, spacing });

    // generate a unique id for the field
    const uuid = useId();
    const _id = id ?? uuid;
    const _labelId = `${_id}--label`;

    /**
     * Get the props from the field
     */
    const getFieldProps = <T extends FormFieldOptions>(props?: T) => {
        // TODO: forward other props?
        return {
            id: _id,
            'aria-labelledby': _labelId,
            ...props,
        };
    };

    /**
     * Get the label props
     */
    const getLabelProps = <T extends FormLabelProps>(props?: T) => {
        return {
            id: _labelId,
            htmlFor: _id,
            ...props,
        };
    };

    /**
     * Get the description props
     */
    const getDescriptionProps = <T extends FormDescriptionProps>(props?: T) => {
        return {
            ...props,
        };
    };

    /**
     * Get the error props
     */
    const getErrorProps = <T extends FormErrorProps>(props?: T) => {
        return {
            ...props,
        };
    };

    // we can instead use a function to render
    if (typeof children === 'function') {
        return children({
            getFieldProps: getFieldProps,
            getLabelProps: getLabelProps,
            getDescriptionProps: getDescriptionProps,
            getErrorProps: getErrorProps,
        });
    }

    // render the default field
    return (
        <StyledField layout={form.layout} {...otherProps}>
            {label && (
                <StyledFieldLabel
                    css={{
                        maxWidth:
                            form.labelOptions.width !== undefined
                                ? 'none'
                                : undefined,
                        width: form.labelOptions.width,
                    }}
                    layout={form.layout}
                    {...getLabelProps(labelProps)}
                >
                    {label}
                </StyledFieldLabel>
            )}
            <StyledFieldContainer layout={form.layout}>
                <StyledFieldContent>
                    {Children.map(children, (child) => {
                        // merge the children if possible
                        if (isValidElement(child)) {
                            // Render the children and merge the field props in. Children props will take precendence
                            return cloneElement(
                                child,
                                getFieldProps(child.props),
                            );
                        }
                        return child;
                    })}
                </StyledFieldContent>
                <StyledContainer spacing={form.spacing}>
                    {description && (
                        <StyledFieldDescription
                            {...getDescriptionProps(descriptionProps)}
                        >
                            {description}
                        </StyledFieldDescription>
                    )}
                    {error && (
                        <StyledFieldErrorContainer
                            {...getErrorProps(errorProps)}
                        >
                            {error}
                        </StyledFieldErrorContainer>
                    )}
                </StyledContainer>
            </StyledFieldContainer>
        </StyledField>
    );
};
