<script setup>
import { createApp, ref, watch, h, nextTick } from 'vue'
import { difference, throttle, debounce } from 'es-toolkit';
import { utopiaUrn, blankUrn, getAccessToken, loadModel } from '@/components/viewer/utils/aps-init';
import { addTemSelectionListener, applyLayerSetting, findTemInside, getTradeCodeFromTem, objIsLand, objIsTem, unloadUnusedExtensions } from '@/components/viewer/utils/aps-viewer';
import { html, renderSvg } from '@/components/viewer/utils/dom';
import { colorStrToVector4, extractPoints, GeometryCallback, getBounds, sortByAngle } from '@/components/viewer/utils/geometry';
import Legends from '@/components/viewer/Legends.vue';
import LayerSetting from '@/components/viewer/LayerSetting.vue';
import ProductNoteHistory from '@/components/viewer/ProductNoteHistory.vue';

const { $exportpdf, $getdata, $connectWebSocket, $snackbar, $store, $subscribe } = useNuxtApp();
const { dealer, layersetting } = $store;
const viewerId = 'viewer';
const viewerRef = useTemplateRef(viewerId);

const products = ref([]);
const productStatuses = ref([]);
const saleStatuses = ref([]);
const carts = ref([]);
const dealers = ref([]);
const selectedProductForNote = ref(null);

const statusModes = {
  SIMPLE: 'Đơn giản',
  DETAILED: 'Chi tiết',
}

const statusMode = ref(statusModes.SIMPLE);

const statuses = computed(() =>
  statusMode.value === statusModes.SIMPLE ? saleStatuses.value : productStatuses.value
);

const defaultStatusIds = computed(() => statuses.value.map(x => x.id));
const defaultCartIds = computed(() => carts.value.map(x => x.id));
const defaultDealerIds = computed(() => dealers.value.map(x => x.id));

const filters = ref({
  statusIds: defaultStatusIds.value,
  cartIds: defaultCartIds.value,
  dealerIds: defaultDealerIds.value
});

