<script setup lang="ts">
// vue 3 + vite use MQTT.js refer to https://github.com/mqttjs/MQTT.js/issues/1269
import * as mqtt from "mqtt/dist/mqtt.min";
import { reactive, ref, onUnmounted } from "vue";

const protocol = location.protocol === "https:" ? "wss" : "ws";
const port = protocol === "wss" ? 8084 : 8083;

// https://github.com/mqttjs/MQTT.js#qos
const qosList = [0, 1, 2];

const connection = reactive({
  protocol,
  host: "broker.emqx.io",
  port,
  clientId: "emqx_vue3_" + Math.random().toString(16).substring(2, 8),
  username: "emqx_test",
  password: "emqx_test",
  clean: true,
  connectTimeout: 30 * 1000, // ms
  reconnectPeriod: 4000 // ms
  // for more options and details, please refer to https://github.com/mqttjs/MQTT.js#mqttclientstreambuilder-options
});

// 订阅 topic/mqttx 主题
const subscription = ref({
  topic: "topic/mqttx",
  qos: 0 as any
});

// 发布 topic/browser 主题
const publish = ref({
  topic: "topic/browser",
  qos: 0 as any,
  payload: '{ "msg": "Hello, I am browser." }'
});

let client = ref({
  connected: false
} as mqtt.MqttClient);

const receivedMessages = ref("");
const subscribedSuccess = ref(false);
const btnLoadingType = ref("");
const retryTimes = ref(0);

const initData = () => {
  client.value = {
    connected: false
  } as mqtt.MqttClient;
  retryTimes.value = 0;
  btnLoadingType.value = "";
  subscribedSuccess.value = false;
};

const handleOnReConnect = () => {
  retryTimes.value += 1;
  if (retryTimes.value > 5) {
    try {
      client.value.end();
      initData();
      console.log("connection maxReconnectTimes limit, stop retry");
    } catch (error) {
      console.log("handleOnReConnect catch error:", error);
    }
  }
};

const createConnection = () => {
  try {
    btnLoadingType.value = "connect";
    const { protocol, host, port, ...options } = connection;
    const connectUrl = `${protocol}://${host}:${port}/mqtt`;
    // 连接MQTT 服务器
    client.value = mqtt.connect(connectUrl, options);

    if (client.value.on) {
      // https://github.com/mqttjs/MQTT.js#event-connect
      client.value.on("connect", () => {
        btnLoadingType.value = "";
        console.log("connection successful");
      });

      // https://github.com/mqttjs/MQTT.js#event-reconnect
      client.value.on("reconnect", handleOnReConnect);

      // https://github.com/mqttjs/MQTT.js#event-error
      client.value.on("error", error => {
        console.log("connection error:", error);
      });

      // https://github.com/mqttjs/MQTT.js#event-message
      client.value.on("message", (topic: string, message) => {
        receivedMessages.value = receivedMessages.value.concat(
          message.toString()
        );
        console.log(`received message: ${message} from topic: ${topic}`);
      });
    }
  } catch (error) {
    btnLoadingType.value = "";
    console.log("mqtt.connect error:", error);
  }
};

// subscribe topic
// https://github.com/mqttjs/MQTT.js#mqttclientsubscribetopictopic-arraytopic-object-options-callback
const doSubscribe = () => {
  btnLoadingType.value = "subscribe";
  const { topic, qos } = subscription.value;
  client.value.subscribe(
    topic,
    { qos },
    (error: Error, granted: mqtt.ISubscriptionGrant[]) => {
      btnLoadingType.value = "";
      if (error) {
        console.log("subscribe error:", error);
        return;
      }
      subscribedSuccess.value = true;
      console.log("subscribe successfully:", granted);
    }
  );
};

// unsubscribe topic
// https://github.com/mqttjs/MQTT.js#mqttclientunsubscribetopictopic-array-options-callback
const doUnSubscribe = () => {
  btnLoadingType.value = "unsubscribe";
  const { topic, qos } = subscription.value;
  client.value.unsubscribe(topic, { qos }, error => {
    btnLoadingType.value = "";
    subscribedSuccess.value = false;
    if (error) {
      console.log("unsubscribe error:", error);
      return;
    }
    console.log(`unsubscribed topic: ${topic}`);
  });
};

// publish message
// https://github.com/mqttjs/MQTT.js#mqttclientpublishtopic-message-options-callback
const doPublish = () => {
  btnLoadingType.value = "publish";
  const { topic, qos, payload } = publish.value;
  client.value.publish(topic, payload, { qos }, error => {
    btnLoadingType.value = "";
    if (error) {
      console.log("publish error:", error);
      return;
    }
    console.log(`published message: ${payload}`);
  });
};

// disconnect
// https://github.com/mqttjs/MQTT.js#mqttclientendforce-options-callback
const destroyConnection = () => {
  if (client.value.connected) {
    btnLoadingType.value = "disconnect";
    try {
      client.value.end(false, () => {
        initData();
        console.log("disconnected successfully");
      });
    } catch (error) {
      btnLoadingType.value = "";
      console.log("disconnect error:", error);
    }
  }
};

