import { ElementType } from 'react';
import classNames from 'classnames';
import Field, { FieldProps } from '../Field/Field';
import { FieldNameContext } from '../Field/FieldNameContext';
import useStandardFormInput from '../Field/useStandardField';
import { FormDefaults } from '../FormDefaults';
import { ValidationFunction } from '../Validation/ValidationFunction';

export type FieldArrayProps<
  TForm extends object,
  TProp extends keyof TForm
> = TForm[TProp] extends Array<any> | undefined | null
  ? {
      /** Name of the field, used on submission. If using codegen this must be the provided dto. */
      name: TProp;
      /** Label of the field. */
      label?: string;
      /** Whether the field should be disabled. */
      disabled?: boolean;
      /** Function to validate the value. */
      validate?:
        | ValidationFunction<TForm[TProp]>
        | ValidationFunction<TForm[TProp]>[];
      children: (
        formBuilder: ArrayFormBuilderProp<TForm[TProp]>
      ) => JSX.Element;
    }
  : never;

export type ArrayFormBuilderProp<TValue extends Array<any> | undefined | null> =
  TValue extends Array<infer TForm> | undefined | null
    ? TForm extends object
      ? {
          Field: <
            TProp extends keyof TForm,
            TRenderComponent extends ElementType
          >(
            props: FieldProps<TForm, TProp, TRenderComponent>
          ) => JSX.Element; // assumes this is never null - thought he final component may not render

          FieldArray: <TProp extends keyof TForm>(
            props: FieldArrayProps<TForm, TProp>
          ) => JSX.Element;
        }
      : never
    : never;

/**
 * An array of fields that allows the user to add multiple instances of the same field.
 *
 * Includes "Add Item" and "Remove Item" buttons to allow the user to speicify the number of fields.
 */
export default function FieldArray<
  TForm extends object,
  TProp extends keyof TForm
>({
  name,
  label,
  validate,
  disabled,
  children,
  ...rest
}: FieldArrayProps<TForm, TProp>) {
  const [input] = useStandardFormInput<TForm[TProp]>({
    name: String(name),
    validate: validate,
    disabled: disabled,
  });

  const values: any[] = !input.value
    ? []
    : Array.isArray(input.value)
    ? input.value
    : [];

  return (
    <div className={FormDefaults.cssClassPrefix + 'field-array'}>
      <div className={FormDefaults.cssClassPrefix + 'field-array-header'}>
        <div className={FormDefaults.cssClassPrefix + 'field-array-title'}>
          {label}
        </div>
        <button
          className={classNames(
            FormDefaults.cssClassPrefix + 'add-array-item-button',
            { [FormDefaults.cssClassPrefix + 'disabled']: disabled }
          )}
          title="Add Item"
          type="button"
          onClick={addItem}>
          +
        </button>
      </div>
      <div className={FormDefaults.cssClassPrefix + 'field-array-body'}>
        {values.map((value, index) => {
          const itemName = `${input.name}[${index}]`;
          return (
            <div
              key={
                (value && value['form-input-array-key']) ||
                (value && value['id']) ||
                itemName
              }
              className={classNames(
                FormDefaults.cssClassPrefix + 'field-array-item',
                {
                  [FormDefaults.cssClassPrefix + 'removed']: value.isDeleted,
                }
              )}
              role="listitem">
              <FieldNameContext.Provider value={itemName}>
                {children({
                  Field: Field,
                  FieldArray: FieldArray,
                } as any)}
              </FieldNameContext.Provider>
              <button
                className={classNames(
                  FormDefaults.cssClassPrefix + 'remove-array-item-button',
                  { [FormDefaults.cssClassPrefix + 'disabled']: disabled }
                )}
                type="button"
                title="Remove Item"
                onClick={() => removeItem(value)}>
                -
              </button>
            </div>
          );
        })}
      </div>
    </div>
  );

  function addItem() {
    if (disabled) {
      return;
    }
    input.onChange([...values, {}] as any);
  }

  function removeItem(item: any) {
    if (disabled) {
      return;
    }
    // assumes anything from the server has an 'id' value sent
    if (!item.id) {
      input.onChange(values.filter((x) => x !== item) as any);
      return;
    }
    const mapped = values.map((x) =>
      x !== item ? x : Object.assign({}, x, { isDeleted: true })
    );
    input.onChange(mapped as any);
  }
}