function addMeshes(viewer) {
  viewer.toolController.setMouseWheelInputEnabled(false); // disable zoom wheel until colors are added

  if (window.colorModel) {
    viewer.unloadModel(window.colorModel);
  }

  viewer.getObjectTree(async (instanceTree) => {
    const sceneBuilder = await viewer.getExtension('Autodesk.Viewing.SceneBuilder');
    const modelBuilder = await sceneBuilder.addNewModel({ conserveMemory: false });
    window.colorModel = modelBuilder.model;
    viewer.setBackgroundColor(255, 255, 255, 255, 255, 255);

    const vpId = 1;
    const vpXform = viewer.model.getPageToModelTransform(vpId);
    const inverseXform = new THREE.Matrix4();
    const pt = new THREE.Vector3();

    const material = new THREE.MeshBasicMaterial({
      depthWrite: false, 
      depthTest: true,
    });
    
    function createMesh(lines, productId) {
      // inverse viewer's matrix to convert CAD coords (~400000) to viewer coords (~10)
      inverseXform.copy(vpXform).invert();

      // Extract all unique points to form a polygon
      const worldPoints = extractPoints(lines);
      const viewerPoints = [];
      
      for (let i = 0; i < worldPoints.length; i++) {
        const { x, y } = worldPoints[i];
        pt.set(x, y, 0).applyMatrix4(inverseXform);
        viewerPoints.push(new THREE.Vector2(pt.x, pt.y));
      }

      let sortedPoints = sortByAngle(viewerPoints);
      if (productId === 9290) {
        // special case - Z.E02.02A, fix manually
        sortedPoints = sortedPoints.filter(coor => coor.y < 4);
      } 

      const shape = new THREE.Shape(sortedPoints);
      const shapeGeo = new THREE.ShapeGeometry(shape);
      const bufferGeo = new THREE.BufferGeometry().fromGeometry(shapeGeo);

      const pos = bufferGeo.getAttribute('position');
      // only set Z after calling ShapeGeometry()
      for (let i = 0; i < pos.count; i++) {
        pos.setZ(i, -1);
      }
      pos.needsUpdate = true;

      const mesh = new THREE.Mesh(bufferGeo, material);
      mesh.dbId = productId; // to be able to find product from an added mesh (in applyColors)

      return mesh;
    }

    const dbIdMap = instanceTree.nodeAccess.dbIdToIndex;
    const allDbIds = Object.keys(dbIdMap).map((id) => parseInt(id));

    viewer.model.getBulkProperties(allDbIds, {
      propFilter: ['name', 'Layer', 'Global width', 'LO'],
    },
    (results) => {
      const frags = viewer.model.getFragmentList();
      window.meshes = [];
      let tems = [];
      let lands = [];

      for (const obj of results) {
        if (objIsTem(obj)) 
          tems.push({ ...obj, bounds: getBounds(obj.dbId, frags) })
        else if (objIsLand(obj)) 
          lands.push({ ...obj, bounds: getBounds(obj.dbId, frags) })
      }
        
      const temsInside = lands.map(land => findTemInside(land, tems));
      
      lands.forEach((land, i) => {
        const temInside = temsInside[i];
        if (!temInside) { 
          if (land.dbId === 70520) {
            return; // redundant polyline in Z.E02.02A from "26.01.16 - Export TMB - Phan mem.dwg"
          }
          console.error("Can't find temInside for land", land);
          return; 
        }
        const trade_code = getTradeCodeFromTem(temInside);
        const product = products.value.find(product => product.trade_code === trade_code);
        if (!product) return; 
       
        const geoCb = new GeometryCallback(viewer, vpXform);

        instanceTree.enumNodeFragments(land.dbId, (fragId) => {
          const renderProxy = viewer.impl.getRenderProxy(viewer.model, fragId);
          const vbr = new Autodesk.Viewing.Private.VertexBufferReader(renderProxy.geometry, viewer.impl.use2dInstancing);
          vbr.enumGeomsForObject(land.dbId, geoCb);
          const mesh = createMesh(geoCb.lines, product.id);
          window.meshes.push(mesh);
          modelBuilder.addMesh(mesh);
        });
      });

      viewer.impl.invalidate(true, true, true);
      applyColors();
      viewer.toolController.setMouseWheelInputEnabled(true); // re-enable zoom wheel
      const legend = document.querySelector('#legend.docking-panel.docking-panel-container-solid-color-a');
      if (legend) {
        legend.style.top = 'initial';
        legend.classList.remove('top-initial-important');
      }
    });
  });
}

/**
 * Show/hide certain colors based on filters, using `viewer.setThemingColor`,
 * not modifying the materials of the added meshes.
 */
function applyColors() {
  const { viewer, colorModel } = window;
  if (!viewer || !colorModel) return;
  
  const { statusIds, cartIds, dealerIds } = filters.value;
  const white = colorStrToVector4('white');

  colorModel.getObjectTree((instanceTree) => {
    const dbIdMap = instanceTree.nodeAccess.dbIdToIndex;
    const allDbIds = Object.keys(dbIdMap).map((id) => parseInt(id));

    for (const dbId of allDbIds) {
      instanceTree.enumNodeFragments(dbId, () => {
        const product = products.value.find(p => p.id === dbId);
        const { status, status__code, status__color, status__sale_status, status__sale_status__color, cart, cart__dealer } = product;

        const notSoldDealerCondition = dealer && status__code === 'not-sold';
        const currentStatus = statusMode.value === statusModes.SIMPLE ? status__sale_status : status;
        const statusCondition = statusIds.includes(currentStatus);
        const statusColor = colorStrToVector4(statusMode.value === statusModes.SIMPLE ? status__sale_status__color : status__color);
        
        const diffCartFilter = difference(defaultCartIds.value, cartIds);
        const diffDealerFilter = difference(defaultDealerIds.value, dealerIds);
        const noCartFilter = diffCartFilter.length === 0;
        const noDealerFilter = diffDealerFilter.length === 0;
        const cartCondition = noCartFilter ? cartIds.includes(cart) || cart === null : cartIds.includes(cart);
        const dealerCondition = noDealerFilter ? dealerIds.includes(cart__dealer) || cart__dealer === null : dealerIds.includes(cart__dealer);

        const showColor = statusCondition && !notSoldDealerCondition && cartCondition && dealerCondition;
        viewer.setThemingColor(dbId, showColor ? statusColor : white, colorModel); 
      });
    }
    viewer.impl.invalidate(true, true, true);
  });
}

