<script lang="ts" setup>
import { propTypes } from '@/utils/propTypes'
import { onBeforeMount, watch, toRefs, PropType } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import ApiVariable from './ApiVariable.vue'
import CodeEdit from './CodeEdit.vue'
import Convert from './convert.js'
import { KeyValue, BODY_TYPE } from './ApiTestModel.js'
export interface ApiBodyItem {
  raw?: string
  typeChange: string
  format?: string
  jsonSchema?: string
  type?: string
  kvs: Item[]
}

export interface Item {
  name: string
  value: string
  description: string
  type: string
}
const props = defineProps({
  isReadOnly: propTypes.bool.def(false),
  isShowEnable: propTypes.bool.def(false),
  body: {
    type: Object as PropType<ApiBodyItem>,
    default: () => ({
      raw: '',
      typeChange: '',
      format: '',
      jsonSchema: '',
      type: '',
      kvs: []
    })
  },
  headers: {
    type: Array as PropType<Item[]>,
    default: () => []
  },
  valueList: {
    type: Array as PropType<Item[]>,
    default: () => []
  }
})
const { t } = useI18n()
const modes = ['text', 'json', 'xml', 'html']
const hasOwnProperty = Object.prototype.hasOwnProperty
const propIsEnumerable = Object.prototype.propertyIsEnumerable
const { body: apiBody, headers } = toRefs(props)

watch(
  () => apiBody.value.raw,
  () => {
    if (apiBody.value.format !== 'JSON-SCHEMA' && apiBody.value.raw) {
      try {
        const MsConvert = new Convert()
        const data = MsConvert.format(JSON.parse(apiBody.value.raw))
        if (apiBody.value.jsonSchema) {
          apiBody.value.jsonSchema = deepAssign(data)
        } else {
          apiBody.value.jsonSchema = data
        }
      } catch (ex) {
        apiBody.value.jsonSchema = ''
      }
    }
  }
)

onBeforeMount(() => {
  if (!apiBody.value.type) {
    apiBody.value.type = BODY_TYPE.FORM_DATA
  }
  if (apiBody.value.kvs) {
    apiBody.value.kvs.forEach(param => {
      if (!param.type) {
        param.type = 'text'
      }
    })
  }
})

const isObj = x => {
  const type = typeof x
  return x !== null && (type === 'object' || type === 'function')
}

const toObject = val => {
  if (val === null || val === undefined) {
    return
  }
  return Object(val)
}

const assignKey = (to, from, key) => {
  const val = from[key]
  if (val === undefined || val === null) {
    return
  }

  if (!hasOwnProperty.call(to, key) || !isObj(val)) {
    to[key] = val
  } else {
    to[key] = assign(Object(to[key]), from[key])
  }
}

const assign = (to, from) => {
  if (to === from) {
    return to
  }
  from = Object(from)
  for (const key in from) {
    if (hasOwnProperty.call(from, key)) {
      assignKey(to, from, key)
    }
  }

  if (Object.getOwnPropertySymbols) {
    const symbols = Object.getOwnPropertySymbols(from)

    for (let i = 0; i < symbols.length; i++) {
      if (propIsEnumerable.call(from, symbols[i])) {
        assignKey(to, from, symbols[i])
      }
    }
  }

  return to
}

const deepAssign = function (target) {
  target = toObject(target)
  for (let s = 1; s < arguments.length; s++) {
    assign(target, arguments[s])
  }
  return target
}
const modeChange = () => {
  switch (apiBody.value.type) {
    case 'JSON':
      setContentType('application/json')
      break
    case 'XML':
      setContentType('text/xml')
      break
    case 'WWW_FORM':
      setContentType('application/x-www-form-urlencoded')
      break
    case 'BINARY':
      setContentType('application/octet-stream')
      break
    default:
      removeContentType()
      break
  }
}

const setContentType = value => {
  let isType = false
  headers.value.forEach(item => {
    if (item.name === 'Content-Type') {
      item.value = value
      isType = true
    }
  })
  if (!isType) {
    headers.value.unshift(new KeyValue({ name: 'Content-Type', value: value }))
  }
}
const removeContentType = () => {
  for (const index in headers.value) {
    if (headers.value[index].name === 'Content-Type') {
      headers.value.splice(parseInt(index), 1)
      emits('headersChange')
      return
    }
  }
}
const changeParameters = val => {
  if (!isNaN(val)) {
    apiBody.value.kvs.splice(val, 1)
  } else {
    apiBody.value.kvs.push(val)
  }
}

const emits = defineEmits(['headersChange'])
</script>

<template>
  <div class="radio-group_api">
    <el-radio-group v-model="apiBody.type">
      <el-radio :disabled="isReadOnly" :label="BODY_TYPE.FORM_DATA" @change="modeChange">
        {{ t('datasource.body_form_data') }}
      </el-radio>

      <el-radio :disabled="isReadOnly" :label="BODY_TYPE.WWW_FORM" @change="modeChange">
        {{ t('datasource.body_x_www_from_urlencoded') }}
      </el-radio>

      <el-radio :disabled="isReadOnly" :label="BODY_TYPE.JSON" @change="modeChange">
        {{ t('datasource.body_json') }}
      </el-radio>

      <el-radio :disabled="isReadOnly" :label="BODY_TYPE.XML" @change="modeChange">
        {{ t('datasource.body_xml') }}
      </el-radio>

      <el-radio :disabled="isReadOnly" :label="BODY_TYPE.RAW" @change="modeChange">
        {{ t('datasource.body_raw') }}
      </el-radio>
    </el-radio-group>
    <div style="padding-top: 16px" v-if="apiBody.type == 'Form_Data' || apiBody.type == 'WWW_FORM'">
      <api-variable
        :is-read-only="isReadOnly"
        :parameters="apiBody.kvs"
        :is-show-enable="isShowEnable"
        type="body"
        @change-parameters="changeParameters"
        :value-list="valueList"
      />
    </div>
    <div v-if="apiBody.type == 'JSON'" class="ms-body">
      <code-edit
        ref="codeEdit"
        :read-only="isReadOnly"
        v-model="apiBody.raw"
        class="api-body-code"
        :data="apiBody.raw"
        :modes="modes"
        mode="json"
        height="200px"
      />
    </div>

    <div v-if="apiBody.type == 'XML'" class="ms-body">
      <code-edit
        ref="codeEdit"
        :read-only="isReadOnly"
        class="api-body-code"
        v-model="apiBody.raw"
        :data="apiBody.raw"
        height="200px"
        mode="xml"
        :modes="modes"
      />
    </div>

    <div v-if="apiBody.type == 'Raw'" class="ms-body">
      <code-edit
        ref="codeEdit"
        :read-only="isReadOnly"
        v-model="apiBody.raw"
        :data="apiBody.raw"
        height="200px"
        class="api-body-code"
        :modes="modes"
      />
    </div>
  </div>
</template>

<style lang="less" scoped>
.radio-group_api {
  :deep(.ed-radio) {
    height: 22px !important;
  }
}
.ms-body {
  padding: 15px 0;
}
</style>

<style lang="less">
.ace_print-margin {
  display: none;
}

.api-body-code {
  border-radius: 4px;
  border: 1px solid #bbbfc4;
}
</style>
