<script setup>
import { computed, reactive, ref, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import Copy from '@/components/icons/Copy.vue'
import Save from '@/components/icons/Save.vue'
import { useThemeVars } from 'naive-ui'
import { formatTypes } from '@/consts/value_view_type.js'
import { types as redisTypes } from '@/consts/support_redis_type.js'
import { ClipboardSetText } from 'wailsjs/runtime/runtime.js'
import { isEmpty, toLower } from 'lodash'
import useBrowserStore from 'stores/browser.js'
import { decodeRedisKey } from '@/utils/key_convert.js'
import FormatSelector from '@/components/content_value/FormatSelector.vue'
import ContentEditor from '@/components/content_value/ContentEditor.vue'
import { formatBytes } from '@/utils/byte_convert.js'

const props = defineProps({
    name: String,
    db: Number,
    keyPath: String,
    keyCode: {
        type: Array,
        default: null,
    },
    ttl: {
        type: Number,
        default: -1,
    },
    value: [String, Array],
    format: {
        type: String,
    },
    decode: {
        type: String,
    },
    size: Number,
    length: Number,
    loading: Boolean,
})

const i18n = useI18n()
const themeVars = useThemeVars()

/**
 *
 * @type {ComputedRef<string|number[]>}
 */
const keyName = computed(() => {
    return !isEmpty(props.keyCode) ? props.keyCode : props.keyPath
})

const keyType = redisTypes.STRING
const viewLanguage = computed(() => {
    switch (viewAs.format) {
        case formatTypes.JSON:
        case formatTypes.UNICODE_JSON:
            return 'json'
        case formatTypes.YAML:
            return 'yaml'
        case formatTypes.XML:
            return 'xml'
        default:
            return 'plaintext'
    }
})

const viewAs = reactive({
    value: '',
    format: '',
    decode: '',
})

const editingContent = ref('')
const resetKey = ref('')

const enableSave = computed(() => {
    return editingContent.value !== viewAs.value && !props.loading
})

const displayValue = computed(() => {
    return viewAs.value || decodeRedisKey(props.value) || ''
})

const showMemoryUsage = computed(() => {
    return !isNaN(props.size) && props.size > 0
})

watch(
    () => props.value,
    (val) => {
        if (!isEmpty(val)) {
            onFormatChanged(viewAs.decode, viewAs.format)
        }
    },
)

const converting = ref(false)
const onFormatChanged = async (decode = '', format = '') => {
    try {
        converting.value = true
        const {
            value,
            decode: retDecode,
            format: retFormat,
        } = await browserStore.convertValue({
            value: props.value,
            decode: decode || props.decode,
            format: format || props.format,
        })
        editingContent.value = viewAs.value = value
        viewAs.decode = decode || retDecode
        viewAs.format = format || retFormat
        browserStore.setSelectedFormat(props.name, props.keyPath, props.db, viewAs.format, viewAs.decode)
        resetKey.value = Date.now().toString()
    } finally {
        converting.value = false
    }
}

/**
 * Copy value
 */
const onCopyValue = () => {
    ClipboardSetText(displayValue.value)
        .then((succ) => {
            if (succ) {
                $message.success(i18n.t('interface.copy_succ'))
            }
        })
        .catch((e) => {
            $message.error(e.message)
        })
}

/**
 * Save value
 */
const browserStore = useBrowserStore()
const saving = ref(false)

const onInput = (content) => {
    editingContent.value = content
}

const onSave = async () => {
    saving.value = true
    try {
        const { success, msg } = await browserStore.setKey({
            server: props.name,
            db: props.db,
            key: keyName.value,
            keyType: toLower(keyType),
            value: editingContent.value,
            ttl: -1,
            format: viewAs.format,
            decode: viewAs.decode,
        })
        if (success) {
            viewAs.value = editingContent.value
            $message.success(i18n.t('interface.save_value_succ'))
        } else {
            $message.error(msg)
        }
    } catch (e) {
        $message.error(e.message)
    } finally {
        saving.value = false
    }
}

defineExpose({
    reset: () => {
        viewAs.value = ''
        viewAs.decode = ''
        viewAs.format = ''
        editingContent.value = ''
    },
})
</script>

<template>
    <div class="content-wrapper flex-box-v">
        <slot name="toolbar" />
        <div class="tb2 value-item-part flex-box-h">
            <div class="flex-item-expand"></div>
            <n-button-group>
                <n-button :disabled="saving" :focusable="false" @click="onCopyValue">
                    <template #icon>
                        <n-icon :component="Copy" size="18" />
                    </template>
                    {{ $t('interface.copy_value') }}
                </n-button>
                <n-button
                    :disabled="!enableSave"
                    :loading="saving"
                    :secondary="enableSave"
                    :type="enableSave ? 'primary' : ''"
                    @click="onSave">
                    <template #icon>
                        <n-icon :component="Save" size="18" />
                    </template>
                    {{ $t('common.save') }}
                </n-button>
            </n-button-group>
        </div>
        <div class="value-wrapper value-item-part flex-item-expand flex-box-v">
            <content-editor
                :content="displayValue"
                :language="viewLanguage"
                :loading="props.loading"
                :offset-key="props.keyPath"
                :reset-key="resetKey"
                class="flex-item-expand"
                keep-offset
                style="height: 100%"
                @input="onInput"
                @reset="onInput"
                @save="onSave" />
            <n-spin v-show="props.loading || converting" />
        </div>
        <div class="value-footer flex-box-h">
            <n-text v-if="!isNaN(props.length)">{{ $t('interface.length') }}: {{ props.length }}</n-text>
            <n-divider v-if="showMemoryUsage" vertical />
            <n-text v-if="showMemoryUsage">{{ $t('interface.memory_usage') }}: {{ formatBytes(props.size) }}</n-text>
            <div class="flex-item-expand" />
            <format-selector
                :decode="viewAs.decode"
                :disabled="enableSave"
                :format="viewAs.format"
                @format-changed="onFormatChanged" />
        </div>
    </div>
</template>

<style lang="scss" scoped>
.value-wrapper {
    //overflow: hidden;
    border-top: v-bind('themeVars.borderColor') 1px solid;
    padding: 5px;
}

.value-footer {
    border-top: v-bind('themeVars.borderColor') 1px solid;
    background-color: v-bind('themeVars.tableHeaderColor');
}
</style>