const debouncedApplyColors = debounce(applyColors, 50);

const showProductViewModal = ref();

function closeProductViewModal() {
  showProductViewModal.value = undefined;
}

function openProductViewModal(product) {
  selectedProductForNote.value = product;
  showProductViewModal.value = {
    component: "product/ProductEdit",
    title: "Thông tin chi tiết",
    vbind: {
      row: product,
      view: true
    },
    width: "80%",
    height: "700px",
  };
}

function openNoPermissionModal() {
  showProductViewModal.value = {
    component: "application/NoPermission",
    title: "Không có quyền truy cập",
    width: "35%",
    height: "auto",
  };
}

const legendsSFC = ref(null);
const isRefreshing = ref(false);

function mountLegends() {
  if (legendsSFC.value) legendsSFC.value.unmount();
  const content = document.querySelector('.content.docking-panel-scroll');

  legendsSFC.value = createApp({
    name: 'Legends',
    render() {
      return h(Legends, {
        products,
        carts,
        dealers,
        defaultCartIds,
        defaultDealerIds,
        filters,
        statuses,
        statusModes,
        statusMode,
        onSwitchStatusMode: (newMode => {
          statusMode.value = newMode;
        }),
        onUpdateFilters: (payload => {
          filters.value = {
            ...filters.value,
            ...payload
          }
        }),
        onResetCartDealerFilters: () => {
          filters.value = {
            ...filters.value,
            cartIds: defaultCartIds.value,
            dealerIds: defaultDealerIds.value,
          }
        },
        onResetFilters: () => {
          filters.value = {
            statusIds: defaultStatusIds.value,
            cartIds: defaultCartIds.value,
            dealerIds: defaultDealerIds.value,
          }
        }
      });
    }
  });

  legendsSFC.value.mount(content);
  return content;
}

async function refreshFromWebSocket() {
  if (isRefreshing.value) return;

  isRefreshing.value = true;
  try {
    await loadData(false);
    debouncedApplyColors();
  } finally {
    isRefreshing.value = false;
  }
}

