import { QuestionCircleOutlined, SaveOutlined } from '@ant-design/icons'
import {
  Alert,
  Button,
  Checkbox,
  Input,
  Modal,
  Row,
  Select,
  Table,
  Tooltip
} from 'antd'
import { FormInstance } from 'antd/es/form/Form'
import { fixControlledValue } from 'antd/es/input/Input'
import { ColumnsType } from 'antd/lib/table'
import { AxiosResponse } from 'axios'
import {
  Dispatch,
  SetStateAction,
  useCallback,
  useEffect,
  useMemo,
  useState
} from 'react'
import { Link } from 'react-router-dom'
import semver from 'semver'
import TOML from 'toml'

import { cloudConfigControllerListParameterAttributes } from 'apiClient/services/devops'
import {
  CloudconfigConfiguration,
  CloudconfigChangeFileReq,
  CloudconfigCreateConfigurationChangeResp,
  CloudconfigParameterSource,
  CloudconfigPreviewConfigurationChangeResp,
  CloudconfigNamedParameter,
  CloudconfigUnifiedParameter,
  CloudconfigConfigurationFiles,
  CloudconfigParameterApplyPolicy,
  CloudconfigClusterType
} from 'apiClient/services/devops/interface'
import CodeValue from 'components/CodeValue'
import ConfigName from 'components/ConfigName'
import ModalForm from 'components/ModalForm'

import BatchModifyForm, { BatchModifyFormType } from '../Forms/BatchModify'
import EditParamForm, {
  EditParamType,
  fixedFiles,
  IsBool,
  IsFloat
} from '../Forms/EditParamForm'
import PreviewForm, {
  PreviewFormType,
  buildPreviewExecutionWindow,
  convertToString
} from '../Forms/PreviewForm'

import ExportInfo from './ExportInfo'

export interface PreviewParam {
  files?: CloudconfigChangeFileReq[]
  [key: string]: any
}
export interface GetResp {
  [key: string]: any
}
export type GetCloudConfigController = (
  params: unknown,
  options?: unknown
) => Promise<AxiosResponse<GetResp>>
export type PreviewCloudConfigController = (
  params: PreviewParam,
  options?: unknown
) => Promise<AxiosResponse<CloudconfigPreviewConfigurationChangeResp>>
export type CreateCloudConfigController = (
  params: PreviewParam,
  options?: unknown
) => Promise<AxiosResponse<CloudconfigCreateConfigurationChangeResp>>

const injectedRegionSizeParams: EditParamType[] = [
  {
    configType: 'tikv',
    filename: 'tikv',
    name: {
      value: 'coprocessor.region-split-size',
      label: 'coprocessor.region-split-size'
    },
    type: 'string',
    newVal: '96MiB',
    inputType: 'string',
    defaultVal: '256MiB',
    applyPolicy: 'RollingRestart',
    risk_level: 'unknown'
  }
]

