<script setup>
  import { ref, reactive, onMounted, onUnmounted } from 'vue'
  import { inject } from 'vue'

  const mqttRef = inject('mqtt')

  import Accordion from 'primevue/accordion'
  import AccordionPanel from 'primevue/accordionpanel'
  import AccordionHeader from 'primevue/accordionheader'
  import AccordionContent from 'primevue/accordioncontent'
  import Dialog from 'primevue/dialog'

  import { JsonForms } from '@jsonforms/vue'
  import { createAjv } from '@jsonforms/core'
  import { primeVueRenderers } from 'jsonforms-primevue'

  import { useToast } from 'primevue/usetoast'

  const toast = useToast()

  const renderers = Object.freeze([
    ...primeVueRenderers,
    // here you can add custom renderers
  ])

  const props = defineProps({
    deviceId: String,
  })

  const filters = ref({})
  const commands = ref([])
  const commandMap = reactive({})
  const commandByResponseId = reactive({})

  const defaultsAjv = createAjv({ useDefaults: true })
  // Suppress Ajv unknown format warning by registering a no-op validator for "textarea"
  defaultsAjv.addFormat('textarea', true)

  function rebuildList() {
    commands.value = Object.values(commandMap)
  }

  function updateCommand(device, name, field, value) {
    if (!commandMap[name]) {
      commandMap[name] = {
        name,
        description: 'No description provided.',
        schema: {},
        input: {},
        response: null,
      }
    }

    commandMap[name][field] = value
    rebuildList()
  }

  function sendCommand(cmd) {
    cmd.responseId = null
    cmd.response = {}

    const topic = `device/${props.deviceId}/command/${cmd.name}`
    var payload = JSON.stringify(cmd.input, null, 2)

    var responseId = generateId()
    commandByResponseId[responseId] = cmd

    mqttRef.value.publish(topic, payload || '{}', {
      qos: 1,
      properties: {
        responseTopic: `client/${mqttRef.value.clientId}/responses`,
        correlationData: new TextEncoder().encode(responseId),
      },
    })
  }

  function generateId() {
    return Math.random().toString(16).substr(2, 8)
  }

  const schemaDialog = reactive({
    visible: false,
    content: '',
  })

  function showSchema(cmd) {
    try {
      schemaDialog.content = JSON.stringify(cmd.schema, null, 2)
    } catch {
      schemaDialog.content = cmd.schema || 'No schema available'
    }
    schemaDialog.visible = true
  }

  onMounted(() => {
    mqttRef.value.subscribe(`device/${props.deviceId}/command/#`, (payload, topic) => {
      const parts = topic.split('/')

      const device = parts[1]
      if (device !== props.deviceId) return

      const command = parts[3]
      const field = parts[4]

      console.log(topic, payload)

      if (field === '$description') {
        updateCommand(device, command, 'description', payload)
      }
      if (field === '$schema') {
        const schema = JSON.parse(payload)
        updateCommand(device, command, 'schema', schema)
      }
    })

    mqttRef.value.subscribe(`client/${mqttRef.value.clientId}/responses`, (payload, topic) => {
      let response = JSON.parse(payload)

      const responseId = response.correlation
      const cmd = commandByResponseId[responseId]

      if (cmd) {
        cmd.response = response

        toast.add({
          severity: response.success ? 'success' : 'error',
          summary: cmd.name,
          detail:
            response.message !== 'None'
              ? response.message
              : response.success
                ? 'Success'
                : 'Failed',
          life: 4000,
        })

        delete commandByResponseId[responseId]
      }
    })
  })

  onUnmounted(() => {
    mqttRef.value.unsubscribe(`device/${props.deviceId}/command/#`)
    mqttRef.value.unsubscribe(`client/${mqttRef.value.clientId}/responses`)
  })

  const jsonFormsConfig = {
    validationMode: 'ValidateOnTouched',
    showAllErrors: false,
    showErrorsOnTouched: true,
  }
</script>

<template>
  <Dialog
    v-model:visible="schemaDialog.visible"
    header="Command Schema"
    :modal="true"
    :closable="true"
    :style="{ width: '700px' }"
  >
    <pre>{{ schemaDialog.content }}</pre>
  </Dialog>
  <div class="layout-card col-item-2">
    <DataView :value="commands">
      <template #header>
        <div class="flex products-header">
          <span class="products-title">Commands</span>
          <IconField class="search-field">
            <InputIcon class="pi pi-search" />
            <InputText v-model="filters['global']" placeholder="Filter by..." />
          </IconField>
        </div>
      </template>
      <template #empty> No commands available.</template>
      <template #list="slotProps">
        <Accordion>
          <AccordionPanel v-for="cmd in slotProps.items" :key="cmd.name" :value="cmd.name">
            <AccordionHeader>
              <div class="command-header">
                <span class="command-name">{{ cmd.name }}</span>
                <span class="command-desc">{{ cmd.description }}</span>
              </div>
            </AccordionHeader>
            <AccordionContent>
              <div class="command-content">
                <!-- Left: Code editor -->
                <JsonForms
                  :data="cmd.input"
                  :schema="cmd.schema"
                  :renderers="renderers"
                  :config="jsonFormsConfig"
                  :onChange="({ data, errors }) => (cmd.input = data)"
                  :ajv="defaultsAjv"
                />

                <!-- Right: Buttons -->
                <div class="button-container">
                  <Button
                    v-if="cmd.response"
                    :icon="cmd.response.success ? 'pi pi-check' : 'pi pi-times'"
                    class="p-button-rounded p-button-text"
                    :class="cmd.response.success ? 'p-button-success' : 'p-button-danger'"
                    v-tooltip="cmd.response.message !== 'None' ? cmd.response.message : null"
                  />
                  <Button
                    icon="pi pi-question-circle"
                    class="p-mb-2"
                    label="Help"
                    severity="secondary"
                    @click="showSchema(cmd)"
                    tooltip="Show schema"
                  />
                  <Button
                    icon="pi pi-send"
                    label="Send"
                    @click="sendCommand(cmd)"
                    tooltip="Send command"
                  />
                </div>
              </div>
            </AccordionContent>
          </AccordionPanel>
        </Accordion>
      </template>
    </DataView>
  </div>
</template>

<style scoped>
  .command-header {
    display: flex;
    gap: 10px;
    align-items: center;
  }

  .command-name {
    font-weight: 600;
  }

  .command-desc {
    font-size: 0.8rem;
  }

  .command-content {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  .editor-container {
    flex: 1;
  }

  .button-container {
    display: flex;
    flex-direction: row;
    gap: 0.5rem;
    justify-content: flex-end;
  }
</style>
