/**
 * @fileoverview
 * Global mixin providing access to the LabThings (WoT) API.
 *
 * This mixin is registered globally in `main.js` using. Do not
 * manually import it in components.
 */
import axios from "axios";
import { useWotStore } from "@/stores/wot.js";
import { useSettingsStore } from "@/stores/settings.js";
import { mapState, mapStores } from "pinia";

export default {
  data() {
    return {
      pollTimers: {},
    };
  },

  computed: {
    ...mapState(useSettingsStore, ["baseUri"]),
    ...mapStores(useWotStore),
  },

  methods: {
    thingDescription(thing) {
      return this.wotStore.thingDescriptions[thing];
    },
    thingList() {
      return this.wotStore.thingList();
    },
    thingAvailable(thing) {
      return this.wotStore.thingAvailable(thing);
    },
    thingPropertyUrl(thing, property, allowUndefined = false) {
      return this.wotStore.thingPropertyUrl(thing, property, "readproperty", allowUndefined);
    },
    thingActionUrl(thing, action, allowUndefined = false) {
      return this.wotStore.thingActionUrl(thing, action, "invokeaction", allowUndefined);
    },
    thingActionAvailable(thing, action) {
      return this.wotStore.thingAffordanceAvailable(thing, "actions", action);
    },
    thingPropertyAvailable(thing, property) {
      return this.wotStore.thingAffordanceAvailable(thing, "properties", property);
    },
    async readThingProperty(thing, property, silenceErrors = false) {
      let url = this.wotStore.thingPropertyUrl(thing, property, "readproperty", false);
      try {
        let response = await axios.get(url);
        return response.data;
      } catch (error) {
        if (!silenceErrors) this.modalError(error);
        return undefined;
      }
    },
    async writeThingProperty(thing, property, value) {
      let url = this.wotStore.thingPropertyUrl(thing, property, "writeproperty", false);
      // `false` and 0 fail because axios turns it to ""
      // Other values should not be stringified or pydantic
      // can't parse them.
      if (value === false || value === true || value === 0) {
        value = JSON.stringify(value);
      }
      await axios.put(url, value);
    },
    async invokeAction(thing, action, data, handleErrors = true) {
      let url = this.thingActionUrl(thing, action);
      try {
        let response = await axios.post(url, data);
        return response;
      } catch (error) {
        if (handleErrors) {
          this.modalError(error);
          return undefined;
        } else {
          throw error;
        }
      }
    },
    async pollUntilComplete(
      taskUrl,
      ongoingMethod,
      finalMethod,
      interval = 500,
      modalErrors = true,
    ) {
      let response;
      let finalMethodCalled = false;
      try {
        response = await axios.get(taskUrl, { baseURL: this.baseUri });
        const result = response.data.status;

        if (result === "running" || result === "pending") {
          ongoingMethod?.(response);
          this.pollTimers[taskUrl] = setTimeout(() => {
            this.pollUntilComplete(taskUrl, ongoingMethod, finalMethod, interval);
          }, interval);
        } else {
          clearTimeout(this.pollTimers[taskUrl]);
          delete this.pollTimers[taskUrl];
          finalMethodCalled = true;
          finalMethod?.(response);
        }
      } catch (error) {
        this.$emit("error", error);
        if (modalErrors) {
          this.modalError(error);
        }
        clearTimeout(this.pollTimers[taskUrl]);
        delete this.pollTimers[taskUrl];
        if (!finalMethodCalled) {
          finalMethod?.(response);
        }
      }
    },
    terminateAction(taskUrl) {
      axios.delete(taskUrl, { baseURL: this.baseUri });
    },
    async findOngoingActions(thing, action) {
      let url = this.thingActionUrl(thing, action);
      try {
        return await axios.get(url);
      } catch (error) {
        console.warn("checkExistingTasks: request failed", error);
        return null;
      }
    },
    /**
     * Find and ongoing Action and return its task response.
     *
     * Returns null if there is no ongoing action
     */
    async getOngoingAction(thing, action) {
      let response = await this.findOngoingActions(thing, action);
      // Exit if response is null, due to an error.
      if (response == null) return null;
      // Check for a task that is ongoing.
      // We can't handle multiple tasks ongoing, so this picks the first.
      // ?? Is the "Nullish Coalescing-Operator" to turn undefined into null.
      return response.data.find((t) => ["pending", "running"].includes(t.status)) ?? null;
    },

    async getThingEndpoint(thing, endpoint) {
      let url = `${this.baseUri}/${thing}/${endpoint}`;
      try {
        const response = await axios.get(url);
        return response.data;
      } catch {
        return undefined;
      }
    },
  },
};