const CurrentConfig = ({
  getAPI,
  getParam,
  cacheAPI,
  cacheParam,
  previewAPI,
  previewParam,
  createAPI,
  createParam,
  type,
  upgradeForm,
  clusterType
}: {
  getAPI: GetCloudConfigController
  getParam: unknown
  cacheAPI: GetCloudConfigController
  cacheParam: unknown
  previewAPI: PreviewCloudConfigController
  previewParam: PreviewParam
  createAPI: CreateCloudConfigController
  createParam: PreviewParam
  type: string
  upgradeForm?: Dispatch<SetStateAction<boolean>>
  clusterType: CloudconfigClusterType
}) => {
  const [configuration, setConfiguration] = useState<CloudconfigConfiguration>()
  const [files, setFiles] = useState<CloudconfigConfigurationFiles>()
  const [filename, setFilename] = useState<string>()
  const [modalEdit, setModalEdit] = useState(false)
  const [modalCommit, setModalCommit] = useState(false)
  const [modalExport, setModalExport] = useState(false)
  const [batchModify, setBatchModify] = useState(false)
  const [modalEditInitial, setModalEditInitial] = useState<{
    configType?: string
    filename?: string
    name?: { value: string; label: string }
    old?: unknown
  }>()
  const [reload, setReload] = useState(false)
  const [onlyShowModified, setOnlyShowModified] = useState(false)

  let defaultFile = 'tidb'
  if (previewParam.change_type == 'tiproxy') {
    defaultFile = 'tiproxy'
  } else if (previewParam.change_type == 'sharedpool') {
    defaultFile = 'tikv'
  }

  const [preview, setPreview] =
    useState<CloudconfigPreviewConfigurationChangeResp>()
  const [searchInput, setSearchInput] = useState<string>('')
  const [changeable, setChangeable] = useState<{
    changeable: boolean
    reason: string
  }>()

  useEffect(() => {
    fetch()
  }, [
    reload,
    JSON.stringify(getParam),
    JSON.stringify(cacheParam),
    clusterType
  ])

  async function fetch() {
    try {
      let s = (await getAPI(getParam)).data
      if (
        s.configuration.version === undefined ||
        s.configuration.version === ''
      ) {
        s.configuration.version = 'v'
      }
      const c = (await cacheAPI(cacheParam)).data
      setConfiguration(s.configuration)
      setFiles(c.configuration.files)
      setFilename(
        Object.keys(c.configuration.files).includes('tidb')
          ? 'tidb'
          : Object.keys(c.configuration.files)[0] || defaultFile
      )
      setChangeable(s.changeable)
    } catch (error) {
      Modal.error({
        title: error.response.data.errcode,
        content: error.response.data.errmsg
      })
    }
  }

  const fillPostParam = () => {
    let sources: string[] = ['custom']
    let res: CloudconfigChangeFileReq[] = []
    Object.keys(files!).map((k) => {
      let text
      if (configuration!.files![k] === undefined) {
        text = undefined
      } else {
        text = configuration!.files![k].text
      }
      // const text = configuration!.files![k].text
      let custom: CloudconfigNamedParameter[] = []
      // let modified: CloudconfigChangedParameter[] = []
      Object.keys(files![k].all_parameters!).map((kp) => {
        const to = getActualValue(files!, k, kp)
        if (sources.includes(files![k].all_parameters![kp].source!)) {
          custom.push({ name: kp, value: to })
        }
      })
      res.push({
        custom_parameters: custom,
        config_type: files![k].config_type,
        filename: files![k].filename,
        previous_text: text
      })
    })
    return res
  }

  async function commit() {
    let req = previewParam
    req.files = fillPostParam()
    try {
      // FIX ME
      const data = (await previewAPI(req)).data
      setPreview(data)
      // if (upgradeForm !== undefined) {
      //   upgradeForm(false)
      // }
      setModalCommit(true)
    } catch (error) {
      Modal.error({
        title: error.response.data.errcode,
        content: error.response.data.errmsg
      })
    }
  }

  const handleUnset = useCallback(
    (filename: string, paramName: string) => {
      let tmp = { ...files }
      if (tmp[filename]?.all_parameters?.[paramName]) {
        // Remove custom value and source
        delete tmp[filename].all_parameters![paramName].custom
        tmp[filename].all_parameters![paramName].source = tmp[filename]
          .all_parameters![paramName].template
          ? 'template'
          : 'kernel default'
      }
      setFiles(tmp)
    },
    [files]
  )

  const columns: ColumnsType<UnifyParam> = useMemo(
    () => [
      {
        title: 'Name',
        render: (_: any, record) => {
          return <ConfigName value={record.param.name} />
        }
      },
      {
        title: 'Filename',
        render: (_: any, record) => {
          return record.filename
        }
      },
      // FIX ME UPGRADE
      ...(type === 'cluster' || type === 'upgrade'
        ? [
            {
              title: (
                <>
                  Source &nbsp;
                  <Tooltip
                    title={
                      <div>
                        Custom: the value has been manually set by the user. It
                        has the highest priority.
                        <br />
                        System Template: the value provided by the dbaas basic
                        config.
                        <br />
                        Engine: the parameter is not set. The value is provided
                        by the kernel program itself.
                      </div>
                    }
                    overlayInnerStyle={{ width: '400px' }}
                  >
                    <QuestionCircleOutlined
                      style={{ fontSize: '16px', color: '#08c' }}
                    />
                  </Tooltip>
                </>
              ),
              render: (_: any, record: UnifyParam) => {
                return String(record.param.source) ===
                  CloudconfigParameterSource.template ? (
                  <Link
                    to={`/template/${
                      files![record.filename].template_id
                    }?cluster_type=${clusterType}`}
                    target="_blank"
                  >
                    System Template
                  </Link>
                ) : (
                  <>{String(record.param.source)}</>
                )
              }
            }
          ]
        : []),
      {
        title: 'Value',
        render: (_: any, record) => {
          let origin = getActualValue(
            configuration!.files!,
            record.filename,
            record.param.name!
          )
          origin =
            origin !== undefined && origin !== null
              ? origin
              : getDefaultValue(files!, record.filename, record.param.name!)
          return convertToString(
            getActualValue(files!, record.filename, record.param.name!)
          ) === convertToString(origin) ? (
            <CodeValue value={origin} />
          ) : (
            <CodeValue
              style={{ color: 'red' }}
              value={getActualValue(
                files!,
                record.filename,
                record.param.name!
              )}
            />
          )
        }
      },
      {
        title: 'Apply Policy',
        render: (_: any, record) => {
          return String(record.param.attribute?.apply_policy)
        }
      },
      {
        title: (
          <>
            Action &nbsp;
            <Tooltip
              title={
                <div>
                  Edit: update config param to custom value
                  <br />
                  Unset: unset the custom value. the template value will be used
                  if available. Otherwise, it will be excluded from config file
                  that passthrought to the tidb cluster, allowing the tidb
                  kernal to fall back to its default value.
                </div>
              }
              overlayInnerStyle={{ width: '400px' }}
            >
              <QuestionCircleOutlined
                style={{ fontSize: '16px', color: '#08c' }}
              />
            </Tooltip>
          </>
        ),
        key: 'action',
        render: (_: any, record) =>
          record.filename === 'INIT_SQL' ? null : (
            <div style={{ display: 'flex', gap: '8px' }}>
              <a
                onClick={() => {
                  const actualValue = getActualValue(
                    configuration?.files!,
                    record.filename,
                    record.param.name!
                  )
                  setModalEditInitial({
                    configType: record.configtype,
                    filename: record.filename,
                    name: {
                      value: record.param.name!,
                      label: record.param.name!
                    },
                    old:
                      actualValue === null || actualValue === undefined
                        ? convertToString(
                            getDefaultValue(
                              files!,
                              record.filename,
                              record.param.name!
                            )
                          )
                        : convertToString(
                            getActualValue(
                              configuration?.files!,
                              record.filename,
                              record.param.name!
                            )
                          )
                  })
                  setModalEdit(true)
                }}
              >
                Edit
              </a>
              {record.param.source === 'custom' && (
                <a
                  onClick={() =>
                    handleUnset(record.filename, record.param.name!)
                  }
                >
                  Unset
                </a>
              )}
            </div>
          )
      }
    ],
    [files, filename, configuration, handleUnset]
  )

  const renderEditParamFields = useCallback(
    (form: FormInstance) => {
      form.setFieldsValue({
        configType: modalEditInitial?.configType,
        filename: modalEditInitial?.filename,
        name: modalEditInitial?.name,
        old: modalEditInitial?.old
      })
      return (
        <EditParamForm
          {...modalEditInitial}
          form={form}
          version={configuration?.version}
          files={configuration?.files}
          filename={filename}
          clusterType={clusterType}
          opCategory={type}
        />
      )
    },
    [modalEdit, filename, configuration, modalEditInitial]
  )

  const handleEdit = useCallback(
    (payload: EditParamType) => {
      payload.newVal = parseFormValue(payload.type, String(payload.newVal!))
      payload.defaultVal = parseFormValue(
        payload.type,
        String(payload.defaultVal ?? '')
      )
      handleParamChange(payload)
      setModalEdit(false)
      setFilename(payload.filename)
    },
    [files, configuration]
  )

  const handleParamChange = (payload: EditParamType) => {
    let tmp = files || {}
    if (!tmp[payload.filename]) {
      tmp![payload.filename!] = {
        all_parameters: {},
        config_type: payload.configType,
        filename: payload.filename,
        text: ''
      }
      tmp![payload.filename!].all_parameters![payload.name.value] = {
        custom: { value: payload.newVal },
        source: 'custom',
        name: payload.name.value,
        attribute: {
          default_value: payload.defaultVal,
          type: payload.type,
          apply_policy: payload.applyPolicy
        }
      }
    } else if (!tmp[payload.filename].all_parameters) {
      tmp[payload.filename].all_parameters = {}
      tmp![payload.filename!].all_parameters![payload.name.value] = {
        custom: { value: payload.newVal },
        source: 'custom',
        name: payload.name.value,
        attribute: {
          default_value: payload.defaultVal,
          type: payload.type,
          apply_policy: payload.applyPolicy
        }
      }
    } else if (!tmp[payload.filename].all_parameters![payload.name.value]) {
      tmp[payload.filename].all_parameters![payload.name.value] = {
        custom: { value: payload.newVal },
        source: 'custom',
        name: payload.name.value,
        attribute: {
          default_value: payload.defaultVal,
          type: payload.type,
          apply_policy: payload.applyPolicy
        }
      }
    } else {
      tmp[payload.filename].all_parameters![payload.name.value].custom = {
        value: payload.newVal
      }
      tmp[payload.filename].all_parameters![payload.name.value].source =
        'custom'
    }
    setFiles(tmp)
  }

  const handleApply = useCallback(
    async (payload: PreviewFormType) => {
      let req = createParam
      req.files = fillPostParam()
      req.apply_policy =
        payload.apply_policy || CloudconfigParameterApplyPolicy.RollingRestart
      req.reason = payload.reason
      req.execution_window = buildPreviewExecutionWindow(payload)
      try {
        let resp = (await createAPI(req)).data.change_id
        Modal.success({
          title: 'Info',
          content: (
            <div>
              <p>
                This config change has been created.
                <br></br>
                However, you should go to to the <b>Detail</b> Page to check the
                Approval information and Apply it. Click <b>OK</b> will redirect
                you to that page.
              </p>
            </div>
          ),
          onOk() {
            window.open(`/confighistories/${resp}`, '_blank')
          }
        })
        setModalCommit(false)
        setReload((pre) => !pre)
      } catch (error) {
        Modal.error({
          title: error.response.data.errcode,
          content: error.response.data.errmsg
        })
      } finally {
        if (upgradeForm) {
          upgradeForm(false)
        }
      }
    },
    [files]
  )

  const handleCancelEdit = useCallback(() => {
    setModalEdit(false)
  }, [])

  const renderPreviewParamFields = useCallback(
    (form: FormInstance) => {
      return (
        <div>
          {needInjectRegionSizeParams() && (
            <Alert
              message="WARNING"
              description={
                <span>
                  Injected region size related parameters for TiKV as part of
                  the upgrade to v8.5.0+. Includes:
                  <ul>
                    <li>* coprocessor.region-split-size</li>
                  </ul>
                  More details see:{' '}
                  <a
                    href="https://pingcap.feishu.cn/wiki/Jgz7wYF25iPxrzkRN6LcSBoInVd"
                    target="_blank"
                    rel="noopener noreferrer"
                  >
                    SOP on Region-Size: Upgrading clusters to v8.5.x
                  </a>
                  .
                </span>
              }
              type="warning"
              showIcon
            />
          )}
          <PreviewForm preview={preview} type={type} form={form} />
        </div>
      )
    },
    [modalCommit]
  )

  const handleCancelPreview = useCallback(() => {
    setModalCommit(false)
  }, [])

  const mapFilenameToConfigType = (fn: string): string => {
    if (files![fn]?.config_type) {
      return files![fn].config_type!
    } else return fn
  }

  const renderBatchModifyFields = useCallback(
    (form: FormInstance) => {
      return (
        <BatchModifyForm
          clusterType={clusterType}
          component={filename}
          form={form}
          version={configuration?.version}
        />
      )
    },
    [modalCommit, filename]
  )

  const handleCancelBatchModify = useCallback(() => {
    setBatchModify(false)
  }, [])

  const handleBatchModify = useCallback(
    async (payload: BatchModifyFormType) => {
      try {
        const parsed = TOML.parse(payload.toml)
        const flat = flattenObject(parsed)
        const dict =
          (
            await cloudConfigControllerListParameterAttributes(
              //form.getFieldValue(['configType']),
              filename!,
              configuration?.version!,
              {
                cluster_type: clusterType || 'DEDICATED_TIDB'
              }
            )
          ).data.items || []
        Object.entries(flat).forEach(([key, value]) => {
          // Check if the key exists in dict
          const found = dict.find((item: any) => item.name === key)
          if (
            !found &&
            fixedFiles.includes(filename!) &&
            clusterType === 'DEDICATED_TIDB'
          ) {
            Modal.error({
              title: 'Param Existence Check Fail',
              content: key + 'does not exist in param dict '
            })
          } else if (!found) {
            let payload: EditParamType = {
              configType: mapFilenameToConfigType(filename!),
              filename: filename!,
              name: { value: key, label: key },
              type: typeInfer(value),
              newVal: value,
              inputType: typeInfer(value),
              defaultVal: undefined,
              applyPolicy: 'RollingRestart'
            }
            handleParamChange(payload)
          } else {
            // type check
            const tc = typeCheck(value, found?.type || 'string')
            if (!tc) {
              Modal.error({
                title: 'Type Check Fail',
                content:
                  found?.name! +
                  "'s type is not consistent with param dict " +
                  found?.type
              })
              return
            }
            // modify one by one
            let payload: EditParamType = {
              configType: mapFilenameToConfigType(filename!),
              filename: filename!,
              name: { value: found?.name!, label: found?.name! },
              type: found?.type!,
              newVal: value,
              inputType: found?.type!,
              defaultVal: found?.default_value,
              applyPolicy: found?.apply_policy || 'RollingRestart',
              risk_level: found?.risk_level
            }
            handleParamChange(payload)
          }
        })
        setBatchModify(false)
      } catch (e) {
        Modal.error({
          title: 'fail to read toml',
          content: String(e)
        })
      }
    },
    [files, filename]
  )

  const needInjectRegionSizeParams = () => {
    if (type !== 'upgrade') {
      return false
    }
    if (configuration === undefined) {
      return false
    }
    if (Object.keys(configuration?.files!).includes('tikv') === false) {
      return false
    }

    var existing = false
    Object.values(configuration?.files!['tikv'].all_parameters!).forEach(
      (param) => {
        if (
          injectedRegionSizeParams.some(
            (injectedParam) => injectedParam.name.value === param.name
          )
        ) {
          existing = true
        }
      }
    )
    if (existing) {
      return false
    }

    const fromVersion = configuration?.version
    const ToVersion = previewParam.version?.to_version

    if (fromVersion !== undefined && ToVersion !== undefined) {
      const baseFrom = semver.coerce(fromVersion)
      const baseTo = semver.coerce(ToVersion)

      if (baseFrom && baseTo) {
        if (semver.lt(baseFrom, '8.5.0') && semver.gte(baseTo, '8.5.0')) {
          return true
        }
      } else {
        Modal.error({
          title: 'Failed to parse version',
          content: 'Failed to parse version'
        })
      }
    }
    return false
  }

  useEffect(() => {
    if (needInjectRegionSizeParams()) {
      injectedRegionSizeParams.forEach((param) => {
        handleParamChange(param)
      })
      setFilename('tikv')
    }
  }, [configuration, type])

  return (
    <div>
      {changeable && changeable.changeable === false && (
        <Alert
          style={{ marginBottom: '5px' }}
          message={
            'You cannot change the config currently, because ' +
            changeable.reason
          }
          type="warning"
          showIcon
        />
      )}

      {type === 'upgrade' && (
        <Alert
          style={{ marginBottom: '5px' }}
          message={
            'Preview the cluster configuration after upgrade. You can also edit them and the modification will be applied together with the upgrade.'
          }
          type="info"
          showIcon
        />
      )}

      {needInjectRegionSizeParams() && (
        <div>
          <Alert
            message="WARNING"
            description={
              <span>
                Injected region size related parameters for TiKV as part of the
                upgrade to v8.5.0+. Includes:
                <ul>
                  <li>* coprocessor.region-split-size</li>
                </ul>
                More details see:{' '}
                <a
                  href="https://pingcap.feishu.cn/wiki/Jgz7wYF25iPxrzkRN6LcSBoInVd"
                  target="_blank"
                  rel="noopener noreferrer"
                >
                  SOP on Region-Size: Upgrading clusters to v8.5.x
                </a>
                .
              </span>
            }
            type="warning"
            showIcon
          />
          <br></br>
        </div>
      )}

      <div
        style={{
          display: 'flex',
          justifyContent: 'space-between',
          alignItems: 'center'
        }}
      >
        <div style={{ display: 'flex', gap: '10px' }}>
          <Select
            options={Object.keys(files || {}).map((v) => {
              return { value: v, label: v }
            })}
            value={filename}
            style={{ width: '18em', alignSelf: 'flex-start' }}
            onChange={(e: string) => {
              setFilename(e)
            }}
          ></Select>
          <Input
            style={{ width: '300px' }}
            placeholder="config key name to search"
            onChange={(e) => {
              setSearchInput(e.target.value)
            }}
          ></Input>
          <Checkbox
            onChange={(e) => {
              if (e.target.checked) {
                setOnlyShowModified(true)
              } else {
                setOnlyShowModified(false)
              }
            }}
          >
            only show modified params
          </Checkbox>
        </div>
        <div style={{ display: 'flex', gap: '10px' }}>
          <Button
            type="primary"
            onClick={() => {
              setModalEditInitial({
                filename: filename,
                configType: mapFilenameToConfigType(filename!)
              })
              setModalEdit(true)
            }}
            disabled={filename === 'INIT_SQL'}
          >
            More Configs
          </Button>
          {type === 'cluster' && (
            <Button
              icon={<SaveOutlined />}
              onClick={() => {
                setModalExport(true)
              }}
            >
              Export
            </Button>
          )}
          <Button
            icon={<SaveOutlined />}
            onClick={() => {
              setBatchModify(true)
            }}
            disabled={filename === 'INIT_SQL'}
          >
            Batch Modify by Toml
          </Button>
        </div>
      </div>
      <br></br>
      <Table
        dataSource={mapToList(files || {})
          .filter((v) => {
            return v.filename === filename
          })
          .filter((v) => {
            return v.param.name?.includes(searchInput)
          })
          .filter((v) => {
            if (onlyShowModified) {
              let origin = getActualValue(
                configuration!.files!,
                v.filename,
                v.param.name!
              )
              origin =
                origin !== undefined && origin !== null
                  ? origin
                  : getDefaultValue(files!, v.filename, v.param.name!)
              return (
                convertToString(
                  getActualValue(files!, v.filename, v.param.name!)
                ) !== convertToString(origin)
              )
            } else {
              return true
            }
          })
          .sort((a, b) => {
            if (a.param.source === b.param.source) {
              if (
                getActualValue(files!, a.filename, a.param.name!) ===
                  getActualValue(
                    configuration?.files!,
                    a.filename,
                    a.param.name!
                  ) &&
                getActualValue(files!, b.filename, b.param.name!) ===
                  getActualValue(
                    configuration?.files!,
                    b.filename,
                    b.param.name!
                  )
              ) {
                return 0
              } else if (
                getActualValue(files!, a.filename, a.param.name!) ===
                getActualValue(configuration?.files!, a.filename, a.param.name!)
              ) {
                return 1
              } else {
                return -1
              }
            } else if (a.param.source === 'custom') {
              return -1
            } else {
              return 1
            }
          })}
        columns={columns}
        pagination={{ pageSize: 10, position: ['bottomLeft'] }}
      />
      <div
        className="flex-container"
        style={{ justifyContent: 'flex-end', display: 'flex' }}
      >
        <div>
          {filename === 'INIT_SQL' ? (
            <Button disabled>Read-only</Button>
          ) : (
            <Button
              type="primary"
              onClick={() => {
                commit()
              }}
              disabled={files === undefined}
            >
              Preview Changes
            </Button>
          )}
        </div>
      </div>
      <ModalForm<EditParamType>
        visible={!!modalEdit}
        name="editParam"
        title={'Edit Parameter'}
        getFields={renderEditParamFields}
        onSubmit={handleEdit}
        onCancel={handleCancelEdit}
        width={600}
        destoryOnClose={true}
        okText="OK"
      />
      <ModalForm<PreviewFormType>
        visible={!!modalCommit}
        name="preview"
        title={'Preview of Change'}
        getFields={renderPreviewParamFields}
        onSubmit={handleApply}
        onCancel={handleCancelPreview}
        width={1000}
        okText="Commit"
      />
      <Modal
        visible={!!modalExport}
        title={'Configuration'}
        onCancel={() => setModalExport(false)}
        footer={null}
      >
        <ExportInfo configuration={configuration}></ExportInfo>
      </Modal>
      <ModalForm<BatchModifyFormType>
        visible={!!batchModify}
        name="batch"
        title={'Batch Modify by Toml'}
        getFields={renderBatchModifyFields}
        onSubmit={handleBatchModify}
        onCancel={handleCancelBatchModify}
        width={1000}
        okText="Commit"
      />
    </div>
  )
}