function initViewer(container) {
  const customToolbarId = 'customToolbar';
  const printId = 'print';
  const legendId = 'legend';
  const noteHistoryId = 'noteHistory';

  class LegendPanel extends Autodesk.Viewing.UI.DockingPanel {
    constructor(container, id, title, options) {
      super(container, id, title, options);
      this.viewer = viewer;
      Autodesk.Viewing.UI.DockingPanel.call(this, container, id, '');

      this.container.classList.add('docking-panel-container-solid-color-a', 'is-flex', 'is-flex-direction-column', 'top-initial-important');
      this.container.style['overflow-x'] = 'unset';
      this.container.style['overflow-y'] = 'unset';

      const panelTitle = this.container.querySelector('.docking-panel-title');
      panelTitle.innerText = 'Chú giải';

      const panelCloseBtn = this.container.querySelector('.docking-panel-close');
      panelCloseBtn.onclick = () => {
        const legendBtnElem = document.getElementById(`${customToolbarId}-${legendId}Tool`);
        this.container.classList.toggle('is-flex');
        legendBtnElem.classList.toggle('active');
        legendBtnElem.classList.toggle('inactive');
      }

      const content = html('div', { class: 'content docking-panel-scroll' });
      this.container.append(content);
      mountLegends();
    }
  }

  class NoteHistoryPanel extends Autodesk.Viewing.UI.DockingPanel {
    constructor(container, id, title, options) {
      super(container, id, title, options);
      this.viewer = viewer;
      this.app = null;
      this.isVisibleState = false;
      Autodesk.Viewing.UI.DockingPanel.call(this, container, id, '');

      this.container.classList.add('docking-panel-container-solid-color-a', 'is-flex-direction-column', 'is-hidden');

      const panelTitle = this.container.querySelector('.docking-panel-title');
      panelTitle.innerText = 'Lịch sử ghi chú';

      const panelCloseBtn = this.container.querySelector('.docking-panel-close');
      panelCloseBtn.onclick = () => {
        this.toggleVisibility();
        this.container.classList.toggle('is-flex');
        this.container.classList.toggle('is-hidden');
      }

      const content = html('div', { id: 'note-history-content', class: 'content docking-panel-scroll' });
      this.container.append(content);
      this.contentElement = content;
      this.mountVueComponent(content);
    }

    toggleVisibility() {
      console.log('[NoteHistoryPanel] toggleVisibility called, current state:', this.isVisibleState);
      const noteHistoryBtnElem = document.getElementById(`${customToolbarId}-${noteHistoryId}Tool`);
      if (this.isVisibleState) {
        this.hide();
        noteHistoryBtnElem?.classList.remove('active');
        noteHistoryBtnElem?.classList.add('inactive');
      } else {
        this.show();
        noteHistoryBtnElem?.classList.add('active');
        noteHistoryBtnElem?.classList.remove('inactive');
      }
    }

    show() {
      console.log('[NoteHistoryPanel] show() called');
      this.isVisibleState = true;
      this.container.style.display = 'flex';

      // Đảm bảo content element giữ nguyên style
      if (this.contentElement) {
        this.contentElement.style.display = 'flex';
        this.contentElement.style.flex = '1';
        this.contentElement.style.overflowY = 'auto';
        this.contentElement.style.overflowX = 'hidden';
        this.contentElement.style.minHeight = '0';
      }

      this.setVisible(true);

      // Force re-render Vue component nếu cần
      if (this.app) {
        this.$forceUpdate?.();
      }
    }

    hide() {
      console.log('[NoteHistoryPanel] hide() called');
      this.isVisibleState = false;
      this.container.style.display = 'none';
      this.setVisible(false);
    }

    mountVueComponent(element) {
      if (this.app) {
        this.app.unmount();
      }
      this.app = createApp({
        name: 'ProductNoteHistory',
        render: () => h(ProductNoteHistory, {
          selectedProduct: selectedProductForNote.value
        })
      });
      this.app.mount(element);
    }

    // Thêm method để refresh Vue component khi cần
    refreshComponent() {
      if (this.contentElement) {
        this.mountVueComponent(this.contentElement);
      }
    }
  }

  class Print extends Autodesk.Viewing.Extension {
    load() {
      if (this.viewer.toolbar) {
        this.createUI();
      } else {
        this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
        this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
      }
      return true;
    }

    unload() {
      console.info('Print extension is now unloaded!');
      return true;
    }

    createUI() {
      const viewer = this.viewer;
      let toolbarGroup = viewer.toolbar.getControl(customToolbarId);
      if (!toolbarGroup) {
        toolbarGroup = new Autodesk.Viewing.UI.ControlGroup(customToolbarId);
        viewer.toolbar.addControl(toolbarGroup);
      }

      this.subToolbar = toolbarGroup;
      const printBtn = new Autodesk.Viewing.UI.Button(`${customToolbarId}-${printId}Tool`);
      const adskButtonIcon = printBtn.container.children.item(0);
      renderSvg('/icon/print.svg').then((icon) => { adskButtonIcon.append(icon) });

      printBtn.onClick = () => this.print(viewer);
      printBtn.setToolTip(printId);
      this.subToolbar.addControl(printBtn);
    }

    print(viewer) {
      const getPdf = async (blob) => {
        const docId = 'bieu-do';
        const div = html('div', { id: docId, style: 'width: 1650px; height: 1030px' }, [
          html('img', { src: blob, style: 'object-fit: contain; max-height: 100%' })
        ]);
        document.body.append(div);
        const fileName = docId;
        $exportpdf(docId, fileName, 'a3', 'landscape');
        $snackbar('Đang xuất PDF...', { type: 'is-info' });
        div.remove();
      }

      const canvasBounds = viewer.impl.getCanvasBoundingClientRect();
      const cvWidth = canvasBounds.width;
      const cvHeight = canvasBounds.height;
      const pageWidth = viewer.model.getMetadata('page_dimensions', 'page_width'); // 11

      const DPI = 150;
      const scale = 3;
      const widthInPixels = Math.floor(pageWidth * DPI * scale);

      const canvasRatio = cvHeight / cvWidth;
      const width = widthInPixels;
      const height = Math.floor(widthInPixels * canvasRatio);
      const screenshotOptions = { fullPage: false, margin: 0 };

      Autodesk.Viewing.ScreenShot.getScreenShotWithBounds(window.viewer, width, height, getPdf, screenshotOptions);
    }

    onToolbarCreated() {
      this.viewer.removeEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
      this.onToolbarCreatedBinded = null;
      this.createUI();
    };
  }

  class Legend extends Autodesk.Viewing.Extension {
    load() {
      if (this.viewer.toolbar) {
        this.createUI();
      } else {
        this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
        this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
      }
      return true;
    }

    unload() {
      console.info('Legend extension is now unloaded!');
      return true;
    }

    createUI() {
      const viewer = this.viewer;
      let panel = this.panel;
      if (!panel) {
        // auto open panel on first load
        panel = new LegendPanel(viewer.container, legendId, legendId);
        viewer.addPanel(panel);
        panel.setVisible(!panel.isVisible());
      }
      let toolbarGroup = viewer.toolbar.getControl(customToolbarId);
      if (!toolbarGroup) {
        toolbarGroup = new Autodesk.Viewing.UI.ControlGroup(customToolbarId);
        viewer.toolbar.addControl(toolbarGroup);
      }

      this.subToolbar = toolbarGroup;
      const legendBtn = new Autodesk.Viewing.UI.Button(`${customToolbarId}-${legendId}Tool`);
      const legendBtnElem = legendBtn.container;
      const adskButtonIcon = legendBtnElem.children.item(0);

      renderSvg('/icon/legend.svg').then((icon) => {
        adskButtonIcon.append(icon);
        // because legend panel is open on first load
        legendBtnElem.classList.add('active');
        legendBtnElem.classList.remove('inactive');
      });

      legendBtn.onClick = () => {
        panel.container.classList.toggle('is-flex');
        panel.container.classList.toggle('is-hidden');
        legendBtnElem.classList.toggle('active');
        legendBtnElem.classList.toggle('inactive');
      };
      legendBtn.setToolTip(legendId);
      this.subToolbar.addControl(legendBtn);
    }

    onToolbarCreated(e) {
      this.viewer.removeEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
      this.onToolbarCreatedBinded = null;
      if (e.type === 'toolbarCreated') {
        this.createUI();
      }
    };
  }

  class NoteHistory extends Autodesk.Viewing.Extension {
    constructor(viewer, options) {
      super(viewer, options);
      this.panel = null;
      this.button = null;
    }

    load() {
      if (this.viewer.toolbar) {
        this.createUI();
      } else {
        this.onToolbarCreatedBinded = this.onToolbarCreated.bind(this);
        this.viewer.addEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
      }
      return true;
    }

    unload() {
      if (this.panel) {
        this.panel.uninitialize();
        this.panel = null;
      }
      if (this.button) {
        const toolbarGroup = this.viewer.toolbar.getControl(customToolbarId);
        toolbarGroup.removeControl(this.button);
        this.button = null;
      }
      return true;
    }

    createUI() {
      const viewer = this.viewer;
      let panel = this.panel;
      if (!panel) {
        panel = new NoteHistoryPanel(viewer.container, noteHistoryId, 'Note History');
        viewer.addPanel(panel);
        this.panel = panel;
      }

      let toolbarGroup = viewer.toolbar.getControl(customToolbarId);
      if (!toolbarGroup) {
        toolbarGroup = new Autodesk.Viewing.UI.ControlGroup(customToolbarId);
        viewer.addControl(toolbarGroup);
      }

      this.button = new Autodesk.Viewing.UI.Button(`${customToolbarId}-${noteHistoryId}Tool`);
      this.button.setToolTip('Lịch sử ghi chú');

      const adskButtonIcon = this.button.container.children.item(0);
      if (!selectedProductForNote.value) {
        adskButtonIcon.style.filter = 'grayscale(100%)';
      }
      renderSvg('/icon/notes.svg').then((icon) => { adskButtonIcon.append(icon) });

      this.button.onClick = () => {
        panel.toggleVisibility();
        panel.container.classList.toggle('is-flex');
        panel.container.classList.toggle('is-hidden');
        this.button.container.classList.toggle('active');
        this.button.container.classList.toggle('inactive');
      };

      toolbarGroup.addControl(this.button);
    }

    onToolbarCreated() {
      this.viewer.removeEventListener(Autodesk.Viewing.TOOLBAR_CREATED_EVENT, this.onToolbarCreatedBinded);
      this.onToolbarCreatedBinded = null;
      this.createUI();
    }
  }

  return new Promise((resolve, _reject) => {
    Autodesk.Viewing.theExtensionManager.registerExtension(printId, Print);
    Autodesk.Viewing.theExtensionManager.registerExtension(legendId, Legend);
    Autodesk.Viewing.theExtensionManager.registerExtension(noteHistoryId, NoteHistory);
    Autodesk.Viewing.Initializer({ env: 'AutodeskProduction', getAccessToken }, () => {
      const config = {
        extensions: [printId, legendId, noteHistoryId, 'Autodesk.ModelStructure', 'Autodesk.Viewing.SceneBuilder'],
      };

      const viewer = new Autodesk.Viewing.GuiViewer3D(container, config);
      viewer.start();
      viewer.setTheme('light-theme');
      viewer.setProgressiveRendering(false);
      viewer.setReverseZoomDirection(true);
      viewer.toolController.setMouseWheelInputEnabled(false);
      const zoomOutFactor = 0.3; // larger means zoom out more - https://stackoverflow.com/a/67918257
      viewer.navigation.FIT_TO_VIEW_VERTICAL_MARGIN = zoomOutFactor;
      viewer.navigation.FIT_TO_VIEW_HORIZONTAL_MARGIN = zoomOutFactor;

      // disable Autodesk's "fit everything in view" mouse & keyboard shortcuts
      viewer.canvas.addEventListener('dblclick', (e) => {
        e.stopPropagation();
      });
      
      document.addEventListener('keydown', e => {
        const canvas = viewerRef.value.querySelector('canvas');
        if (canvas && (e.key === 'F' || e.key === 'f')) {
          e.stopImmediatePropagation();
        }
      }, true);
      
      viewer.addEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, function onceLoaded(e) {
        const viewer = e.target;
        viewer.removeEventListener(Autodesk.Viewing.GEOMETRY_LOADED_EVENT, onceLoaded);
        applyLayerSetting(layersetting, $store);
        addMeshes(viewer);
      });

      function resetCameraIfZoomError() {
        const { position, target } = viewer.navigation.getCamera();
        const threshold = 500;
        const pxErr = position.x > threshold || position.x < 0;
        const pyErr = position.y > threshold || position.y < 0;
        const pzErr = position.z > threshold;
        const txErr = target.x > threshold || target.x < 0;
        const tyErr = target.y > threshold || target.y < 0;
        const tzErr = target.z > threshold;
        const zoomError = pxErr || pyErr || pzErr || txErr || tyErr || tzErr;

        if (zoomError) {
          viewer.navigation.setRequestHomeView(true);
          /* 
          // Debug log
          console.log('Reset because zoom error:');
          if (pxErr) {
            console.log('px', position.x)
          } else if (pyErr) {
            console.log('py', position.y)
          } else if (pzErr) {
            console.log('pz', position.z)
          } else if (txErr) {
            console.log('tx', target.z)
          } else if (tyErr) {
            console.log('ty', target.z)
          } else if (tzErr) {
            console.log('tz', target.z)
          } */
        }
      }

      viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, throttle(resetCameraIfZoomError , 300));
      addTemSelectionListener(viewer, products.value, openProductViewModal, openNoPermissionModal);
      unloadUnusedExtensions(viewer);
      resolve(viewer);
    });
  });
}

