import { SchemaFieldComponents, VevProps } from '@vev/utils';
import { isFunction } from 'lodash';
import React from 'react';
import {
  SilkeAlertBanner,
  SilkeBox,
  SilkeVariablesWrapper,
  useExtendedFields,
  useFormField,
} from '..';
import { safeEvalFunction } from './safe-eval';
import { isEvalCallback } from './schema-utils';
import { useSchemaContext } from './silke-schema-context';
import { SilkeDivider } from '../silke-divider';

import ArrayField from './fields/array-field';
import ImageField from './fields/image-field';
import VideoField from './fields/video-field';
import NumberField from './fields/number-field';
import ObjectField from './fields/object-field';
import SelectField from './fields/select-field';
import StringField from './fields/string-field';
import ToggleField from './fields/toggle-field';
import LinkField from './fields/link-field';
import LayoutField from './fields/layout-field';
import IconField from './fields/icon-field';
import EventField from './fields/event-field';
import UploadField from './fields/upload-field';
import AudioField from './fields/audio-field';
import ColorField from './fields/color-field';
import GroupField from './fields/group-field';
import PopoverField from './fields/popover-field';

type SilkeSchemaFieldProps = {
  field: VevProps;
  value?: any;
  noPad?: boolean;
  isFirst?: boolean;
  disabled?: boolean;
  readonly?: boolean;
  itemIndex?: number;
};

const SCHEMA_FIELD_COMPONENTS: Partial<SchemaFieldComponents> = {
  string: StringField,
  color: ColorField,
  number: NumberField,
  array: ArrayField,
  image: ImageField,
  audio: AudioField,
  video: VideoField,
  boolean: ToggleField,
  object: ObjectField,
  select: SelectField,
  link: LinkField,
  layout: LayoutField,
  icon: IconField,
  event: EventField,
  upload: UploadField,
  group: GroupField,
  popover: PopoverField,
};

export function SilkeSchemaField({
  field,
  value: fieldValue,
  noPad,
  isFirst,
  disabled,
  readonly,
  itemIndex,
}: SilkeSchemaFieldProps) {
  const extendedFields = useExtendedFields();

  const { value, onChange } = useFormField({ name: field?.name, value: fieldValue });

  const context = useSchemaContext();

  let Field = (field?.component || SCHEMA_FIELD_COMPONENTS[field?.type]) as any;

  if (!Field) {
    const extendedField = extendedFields.find((extField) => extField.type === field?.type);
    if (extendedField) {
      Field = extendedField.field;
    }
  }

  if (!field?.name && !['layout', 'divider', 'group', 'popover'].includes(field?.type))
    return (
      <SilkeAlertBanner
        kind="error"
        fluid
        title="Field is missing name"
        subtitle={JSON.stringify(field)}
      />
    );

  if (!field) return null;

  const getVariableType = (field: VevProps) => {
    if (field.component) return 'none';
    if (field.type === 'number' && (field.options?.display === 'slider' || field.options?.display === 'range-slider')) return 'none';
    if (field.type === 'string') return 'text';
    return field.type;
  };

  return (
    <>
      {['object', 'divider'].includes(field?.type) && !isFirst && (
        <SilkeBox column flex vPad={field?.type === 'divider' ? 'xs' : 's'}>
          <SilkeDivider dark />
        </SilkeBox>
      )}
      <SilkeBox hPad={!noPad ? 'm' : undefined} flex>
        <SilkeVariablesWrapper
          component={Field}
          disabled={disabled}
          readonly={readonly}
          templateVariable
          variableType={getVariableType(field)}
          label={field.title || field?.name}
          fullWidthWrapper
          value={value}
          schema={field as any}
          context={context}
          help={field.description}
          itemIndex={itemIndex}
          onChange={async (value: any) => {
            if (isEvalCallback(field.onChange)) {
              value = await safeEvalFunction(field.onChange.value, value, context);
            } else if (isFunction(field.onChange)) {
              // @ts-ignore TODO: fix this
              value = (await field.onChange(value as any, context)) as any;
            }
            onChange(value);
          }}
        />
      </SilkeBox>
    </>
  );
}