export type UnifyParam = {
  key: string
  filename: string
  param: CloudconfigUnifiedParameter
  configtype: string
}

const mapToList = (m: CloudconfigConfigurationFiles) => {
  var l: UnifyParam[] = []
  Object.keys(m).forEach((key) => {
    const fileAvailableParameterGroup = m[key]
    const fileValue = fileAvailableParameterGroup.all_parameters || {}
    Object.keys(fileValue).forEach((key2) => {
      const p = fileValue[key2]
      l.push({
        filename: key,
        param: p,
        key: key + '_' + p.name,
        configtype: fileAvailableParameterGroup.config_type || key
      })
    })
  })
  return l
}

export const getActualValue = (
  files: CloudconfigConfigurationFiles,
  filename: string,
  paramName: string
) => {
  if (!files) {
    return null
  }
  if (files[filename] === undefined || files[filename] === null) {
    return null
  } else if (
    files[filename].all_parameters === undefined ||
    files[filename].all_parameters === null
  ) {
    return null
  } else if (
    files[filename].all_parameters![paramName] === undefined ||
    files[filename].all_parameters![paramName] === null
  ) {
    return null
  } else {
    let p = files[filename].all_parameters![paramName]
    if (p.source === 'custom') {
      return p.custom?.value
    } else if (p.source === 'template') {
      return p.template?.value
    } else {
      return p.attribute?.default_value
    }
  }
}