const componentActivated = ref(true);
const isViewerLoading = ref(false);

async function loadViewer() {
  if (!viewerRef.value) return;

  isViewerLoading.value = true;
  const scale = 0.25;

  try {
    const viewer = await initViewer(viewerRef.value);
    window.viewer = viewer;
    const transformMatrix = new THREE.Matrix4().scale(new THREE.Vector3(scale, scale, scale));
    const mainModel = await loadModel(viewer, utopiaUrn);
    const blankModel = await loadModel(viewer, blankUrn, transformMatrix);
    isViewerLoading.value = false;

    window.mainModel = mainModel;
    window.blankModel = blankModel;

    blankModel.getObjectTree((instanceTree) => {
      const dbIdMap = instanceTree.nodeAccess.dbIdToIndex;
      const allDbIds = Object.keys(dbIdMap).map((id) => parseInt(id));
      allDbIds.forEach((dbId) => {
        viewer.setThemingColor(dbId, new THREE.Vector4(1, 1, 1, 1), window.blankModel); // turns white to simulate invisibility
      })
    });
  } catch (error) {
    console.log('Error in loadViewer', error);
  }
}

function mountLayerSetting() {
  const observer = new MutationObserver((mutations, observer) => {
    mutations.forEach(mutation => {
      if (mutation.addedNodes.length > 0) {
        const addedNode = mutation.addedNodes.item(0);
        if (addedNode.nodeType === 1
          && addedNode.nodeName === 'DIV'
          && addedNode.id === 'ViewerLayersPanel'
        ) {
          const layerSettingSFCContainer = html('div', { id: 'layerSettingSFCContainer' });
          addedNode.append(layerSettingSFCContainer);

          const layerSettingSFC = createApp({
            name: 'LayerSetting',
            render() {
              return h(LayerSetting);
            }
          });

          layerSettingSFC.mount(layerSettingSFCContainer);
          observer.disconnect();
        }
      }
    });
  });

  observer.observe(viewerRef.value, {
    subtree: true,
    childList: true,
  })
}