const handleProtocolChange = (value: string) => {
  connection.port = value === "wss" ? 8084 : 8083;
};

onUnmounted(() => {
  try {
    if (client.value.end) {
      client.value.end();
      console.log("disconnected successfully");
    }
  } catch (error) {
    console.log(error);
  }
});
</script>

<template>
  <el-card shadow="never" :body-style="{ padding: '20px' }">
    <template #header>
      <div>
        基于
        <el-link
          type="primary"
          underline="never"
          href="https://github.com/mqttjs/MQTT.js"
          target="_blank"
        >
          MQTT.js
        </el-link>
        和 免费的公共MQTT代理
        <el-link
          type="primary"
          underline="never"
          href="broker.emqx.io"
          target="_blank"
        >
          EMQX
        </el-link>
        实现的一套 MQTT 客户端
      </div>
      <el-link
        class="mt-2"
        href="https://github.com/pure-admin/vue-pure-admin/blob/main/src/views/able/mqtt-client.vue"
        target="_blank"
      >
        代码位置 src/views/able/mqtt-client.vue
      </el-link>
    </template>

    <el-card shadow="never">
      <h1>设置</h1>
      <el-form label-position="top" :model="connection">
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item prop="protocol" label="协议">
              <el-select
                v-model="connection.protocol"
                @change="handleProtocolChange"
              >
                <el-option label="ws://" value="ws" />
                <el-option label="wss://" value="wss" />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="host" label="主机">
              <el-input v-model="connection.host" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="port" label="端口">
              <el-input
                v-model.number="connection.port"
                type="number"
                placeholder="8083/8084"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="clientId" label="客户端ID">
              <el-input v-model="connection.clientId" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="username" label="用户名">
              <el-input v-model="connection.username" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="password" label="密码">
              <el-input v-model="connection.password" />
            </el-form-item>
          </el-col>

          <el-col :span="24">
            <el-button
              type="primary"
              :disabled="client.connected"
              :loading="btnLoadingType === 'connect'"
              @click="createConnection"
            >
              {{ client.connected ? "已连接" : "连接" }}
            </el-button>

            <el-button
              v-if="client.connected"
              type="danger"
              :loading="btnLoadingType === 'disconnect'"
              @click="destroyConnection"
            >
              断开连接
            </el-button>
          </el-col>
        </el-row>
      </el-form>
    </el-card>
    <el-card shadow="never" class="mt-4">
      <h1>订阅</h1>
      <el-form label-position="top" :model="subscription">
        <el-row :gutter="20" :align="'middle'">
          <el-col :span="8">
            <el-form-item prop="topic" label="主题">
              <el-input
                v-model="subscription.topic"
                :disabled="subscribedSuccess"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="qos" label="通信质量">
              <el-select
                v-model="subscription.qos"
                :disabled="subscribedSuccess"
              >
                <el-option
                  v-for="qos in qosList"
                  :key="qos"
                  :label="qos"
                  :value="qos"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
        <el-row>
          <el-col>
            <el-button
              type="primary"
              class="sub-btn"
              :loading="btnLoadingType === 'subscribe'"
              :disabled="!client.connected || subscribedSuccess"
              @click="doSubscribe"
            >
              {{ subscribedSuccess ? "已订阅" : "订阅" }}
            </el-button>
            <el-button
              v-if="subscribedSuccess"
              type="primary"
              class="sub-btn"
              :loading="btnLoadingType === 'unsubscribe'"
              :disabled="!client.connected"
              @click="doUnSubscribe"
            >
              取消订阅
            </el-button>
          </el-col>
        </el-row>
      </el-form>
    </el-card>
    <el-card shadow="never" class="mt-4">
      <h1>发布</h1>
      <el-form label-position="top" :model="publish">
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item prop="topic">
              <template #label>
                <span>主题</span>
                <el-text type="info" size="small">
                  可将订阅主题设置为topic/browser，测试MQTT的自发自收。
                </el-text>
              </template>
              <el-input v-model="publish.topic" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="payload" label="有效载荷">
              <el-input v-model="publish.payload" />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item prop="qos" label="通信质量">
              <el-select v-model="publish.qos">
                <el-option
                  v-for="qos in qosList"
                  :key="qos"
                  :label="qos"
                  :value="qos"
                />
              </el-select>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      <el-col :span="24">
        <el-button
          type="primary"
          :loading="btnLoadingType === 'publish'"
          :disabled="!client.connected"
          @click="doPublish"
        >
          发布
        </el-button>
      </el-col>
    </el-card>
    <el-card shadow="never" class="mt-4">
      <h1>接收</h1>
      <el-col :span="24">
        <el-input
          v-model="receivedMessages"
          type="textarea"
          :rows="3"
          readonly
        />
      </el-col>
    </el-card>
  </el-card>
</template>
