import {
  FieldDataType,
  I18N_T,
  ModelType,
  omit,
  whenIsTrue,
} from "@gadmin2/react-common";
import { getValueFromEvent } from "@refinedev/antd";
import { UploadFile } from "antd/lib/upload/interface";
import {
  SelectProps,
  Form,
  Upload,
  Select,
  Input,
  FormInstance,
  InputNumber,
  DatePicker,
  Switch,
  Checkbox,
  Radio,
} from "antd";

import MDEditor from "@uiw/react-md-editor";
import { DefaultOptionType } from "antd/es/select";
import { UploadChangeParam } from "antd/es/upload";
import { ModelConfig } from "generated/types/config.types";
import dayjs from "dayjs";

function getDependanceFields(config: ModelConfig["form"]["reaction"] = {}) {
  const dependencies: string[] = [];
  Object.values(config).forEach(({ toggle }) =>
    toggle.forEach(({ when }) => dependencies.push(...Object.keys(when)))
  );
  return dependencies;
}
export function onFormValuesChange(
  form: any,
  config: ModelConfig["form"]["reaction"] = {}
) {
  const dependencies = getDependanceFields(config);
  return (
    changedValues: Record<string, any>,
    allValues: Record<string, any>
  ) => {
    if (Object.keys(changedValues).some((key) => dependencies.includes(key))) {
      Object.entries(config).forEach(([k, v]) => {
        let matched = false;
        v.toggle.forEach(({ props, when }) => {
          if (whenIsTrue(when, allValues)) {
            matched = true;
            if (props?.hasOwnProperty?.("value")) {
              form.setFieldValue(k, props.value);
            }
            if (props.hidden) {
              if (form.getFieldError(k)?.length) {
                form.setFieldValue(k, null);
                form.validateFields([k]);
              }
            }
          }
        });
        if (!matched && v.defaultProps) {
          if (v.defaultProps?.hasOwnProperty?.("value")) {
            form.setFieldValue(k, v.defaultProps.value);
          }
          if (v.defaultProps.hidden) {
            if (form.getFieldError(k)?.length) {
              form.setFieldValue(k, null);
              form.validateFields([k]);
            }
          }
        }
      });
    }
  };
}

function getConfigProperty(
  fieldName: string,
  form: FormInstance<any>,
  config: ModelConfig["form"]["reaction"] = {}
) {
  if (!config[fieldName]) {
    return {};
  }

  const values = form.getFieldsValue();
  const toggle = config[fieldName].toggle.find(({ when }) =>
    whenIsTrue(when, values)
  );
  if (toggle) {
    return toggle.props;
  }
  return config[fieldName]?.defaultProps || {};
}