async function loadData(first = true) {
  const [productsData, productStatusesData, saleStatusesData, cartsData, dealersData] = await Promise.all([
    $getdata('product', {
      cart__dealer__code: dealer?.code
    }),
    $getdata('productstatus', undefined, { sort: "index" }),
    $getdata('salestatus', undefined, { sort: "name" }),
    $getdata('cart', undefined, {
      filter: { dealer: dealer?.id },
      values: "id,code,name,dealer,index",
      distinct_values: {
        product_count: { type: "Count", field: "prdcart" }
      },
      summary: "annotate",
      sort: "index"
    }),
    $getdata('dealer'),
  ]);

  products.value = productsData;
  productStatuses.value = productStatusesData;
  saleStatuses.value = saleStatusesData;
  carts.value = cartsData;
  dealers.value = dealersData;

  if (first) {
    filters.value = {
      statusIds: defaultStatusIds.value,
      cartIds: defaultCartIds.value,
      dealerIds: defaultDealerIds.value
    };
  }
}

watch([products, filters], debouncedApplyColors, { deep: true });

watch(statuses, (val, oldVal) => {
  if (oldVal.length !== val.length) {
    // only reset status filter if statuses changes because of statusMode,
    // not when new data is fetched
    filters.value.statusIds = val.map(s => s.id);
  }
}, { deep: true });

