import { onMounted, ref, computed, watch, markRaw, inject } from "vue";
import { getDashboardDevice } from "@/apis/dashboard";
import useSearchParams from "@/hooks/useSearchParam";
import useSystemHeatmap from "./useSystemHeatmap";

export default function useSystemStatusByBaja(updateHeatBarIsShow) {
  const rawData = ref([]);
  const forgeViewer = ref(null);
  const urn = ref("");
  const { searchParams } = useSearchParams();

  const initialData = ref(null);

  const updateInitialData = (data = false) => {
    initialData.value = data;
  };

  const { updateHeatMapData, updateTemp, initHeatMap } =
    useSystemHeatmap(updateHeatBarIsShow);

  const updateForgeViewer = (viewer) => {
    if (!viewer) {
      forgeViewer.value = null;
      return;
    }
    forgeViewer.value = markRaw(viewer);
    initHeatMap(viewer);
  };

  const getSubPoint = (normal, close, error, sub_points) => {
    let points = {
      ...Object.fromEntries(sub_points.map((p) => [p, ""])),
    };
    if (normal) points[normal] = "";
    if (close) points[close] = "";
    if (error) points[error] = "";
    return points;
  };
  const subscribeData = ref({});

  watch(rawData, () => {
    let sub_data = {};

    rawData.value.forEach((d) => {
      sub_data = {
        ...sub_data,
        ...Object.fromEntries(
          d.device.map((dev) => [
            dev.device_number,
            {
              ...dev,
              labelText: d.labelText,
              show_value: d.labelText,
              device_normal_point_name: d.device_normal_point_name,
              device_close_point_name: d.device_close_point_name,
              device_error_point_name: d.device_error_point_name,
              device_normal_point_value: d.device_normal_point_value,
              device_close_point_value: d.device_close_point_value,
              device_error_point_value: d.device_error_point_value,
              device_normal_color: d.device_normal_color,
              device_close_color: d.device_close_color,
              device_error_color: d.device_error_color,
              forge_dbid: parseInt(dev.forge_dbid),
              device_coordinate_3d: dev.device_coordinate_3d
                ? JSON.parse(dev.device_coordinate_3d)
                : { x: 0, y: 0 },
              points: getSubPoint(
                d.device_normal_point_name,
                d.device_close_point_name,
                d.device_error_point_name,
                d.points
              ),
              is_show: true,
              currentColor: d.device_normal_color,
            },
          ])
        ),
      };
    });
    subscribeData.value = sub_data;
    updateHeatMapData(sub_data);
    updateSubscribeDataFromBaja(sub_data);
  });

  const visibleDbid = computed(() => {
    let visible = [];

    rawData.value.forEach((d) => {
      visible = [
        ...visible,
        ...d.device.map((dev) => parseInt(dev.forge_dbid)),
      ];
    });

    return visible;
  });

  const getDevice = async (option = 1) => {
    const res = await getDashboardDevice({
      option: parseInt(option),
    });
    rawData.value = res.data.map((d) => ({
      ...d,
      key: d.subSys,
    }));
  };

  // subscribe from baja
  const booleanPointFacets = ref({});
  const updateFacets = (point, facets) => {
    booleanPointFacets.value = {
      ...booleanPointFacets,
      [point]: facets,
    };
  };
  const updateDeviceData = (device_number, point, value) => {
    const correspondPoint = initialData.value.points.find(
      ({ name }) => name === point
    );
    // console.log("sub 回傳值 ", typeof value)
    const text = correspondPoint
      ? correspondPoint.values.find(
          ({ value: pValue }) => pValue === parseInt(value)
        )?.text || ""
      : value;

    // console.log("sub", correspondPoint, device_number, point, value);
    subscribeData.value[device_number].points[point] = text;
    if (
      point.toLowerCase() === "temp" &&
      parseInt(searchParams.value.option) > 1
    ) {
      updateTemp(device_number, value);
    }
    if (point === subscribeData.value[device_number].device_error_point_name) {
      subscribeData.value[device_number].currentColor =
        value === subscribeData.value[device_number].device_error_point_value
          ? subscribeData.value[device_number].device_error_color
          : subscribeData.value[device_number].device_normal_color;
    }

    updateLabelText(device_number, point, text);
  };

  const transformDeviceNumber = (device_number) => {
    return device_number.replaceAll("_", "/");
  };

  const updateLabelText = (key, point, value) => {
    let text = subscribeData.value[key].labelText.replace(`%${point}`, value);
    Object.keys(subscribeData.value[key].points)
      .filter((p) => p !== point)
      .forEach((p) => {
        text = text.replace(`%${p}`, subscribeData.value[key].points[p]);
      });
    subscribeData.value[key].show_value = text;
  };

  const subComponents = ref(null);
  const updateSubscribeDataFromBaja = (data) => {
    for (let [key, value] of Object.entries(data)) {
      window.require &&
        window.requirejs(["baja!"], (baja) => {
          console.log("進入 bajaSubscriber 準備執行BQL訂閱");

          const ordPath = transformDeviceNumber(key);
          baja.Ord.make(`local:|foxs:|station:|slot:/${ordPath}`)
            .get()
            .then((folder) => {
              const batch = new baja.comm.Batch();

              const sub = new baja.Subscriber();
              sub.attach({
                changed: function (prop, cx) {
                  if (prop.$getDisplayName() !== "Out") return;
                  if (
                    Object.hasOwn(
                      booleanPointFacets.value,
                      prop.$complex.$propInParent.$slotName
                    )
                  ) {
                    const facets =
                      booleanPointFacets.value[
                        prop.$complex.$propInParent.$slotName
                      ];

                    for (let [facetKey, facetValue] of Object.entries(facets)) {
                      if (facetValue === prop.$getValue().getValueDisplay()) {
                        updateDeviceData(
                          key,
                          prop.$complex.$propInParent.$slotName,
                          facetKey
                        );
                      }
                    }
                  } else {
                    updateDeviceData(
                      key,
                      prop.$complex.$propInParent.$slotName,
                      prop.$getValue().getValueDisplay()
                    );
                  }
                },
              });

              folder
                .getSlots()
                .is("control:ControlPoint")
                .eachValue((point) => {
                  if (
                    Object.keys(value.points).includes(point.getDisplayName())
                  ) {
                    baja.Ord.make(
                      `local:|foxs:|station:|slot:/${ordPath}/${point.getDisplayName()}`
                    )
                      .get()
                      .then((component) => {
                        if (
                          point.getType().getTypeSpec() ===
                          "control:BooleanWritable"
                        ) {
                          const facets = component.getFacets1().toObject();
                          updateFacets(point.getDisplayName(), facets);
                          for (let [facetKey, facetValue] of Object.entries(
                            facets
                          )) {
                            if (
                              facetValue ===
                              component.getOut().getValue().toString()
                            ) {
                              updateDeviceData(
                                key,
                                point.getDisplayName(),
                                facetKey
                              );
                            }
                          }
                        } else {
                          updateDeviceData(
                            key,
                            point.getDisplayName(),
                            component.getOut().getValue()
                          );
                        }

                        sub
                          .subscribe({
                            comps: component, // Can also just be an singular Component instance
                            batch, // if defined, any network calls will be batched into this object (optional)
                          })
                          .then(() => {
                            console.log("subscribed successfully");
                            subComponents.value = sub;
                          })
                          .catch(function (err) {
                            baja.error(
                              "some components failed to subscribe: " + err
                            );
                          });
                      });
                  }
                });
            });
        });
    }
  };

  const updateDbidPosition = (viewer, data) => {
    if (!viewer) return;
    if (!forgeViewer.value) forgeViewer.value = markRaw(viewer);
    const tree = viewer.model.getData().instanceTree;
    const fragList = viewer.model.getFragmentList();
    for (let [key, value] of Object.entries(data)) {
      const nodebBox = new window.THREE.Box3();

      // for each fragId on the list, get the bounding box
      tree.enumNodeFragments(
        value.forge_dbid,
        (fragId) => {
          const fragbBox = new window.THREE.Box3();
          fragList.getWorldBounds(fragId, fragbBox);
          nodebBox.union(fragbBox); // create a unifed bounding box
        },
        true
      );
      subscribeData.value[key].device_coordinate_3d = viewer.worldToClient(
        nodebBox.getCenter()
      );
      subscribeData.value[key].is_show = viewer.isNodeVisible(value.forge_dbid);
    }
  };

  const fitToView = () => {
    if(!searchParams.value.camera_position) return
    const { x, y, z } = JSON.parse(searchParams.value.camera_position);
    const newPosition = new THREE.Vector3(x, y, z); //!<<< 相机的新位置
    
    const {
      x: x1,
      y: y1,
      z: z1,
    } = JSON.parse(searchParams.value.target_position); //!<<< 计算新焦点位置
    const newTarget = new THREE.Vector3(x1, y1, z1); //!<<< 焦點的新位置

    forgeViewer.value.navigation.getCamera().setView({
      position: newPosition.clone(),
      target: newTarget.clone(),
    });
    setTimeout(() => {
      updateDbidPosition(forgeViewer.value, subscribeData.value);
    }, 700);
  };

  const hideAllObjects = (instanceTree, filDbids = []) => {
    const tree = instanceTree || forgeViewer.value.model?.getInstanceTree();
    const allDbIdsStr = Object.keys(tree.nodeAccess.dbIdToIndex);
    for (var i = 0; i < allDbIdsStr.length; i++) {
      forgeViewer.value.hide(parseInt(allDbIdsStr[i]));
    }

    for (var i = 0; i < filDbids.length; i++) {
      forgeViewer.value.show(parseInt(filDbids[i]));
    }
    fitToView();
    forgeViewer.value.impl.invalidate(true);
  };

  const loadModel = (viewer, urn) => {
    return new Promise(function (resolve, reject) {
      async function onDocumentLoadSuccess(doc) {
        viewer.setGroundShadow(false);
        viewer.impl.renderer().setClearAlpha(0); //clear alpha channel
        viewer.impl.glrenderer().setClearColor(0xffffff, 0); //set transparent background, color code does not matter
        viewer.impl.invalidate(true); //trigger rendering

        const documentNode = await viewer.loadDocumentNode(
          doc,
          doc.getRoot().getDefaultGeometry()
        );

        resolve(documentNode);
      }
      function onDocumentLoadFailure(code, message, errors) {
        reject({ code, message, errors });
      }
      Autodesk.Viewing.Document.load(
        "urn:" + urn,
        onDocumentLoadSuccess,
        onDocumentLoadFailure
      );
    });
  };

  const reloadModal = () => {};

  watch(visibleDbid, (newValue) => {
    forgeViewer.value &&
      hideAllObjects(forgeViewer.value.model.getData().instanceTree, newValue);
  });

  watch(initialData, (newValue) => {
    if (newValue) {
      getDevice(searchParams.value.option);
    }
  });

  watch(
    searchParams,
    (newValue) => {
      getDevice(newValue.option);
    },
    {
      deep: true,
    }
  );

  return {
    subscribeData,
    visibleDbid,
    updateDbidPosition,
    hideAllObjects,
    updateForgeViewer,
    loadModel,
    urn,
    updateInitialData,
    subComponents,
  };
}