function getElement(
  field: FieldDataType,
  resourceName: string,
  t: I18N_T,
  form: FormInstance<any>,
  config: ModelConfig["form"]["reaction"],
  modelsEnum: Record<string, Record<string, string>>,
  model: ModelType,
  apiUrl: string,
  relationSelectProps: Record<
    string,
    SelectProps<
      {
        value: string;
        label: string;
      },
      DefaultOptionType
    >
  >,
  onUploadFileStateChange: (info: UploadChangeParam<UploadFile<any>>) => void
) {
  const { type: fieldType, args } = field.uiType.formItem;
  const fieldName = field.name;
  let element: JSX.Element;
  const fieldProps = getConfigProperty(fieldName, form, config);
  const required = !!fieldProps.required;
  const hidden = !!fieldProps.hidden;
  const readonly = !!fieldProps.readonly;
  const itemProps = {
    ...field,
    key: fieldName,
    label: t(
      `resources.${resourceName}.fields.${fieldName}`,
      fieldName.endsWith(".connect")
        ? t(
            `resources.${
              model[fieldName.split(".")[0]]?.type?.toLowerCase() || ""
            }.name`,
            model[fieldName.split(".")[0]]?.type
          )
        : fieldName
    ),
    name: fieldName.endsWith(".connect")
      ? [fieldName.split(".")[0], field.isList ? "ids" : "id"]
      : fieldName,
    rules: [
      {
        required: field.isRequired || required,
      },
      field.validator,
      ...(field.rules || []),
    ],
    hidden,
  };
  if (field.component) {
    element = <Form.Item {...itemProps}>{field.component}</Form.Item>;
  } else if ("File" === fieldType || "Image" === fieldType) {
    element = (
      <Form.Item {...omit(itemProps, ["name"])}>
        <Form.Item
          name={itemProps.name}
          valuePropName="fileList"
          getValueFromEvent={getValueFromEvent}
          noStyle
        >
          <Upload.Dragger
            disabled={readonly}
            name="file"
            action={apiUrl + `/${resourceName}/upload`}
            listType={fieldType === "Image" ? "picture" : "text"}
            maxCount={args.maxCount || 1}
            multiple={(args.maxCount || 1) > 1}
            accept={args.accept || "*"}
            onChange={onUploadFileStateChange}
          >
            <p className="ant-upload-text">
              {t("form.uploadPlaceholder", "Drag & drop a file in this area")}
            </p>
          </Upload.Dragger>
        </Form.Item>
      </Form.Item>
    );
  } else if ("Email" === fieldType) {
    element = (
      <Form.Item {...itemProps} rules={[{ type: "email" }, ...itemProps.rules]}>
        <Input disabled={readonly} />
      </Form.Item>
    );
  } else if ("Url" === fieldType) {
    element = (
      <Form.Item {...itemProps} rules={[{ type: "url" }, ...itemProps.rules]}>
        <Input disabled={readonly} />
      </Form.Item>
    );
  } else if (fieldName.endsWith(".connect")) {
    element = (
      <Form.Item {...itemProps}>
        <Select
          {...(relationSelectProps as any)[fieldName.split(".")[0]]}
          mode={field.isList ? "multiple" : undefined}
          disabled={readonly}
        />
      </Form.Item>
    );
  } else if (field.kind === "enum") {
    element = (
      <Form.Item {...itemProps}>
        <Select
          showSearch
          options={Object.entries((modelsEnum as any)[field.type]).map(
            ([k, v]) => ({
              value: v,
              label: t(`resources.${resourceName}.enums.${fieldName}.${k}`, k),
            })
          )}
          disabled={readonly}
        />
      </Form.Item>
    );
  } else if (fieldType === "Select") {
    element = (
      <Form.Item {...itemProps}>
        <Select
          showSearch
          mode={args.mode}
          options={args.options.map((value: string) => ({
            value,
            label: t(
              `resources.${resourceName}.enums.${fieldName}.${value}`,
              value
            ),
          }))}
          disabled={readonly}
        />
      </Form.Item>
    );
  } else if (fieldType === "CheckBox") {
    element = (
      <Form.Item {...itemProps}>
        <Checkbox.Group
          options={args.options.map((value: string) => ({
            value,
            label: t(
              `resources.${resourceName}.enums.${fieldName}.${value}`,
              value
            ),
          }))}
          disabled={readonly}
        />
      </Form.Item>
    );
  } else if (fieldType === "Radio") {
    element = (
      <Form.Item {...itemProps}>
        <Radio.Group
          options={args.options.map((value: string) => ({
            value,
            label: t(
              `resources.${resourceName}.enums.${fieldName}.${value}`,
              value
            ),
          }))}
          disabled={readonly}
        />
      </Form.Item>
    );
  } else if (fieldType === "Markdown") {
    element = (
      <Form.Item {...itemProps}>
        <MDEditor data-color-mode="light" />
      </Form.Item>
    );
  } else if (fieldType === "Int" || fieldType === "BigInt") {
    element = (
      <Form.Item
        {...itemProps}
        rules={[{ type: "integer" }, ...itemProps.rules]}
      >
        <InputNumber disabled={readonly} />
      </Form.Item>
    );
  } else if (fieldType === "Float" || fieldType === "Decimal") {
    element = (
      <Form.Item
        {...itemProps}
        rules={[{ type: "number" }, ...itemProps.rules]}
      >
        <InputNumber disabled={readonly} />
      </Form.Item>
    );
  } else if (fieldType === "Boolean") {
    element = (
      <Form.Item {...itemProps} valuePropName="checked">
        <Switch disabled={readonly} />
      </Form.Item>
    );
  } else if (fieldType === "DateTime") {
    element = (
      <Form.Item
        {...itemProps}
        getValueProps={(value) => ({
          value: value ? dayjs(value) : "",
        })}
        rules={[
          {
            required: field.isRequired || required,
          },
          { type: "date" },
        ]}
      >
        <DatePicker showTime disabled={readonly} />
      </Form.Item>
    );
  } else if (fieldType === "Text") {
    element = (
      <Form.Item {...itemProps}>
        <Input.TextArea rows={args.rows || 5} disabled={readonly} />
      </Form.Item>
    );
  } else {
    element = (
      <Form.Item {...itemProps}>
        <Input disabled={readonly} />
      </Form.Item>
    );
  }

  return element;
}

export function getFormItem(
  field: FieldDataType,
  resourceName: string,
  t: I18N_T,
  form: FormInstance<any>,
  config: ModelConfig["form"]["reaction"],
  modelsEnum: Record<string, Record<string, string>>,
  model: ModelType,
  apiUrl: string,
  relationSelectProps: Record<
    string,
    SelectProps<
      {
        value: string;
        label: string;
      },
      DefaultOptionType
    >
  >,
  onUploadFileStateChange: (info: UploadChangeParam<UploadFile<any>>) => void
) {
  if (config && config.hasOwnProperty(field.name)) {
    const dependencies = Object.keys(
      config[field.name].toggle.reduce((acc: {}, { when }) => {
        Object.assign(acc, when);
        return acc;
      }, {})
    );

    return (
      <Form.Item noStyle dependencies={dependencies}>
        {() => {
          return getElement(
            field,
            resourceName,
            t,
            form,
            config,
            modelsEnum,
            model,
            apiUrl,
            relationSelectProps,
            onUploadFileStateChange
          );
        }}
      </Form.Item>
    );
  } else {
    return getElement(
      field,
      resourceName,
      t,
      form,
      config,
      modelsEnum,
      model,
      apiUrl,
      relationSelectProps,
      onUploadFileStateChange
    );
  }
}