// Watch for product selection to enable/disable the note history button
watch(selectedProductForNote, (newProduct) => {
  const noteHistoryBtn = document.getElementById('customToolbar-noteHistoryTool');
  if (noteHistoryBtn) {
    const icon = noteHistoryBtn.querySelector('.adsk-button-icon');
    if (newProduct) {
      if (icon) icon.style.filter = 'none'; // Enabled look
    } else {
      if (icon) icon.style.filter = 'grayscale(100%)'; // Disabled look
      const ext = window.viewer?.getExtension?.('noteHistory');
      if (ext?.panel) {
        ext.panel.setVisible(false);
      }
      noteHistoryBtn.classList.remove('active');
      noteHistoryBtn.classList.add('inactive');
    }
  }
});

function handleNavigateToProduct(event) {
  const { productId } = event.detail;
  if (!window.viewer || !window.meshes || !productId) return;

  const mesh = window.meshes.find(m => m.dbId === productId);
  if (mesh) {
    const boundingBox = new THREE.Box3().setFromObject(mesh);
    window.viewer.navigation.fitBounds(false, boundingBox);
  } else {
    console.warn(`Product mesh with ID ${productId} not found.`);
  }
}

async function handleWsMessage({ detail: response }) {
  if (response.type === 'realtime_update') {
    const { name, change_type, record } = response.payload;
    if (name.toLowerCase() !== 'product') return;

    console.log(`[Viewer] Received WS update for Product: ${change_type}`, record);
    if(dealer){
      if(record.cart__dealer__code === dealer.code){
        console.log(dealer.code)
        $snackbar("Căn " + record.trade_code + " đã có cập nhật mới", "Thành công", "Success");
      }
    } else {
      $snackbar("Căn " + record.trade_code + " đã có cập nhật mới", "Thành công", "Success");
    }
    
    
    await nextTick();
    await refreshFromWebSocket();
  }
}

