import * as React from 'react';
import classNames from 'classnames/bind';

import { Field as ReduxField, FieldArray as ReduxFieldArray } from 'redux-form';

import { Field as FinalFormField, FormSpy, Form } from 'react-final-form';
import { FieldArray as FinalFormFieldArray } from 'react-final-form-arrays';

import { composeValidators, geoLocValidation, required } from 'store/utils/validation';
import InputLanguage from 'components/formHelpers/InputLanguage';
import GenericFieldArray from 'components/formHelpers/GenericFieldArray';
import { viewConfig, config } from 'config/general';
import ModuleButton from 'components/items/ModuleButton';

import Input from 'components/formItems/Input';
import NumberInput from 'components/formItems/NumberInput';
import Textarea from 'components/formItems/Textarea';
import UploadZone from 'components/formItems/UploadZone';
import Password from 'components/formItems/Password';
import Wysiwyg from 'components/formItems/Wysiwyg';
import CkEditorField from 'components/formItems/CkEditorField';
import FileStackInput from 'components/formItems/FileStackInput';

import CustomSelect from 'components/formItems/CustomSelect';
import Checkbox from 'components/formItems/Checkbox';
import DateTimePicker from 'components/formItems/DateTimePicker';

import SwitchInput from 'components/formItems/SwitchInput';
import ScheduleInput from 'components/formItems/ScheduleInput';
import { handleVarInString } from 'store/utils/adminHelpers';
import _ from 'lodash';
import { clone } from 'store/utils/helper';
import ColorPicker from 'components/formItems/ColorPicker';
import TagsInput from 'components/formItems/TagsInput';
import GeoLocInput from 'components/formItems/GeoLocInput';
import JsonField from 'components/formItems/JsonField';
import PriceForm from 'components/forms/PriceForm';
import FormItemLabel from 'components/items/FormItemLabel';
import CodeEditorInput from 'components/formItems/CodeEditorInput';
import GenericDropDown from 'components/pageItems/GenericDropDown';
import LanguageSelector from 'components/pageItems/LanguageSelector';
import MultipleUploadZone from 'components/formItems/MultipleUploadZone';
import styleIdentifiers from './genericFields.scss';

const styles = classNames.bind(styleIdentifiers);

export interface GenericFieldsProps {
  fields: Record<string, any>;
  base?: any;
  push: Function;
  upload: Function;
  loadData: Function;
  loadOne: Function;
  edit?: boolean;
  values?: Record<string, any>;
  sectionWrapperClassName: string;
  sectionClassName: string;
  sectionTitleClassName: string;
}

interface GenericFieldsState {}

export default class GenericFields extends React.Component<GenericFieldsProps, GenericFieldsState> {
  constructor(props) {
    super(props);

    this.state = {
      activeLgs: [config.defaultLanguage],
      errors: null,
    };
  }

  getName = (item: Record<string, any>, key: string): string => {
    const { base } = this.props;

    const name = item.name || item.dataField || key;
    return base ? `${base}.${name}` : name;
  };

  checkIfLanguageSelector = array => {
    for (let index = 0; index < array.length; index++) {
      const element = array[index];
      if (element.type?.includes('multi')) return true;
      if (element.fields) {
        const sublevel = Object.values(element.fields);
        if (this.checkIfLanguageSelector(sublevel)) {
          return true;
        }
      }
    }
    return false;
  };

  checkIfErrorIsInSection = section => {
    const { errors } = this.state;
    if (!errors) return false;

    for (let index = 0; index < Object.keys(errors).length; index++) {
      const element = Object.keys(errors)[index];
      return section.includes(element);
    }

    return false;
  };