const getDefaultValue = (
  files: CloudconfigConfigurationFiles,
  filename: string,
  paramName: string
) => {
  if (files[filename] === undefined || files[filename] === null) {
    return null
  } else if (
    files[filename].all_parameters === undefined ||
    files[filename].all_parameters === null
  ) {
    return null
  } else if (
    files[filename].all_parameters![paramName] === undefined ||
    files[filename].all_parameters![paramName] === null
  ) {
    return null
  } else {
    let p = files[filename].all_parameters![paramName]
    return p.attribute?.default_value
  }
}

export const parseFormValue = (type: string | undefined, val: string) => {
  if (type === 'bool') {
    return val === 'true'
  } else if (type === 'float') {
    return parseFloat(val)
  } else if (type === 'int') {
    return parseInt(val)
  } else if (type === 'json') {
    if (val == '') {
      return null
    }
    return JSON.parse(val)
  } else if (type === 'string') {
    return val
  } else {
    throw new Error('unknown type, please choose type manually')
  }
}

export function flattenObject(
  obj: any,
  prefix = '',
  res: Record<string, any> = {}
) {
  for (const key in obj) {
    if (Object.prototype.hasOwnProperty.call(obj, key)) {
      const value = obj[key]
      const newKey = prefix ? `${prefix}.${key}` : key
      if (
        value !== null &&
        typeof value === 'object' &&
        !Array.isArray(value)
      ) {
        flattenObject(value, newKey, res)
      } else {
        res[newKey] = value
      }
    }
  }
  return res
}

export function typeCheck(value: any, type: string) {
  if (type === 'bool') {
    return typeof value === 'boolean'
  } else if (type === 'float') {
    return typeof value === 'number' && !Number.isNaN(value)
  } else if (type === 'int') {
    return typeof value === 'number' && Number.isInteger(value)
  } else if (type === 'json') {
    // TOML 解析出来的对象/数组都可以
    return typeof value === 'object' && value !== null
  } else if (type === 'string') {
    return typeof value === 'string'
  } else {
    return false
  }
}

function typeInfer(value: any): 'bool' | 'float' | 'int' | 'json' | 'string' {
  if (typeof value === 'boolean') {
    return 'bool'
  }
  if (typeof value === 'number') {
    return Number.isInteger(value) ? 'int' : 'float'
  }
  if (value !== null && typeof value === 'object') {
    return 'json'
  }
  return 'string'
}

export default CurrentConfig