const viewerSDKSharedPath = 'https://developer.api.autodesk.com/modelderivative/v2/viewers/7.115.0';
const viewerSDKPattern = `script[src^="${viewerSDKSharedPath}"],link[href^="${viewerSDKSharedPath}"]`;

async function initialize() {
  await loadData();
  mountLayerSetting();

  // Connect to WebSocket
  $connectWebSocket();

  if (dealer?.id) {
    $subscribe('product', { cart__dealer__code: dealer?.code }, (initialData) => {
      if (initialData && initialData.rows) {
        products.value = initialData.rows;
      }
    });
  } else {
    $subscribe('product', undefined, (initialData) => {
      if (initialData && initialData.rows) {
        products.value = initialData.rows;
      }
    });
  }

  window.addEventListener('navigateToProduct', handleNavigateToProduct);
  window.addEventListener('ws_message', handleWsMessage);

  if (window.Autodesk) {
    loadViewer();
  }
  else {
    const viewerScript = html('script', { src: `${viewerSDKSharedPath}/viewer3D.js`, onload: loadViewer });
    const viewerStyle = html('link', { href: `${viewerSDKSharedPath}/style.css`, rel: 'stylesheet' });
    document.head.append(viewerScript, viewerStyle);
  }
}

function cleanup() {
  const viewerSDKs = document.querySelectorAll(viewerSDKPattern);

  if (viewerSDKs.length > 0) {
    viewer.finish();
    viewer = null;
    Autodesk.Viewing.shutdown();
    Autodesk = undefined;

    viewerSDKs.forEach(script => script.remove());
    isViewerLoading.value = false;
    componentActivated.value = false;

    window.removeEventListener('navigateToProduct', handleNavigateToProduct);
    window.removeEventListener('ws_message', handleWsMessage);
  }
}

onMounted(cleanup);
onUnmounted(cleanup);

onActivated(() => {
  const viewerSDKs = document.querySelectorAll(viewerSDKPattern);

  if (viewerSDKs.length === 0) {
    componentActivated.value = true;
    initialize();
  }
});

onDeactivated(() => {
  if (isViewerLoading.value) cleanup();
});
</script>

<template>
  <div v-if="componentActivated" id="ViewerMaster">
    <div :ref="viewerId" :id="viewerId"></div>
    <div id="overlay"></div>
    <Modal @close="closeProductViewModal" v-if="showProductViewModal" v-bind="showProductViewModal" />
  </div>
</template>

<style>
#ViewerMaster {
  --top: 94px;

  position: absolute;
  width: 100%;
  top: var(--top);
  left: 0;
}

.adsk-viewing-viewer {
  font-family: var(--bulma-body-family) !important;

  .docking-panel-footer-resizer {
    cursor: nwse-resize !important;
  }
}

#viewer,
#overlay {
  position: absolute;
  width: 100%;
  left: 0;
  height: calc(100vh - var(--top));
}

#modelTools,
#toolbar-fullscreenTool {
  display: none;
}

.viewcube {
  display: none !important;
}

.homeViewWrapper {
  position: absolute;
  right: 12px;
  top: 10px;
  margin: 0;
}

#customToolbar {
  .adsk-button-icon {
    padding-top: 0;

    svg {
      width: 28px;
      height: 28px;
    }
  }

  .adsk-button.active svg {
    color: #00bfff; /* from Autodesk styles */
  }
}

#overlay {
  z-index: 1;
  background-color: rgba(0, 0, 0, 0.5);
  padding: 1em;
  display: none;

  >.notification {
    margin: auto;
    padding: 1em;
    max-width: 50%;
    background: white;
  }
}
</style>