  renderItem = (item: any, key: string, index): JSX => {
    const {
      edit,
      oneSection,
      sectionWrapperClassName,
      sectionClassName,
      sectionTitleClassName,
      allOpened,
      fieldClassName,
    } = this.props;
    const { activeLgs } = this.state;

    if (!item) return false;

    if (edit && (item.hideUpdate || item.noUpdate)) return false;
    if (!edit && (item.hideCreate || item.noCreate)) return false;

    if (item.type === 'group' || item.type === 'col' || item.type === 'column') {
      return (
        <div key={key} className={styles('group')}>
          <GenericFields {...this.props} fields={item.fields} />
        </div>
      );
    }

    if (item.type === 'section') {
      let hasError = false;
      hasError = this.checkIfErrorIsInSection(Object.keys(item.fields));
      let arrayFields = item.fields;
      if (arrayFields && !Array.isArray(arrayFields)) {
        arrayFields = Object.values(arrayFields);
      }
      if (Object.keys(item.fields)[0] === '0') {
        const allName = [];
        for (let i = 0; i < Object.values(item.fields).length; i++) {
          const element = Object.values(item.fields)[i];
          allName.push(element?.name || element?.label);
        }
        hasError = this.checkIfErrorIsInSection(allName);
      }

      return (
        <div key={key} className={styles('section-wrapper', sectionWrapperClassName)}>
          <GenericDropDown
            title={item.label || 'Default'}
            titleClassName={styles(
              'section-title',
              sectionTitleClassName,
              hasError && 'section-title-error',
            )}
            defaultOpen={item.opened || oneSection || allOpened}
            className={styles('section', item.class, sectionClassName, hasError && 'section-error')}
          >
            {this.checkIfLanguageSelector(arrayFields) && (
              <LanguageSelector
                active={activeLgs}
                onSelect={e => this.setState({ activeLgs: e })}
              />
            )}
            <GenericFields
              {...this.props}
              oneSection={false}
              fields={item.fields}
              activeLgs={activeLgs}
            />
          </GenericDropDown>
        </div>
      );
    }

    if (item.displayIf || item.hideIf) {
      return (
        <div key={key} className={styles('field', fieldClassName)}>
          <FormSpy>{({ values }) => this.renderField(item, key, values)}</FormSpy>
        </div>
      );
    }

    return (
      <div key={key} className={styles('field', fieldClassName)}>
        {this.renderField(item, key)}
      </div>
    );
  };

  checkNoMargin = (item: Record<string, any>): boolean => {
    const { edit } = this.props;

    return item.first || item.noMargin || (!edit && item.firstCreate) || (edit && item.firstUpdate);
  };

  renderField = (item: any, key: string, formValues: object): JSX => {
    const {
      base,
      resourceData,
      upload,
      loadData,
      edit,
      uploadS3,
      finalForm,
      propsInput,
      uploadApollo,
      loadApolloData,
      fieldsProps,
      dialogShow,
      dialogHide,
      activeLgs,
      lg,
      form,
      value,
    } = this.props;

    // Default inputs props
    const inputProps =
      (propsInput && viewConfig && { ...propsInput, ...viewConfig.inputProps }) ||
      (viewConfig && viewConfig.inputProps) ||
      propsInput ||
      {};

    if (!edit && item.type === 'link') return false;

    const type = item.type;

    const name = this.getName(item, key);

    // Override field props
    let overwriteProps = {};

    // Override by type
    if (fieldsProps && fieldsProps[type]) overwriteProps = fieldsProps[type];

    // Override by field name
    if (fieldsProps && fieldsProps[name]) {
      // extend type fields (if exists) with field type
      overwriteProps = {
        ...overwriteProps,
        ...fieldsProps[name],
      };
    }

    const displayIf = item.displayIf;
    const hideIf = item.hideIf;

    if (item.hide) return false;

    // Conditional display / Hide
    if (displayIf && Array.isArray(displayIf)) {
      // 1. check current level
      let check = _.get(formValues, `${base}.${displayIf[0]}`);
      // 2. check root level
      if (!check) check = _.get(formValues, displayIf[0]);

      if (!check || check !== displayIf[1]) {
        return false;
      }
    } else if (displayIf && typeof displayIf === 'object') {
      const keys = Object.keys(displayIf);

      for (let i = 0; i < keys.length; i++) {
        // 1. check current level
        let check = _.get(formValues, `${base}.${keys[i]}`);
        // 2. check root level
        if (!check) check = _.get(formValues, keys[i]);

        if (!check || check !== displayIf[keys[i]]) {
          return false;
        }
      }
    }

    if (hideIf && Array.isArray(hideIf)) {
      // 1. check current level
      let check = _.get(formValues, `${base}.${hideIf[0]}`);
      // 2. check root level
      if (!check) check = _.get(formValues, hideIf[0]);

      if (check && check === hideIf[1]) {
        return false;
      }
    } else if (hideIf && typeof hideIf === 'object') {
      const keys = Object.keys(hideIf);
      for (let i = 0; i < keys.length; i++) {
        // 1. check current level
        let check = _.get(formValues, `${base}.${keys[i]}`);
        // 2. check root level
        if (!check) check = _.get(formValues, keys[i]);
        if (check && check === hideIf[keys[i]]) {
          return false;
        }
      }
    }

    const Field = finalForm ? FinalFormField : ReduxField;
    const FieldArray = finalForm ? FinalFormFieldArray : ReduxFieldArray;

    const commonProps = {
      ...inputProps,
      defaultValue: item.defaultValue,
      label: item.label || key,
      warning: item.warning,
      name,
      // override button class (need to be implemented in components)
      button: ModuleButton,
      validate: item.required ? required : null,
      noMargin: this.checkNoMargin(item),
      info: item.info,
      labelClassName: styles('generic-label', type === 'bool' && 'radio-generic-label'),
    };

    if (type === 'string' || type === 'input' || type === 'text') {
      return (
        <Field
          component={Input}
          {...commonProps}
          min={item.min}
          max={item.max}
          disabled={item.disabled || type === 'text'}
          onClick={() => (item.disabled || type === 'text') && item.onClick && item.onClick(value)}
          placeholder={item.placeholder}
          initialValue={item.initialValue}
          inputType={item.inputType || 'text'}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }
    if (type === 'textarea') {
      return (
        <Field
          {...commonProps}
          component={Textarea}
          border
          rows={item.rows || 4}
          type={item.inputType || 'text'}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }
    if (type === 'tags') {
      return (
        <Field
          {...commonProps}
          component={TagsInput}
          name={name}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }
    if (type === 'password') {
      return (
        <Field
          {...commonProps}
          noMargin={this.checkNoMargin(item)}
          component={Password}
          name={name}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }
    if (type === 'switch') {
      return (
        <Field
          {...commonProps}
          component={SwitchInput}
          wide={item.wide}
          left={item.left}
          right={item.right}
          position={item.position || 'right'}
          labelClassName={styles('label-switch')}
          name={name}
          type={item.inputType || 'text'}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }
    if (type === 'number') {
      return (
        <Field
          {...commonProps}
          component={NumberInput}
          suffix={item.suffix || ''}
          decimalSeparator={item.decimalSeparator || '.'}
          decimalScale={item.decimalScale || 2}
          thousandSeparator={item.thousandSeparator || ' '}
          {...overwriteProps}
        />
      );
    }
    if (type === 'link') {
      return (
        <Field
          {...commonProps}
          component={Input}
          disabled
          inputProps={inputProps}
          type={item.inputType || 'text'}
          label={item.label || key}
          {...overwriteProps}
        />
      );
    }
    if (type === 'multi') {
      return (
        <InputLanguage
          {...commonProps}
          redux={!finalForm}
          item={item}
          disabled={item.disabled}
          inputProps={inputProps}
          warning={item.warning}
          activeLgs={activeLgs}
          {...overwriteProps}
        />
      );
    }
    if (type === 'multiarea') {
      return (
        <InputLanguage
          {...commonProps}
          autoResize={item.autoResize || true}
          redux={!finalForm}
          type="textarea"
          item={item}
          disabled={item.disabled}
          inputProps={inputProps}
          warning={item.warning}
          activeLgs={activeLgs}
          {...overwriteProps}
        />
      );
    }
    if (type === 'multiwysiwyg') {
      let uploadFunc;
      if (item.s3Upload) uploadFunc = uploadS3;
      if (item.apollo) uploadFunc = uploadApollo;
      else uploadFunc = upload;

      return (
        <InputLanguage
          {...commonProps}
          redux={!finalForm}
          type="wysiwyg"
          item={item}
          disabled={item.disabled}
          inputProps={inputProps}
          upload={uploadFunc}
          apollo={item.apollo}
          uploadUrl={item.url || 'pictures'}
          warning={item.warning}
          activeLgs={activeLgs}
          {...overwriteProps}
        />
      );
    }
    if (type === 'multicode') {
      return (
        <InputLanguage
          {...commonProps}
          autoResize={item.autoResize || true}
          redux={!finalForm}
          type="code"
          language={item.language}
          item={item}
          disabled={item.disabled}
          activeLgs={activeLgs}
          inputProps={inputProps}
          {...overwriteProps}
        />
      );
    }

    if (type === 'separator') {
      return (
        <div
          className={styles(
            'separator',
            item.noMargin && 'no-margin',
            item.small && 'intermediary',
          )}
        >
          <h3>{item.label}</h3>
        </div>
      );
    }
    if (type === 'price') {
      return (
        <ModuleButton
          label="Manage price"
          color="primary"
          action={() =>
            dialogShow({
              id: 'custom',
              large: true,
              children: (
                <Form
                  component={PriceForm}
                  initialValues={{
                    price: formValues?.price || value?.price,
                    vatClassId: formValues?.vatClassId || value?.vatClassId,
                    currency: formValues?.currency || value?.currency,
                  }}
                  onSubmit={val => {
                    dialogHide('custom');
                    if (form) {
                      form.change('price', val.price);
                      form.change('currency', val.currency);
                      form.change('vatClassId', val.vatClassId);
                    }
                  }}
                />
              ),
            })
          }
        />
      );
    }
    if (type === 'datetime' || type === 'date') {
      return (
        <Field
          {...commonProps}
          {...item}
          component={DateTimePicker}
          name={name}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }

    if (type === 'list') {
      return (
        <div>
          {item.label && (
            <div className={styles('separator', this.checkNoMargin(item) && 'no-margin')}>
              <h3>{item.label}</h3>
            </div>
          )}
          <div className={styles('list-values', this.checkNoMargin(item) && 'no-margin')}>
            <FieldArray
              component={GenericFieldArray}
              lg={lg}
              name={name}
              finalForm={finalForm}
              formValues={formValues}
              infos={item.fields}
              item={item}
              resourceData={resourceData}
              activeLgs={activeLgs}
            />
          </div>
        </div>
      );
    }
    if (type === 'select') {
      return (
        <Field
          component={CustomSelect}
          {...commonProps}
          orderBy={item.order}
          onlyItemsValues={item.onlyItemsValues}
          items={
            (item.itemsDataKey && resourceData && resourceData[item.itemsDataKey]) ||
            item.values ||
            item.list
          }
          apollo={item.apollo}
          asObject={item.asObject}
          multipleSelection={item.multiple || item.multipleSelection}
          filter={!item.noFilter}
          inputs={item.inputs || {}}
          apiPayload={item.apiPayload || item.api || null}
          noMountApi={item.noMountApi}
          noFilterApi={item.noFilterApi}
          copyObject={item.copyObject}
          loadApiData={item.apollo ? loadApolloData : loadData}
          modelValues={item.modelValues}
          noHttpHeader={item.noHttpHeader || false}
          {...overwriteProps}
        />
      );
    }
    if (type === 'geoloc') {
      let validation;
      if (finalForm) {
        if (item.required) {
          validation = composeValidators(item.required && required, geoLocValidation);
        } else validation = geoLocValidation;
      } else {
        validation = [geoLocValidation];
        if (item.required) validation.push(required);
      }
      return (
        <Field
          component={GeoLocInput}
          multiple={item.multiple}
          {...inputProps}
          {...commonProps}
          {...overwriteProps}
          validate={validation}
        />
      );
    }
    if (type === 'bool') {
      return (
        <Field
          {...commonProps}
          component={Checkbox}
          type="checkbox"
          useBox
          activeClassName={styles('active')}
          items={item.values}
          disabled={item.disabled}
          {...overwriteProps}
        />
      );
    }
    if (type === 'color') {
      return (
        <Field
          {...commonProps}
          component={ColorPicker}
          saveValue={item.saveValue}
          disabled={item.disabled}
          {...overwriteProps}
        />
      );
    }
    if (type === 'filestack') {
      return (
        <Field
          {...commonProps}
          component={FileStackInput}
          info={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }

    if (type === 'schedule') {
      return (
        <Field
          {...commonProps}
          component={ScheduleInput}
          noMargin={this.checkNoMargin(item)}
          name={name}
          initialValue={item.initialValue}
          label={item.label || key}
          noValidation={item.noValidation}
          {...overwriteProps}
        />
      );
    }
    if (type === 'wysiwyg') {
      let uploadFunc;
      if (item.s3Upload) uploadFunc = uploadS3;
      if (item.apollo) uploadFunc = uploadApollo;
      else uploadFunc = upload;

      return (
        <Field
          {...commonProps}
          component={CkEditorField}
          upload={uploadFunc}
          apollo={item.apollo}
          uploadUrl={item.url || 'pictures'}
          noResize={item.noResize}
          name={name}
          label={item.label || key}
          validate={item.required ? required : null}
          {...overwriteProps}
        />
      );
    }
    if (type === 'wysiwyg2') {
      return (
        <Field
          {...commonProps}
          component={Wysiwyg}
          customOptions={item.customOptions}
          {...overwriteProps}
        />
      );
    }
    if (type === 'code') {
      return (
        <Field
          {...commonProps}
          component={CodeEditorInput}
          language={item.language}
          {...overwriteProps}
        />
      );
    }

    // File part
    let filePrefix = item.s3UploadPrefix || item.uploadPrefix || item.prefix;
    filePrefix = filePrefix && handleVarInString(resourceData || formValues, filePrefix);
    if (filePrefix) {
      filePrefix = filePrefix
        .toLowerCase()
        .split(' ')
        .join('_');
    }

    let uploadFunc;
    if (item.s3Upload) uploadFunc = uploadS3;
    if (item.apollo) uploadFunc = uploadApollo;
    else uploadFunc = upload;

    const uploadProps = {
      component: UploadZone,
      s3Upload: item.s3Upload,
      uploadPrefix: filePrefix,
      noFileName: item.noFileName || item.noRename,
      upload: uploadFunc,
      noUnique: item.noUnique,
      apollo: item.apollo,
      uploadPath: item.apiPath,
      asObject: item.asObject,
      showProgress: item.showProgress,
      treatmentFile: item.treatmentFile,
      saveValue: item.saveValue,
      subInfo: item.subInfo,
      inputs: item.inputs,
      noUpload: item.noUpload,
      displayValue: item.displayValue || false,
    };

    if (type === 'file') {
      return (
        <Field
          {...commonProps}
          {...uploadProps}
          type={type}
          uploadUrl={item.url || 'files'}
          accept={item.allowFile || item.accept || 'application/*'}
          noPreview={!item.apollo}
          filePreview
          noResize
          {...overwriteProps}
        />
      );
    }

    if (type === 'video') {
      return (
        <Field
          {...commonProps}
          {...uploadProps}
          type={type}
          uploadUrl={item.url || 'videos'}
          accept="video/*"
          noResize
          videoPreview
          {...overwriteProps}
        />
      );
    }

    if (type === 'picture') {
      return (
        <Field
          {...commonProps}
          {...uploadProps}
          type={type}
          uploadUrl={item.url || 'pictures'}
          cropperOptions={item.cropperOptions}
          withLibrary
          aspectRatio={item.aspectRatio || item.ratio}
          accept="image/*"
          noResize={item.noResize}
          {...overwriteProps}
        />
      );
    }
    if (type === 'multiFiles') {
      return (
        <Field
          {...commonProps}
          {...uploadProps}
          component={MultipleUploadZone}
          type={type}
          oneFile={item.oneFile}
          renderPreviewTop={item.renderPreviewTop}
          previewType="file"
          accept="application/*"
          {...overwriteProps}
        />
      );
    }

    if (type === 'multiPictures') {
      return (
        <Field
          {...commonProps}
          {...uploadProps}
          component={MultipleUploadZone}
          type={type}
          oneFile={item.oneFile}
          renderPreviewTop={item.renderPreviewTop}
          previewType="picture"
          // cropperOptions={item.cropperOptions}
          // withLibrary
          displayValue={item.displayValue || false}
          aspectRatio={item.aspectRatio || item.ratio}
          accept="image/*"
          noResize={item.noResize}
          {...overwriteProps}
        />
      );
    }

    if (type === 'json') {
      return (
        <div>
          {item.label && (
            <div className={styles('label', this.checkNoMargin(item) && 'no-margin')}>
              {item.label}
            </div>
          )}
          <Field
            {...commonProps}
            component={JsonField}
            name={name}
            infos={item.fields}
            item={item}
            resourceData={resourceData}
            {...overwriteProps}
          />
        </div>
      );
    }
    return false;
  };

  render(): JSX {
    const { fields, item, className } = this.props;
    const data = fields || item;
    const keys = Object.keys(data);

    return (
      <div className={styles('GenericFields', className)}>
        <FormSpy
          subscription={{ submitFailed: true, errors: true }}
          onChange={e => {
            if (e?.submitFailed) this.setState({ errors: e?.errors });
          }}
        />
        {keys.map((key, index) => this.renderItem(data[key], key, index))}
      </div>
    );
  }
}
