/**
 * Standalone entry point for the Adom 3D Viewer.
 * Bundles Svelte + Babylon.js + the ThreeDViewer component into a single IIFE.
 * Exposes window.Adom3DViewer with init/loadModel/clearScene/getScene APIs.
 * Includes laser etch and Y-up→Z-up helpers inside the bundle scope.
 */

// Browser shim for the Node `process` global. @babylonjs/inspector v9 pulls
// in React + Fluent UI, which contain runtime `process.env.FA_VERSION` and
// `process.env.NODE_ENV` lookups. Vite's `define` handles NODE_ENV at build
// time, but FA_VERSION still references `process.*` at runtime — without a
// shim the inspector throws "process is not defined" the moment a user clicks
// the toolbar Inspector button. Define `process` BEFORE any other imports so
// every module that touches it sees the shim.
if (typeof (globalThis as any).process === 'undefined') {
  (globalThis as any).process = { env: {}, browser: true, platform: 'browser' };
}

import ThreeDViewer from './src/ThreeDViewer.svelte';

// Side-effect: registers the debugLayer getter on Scene.
import '@babylonjs/core/Debug/debugLayer';

// Bring in the entire @babylonjs/core namespace so apps poking at the scene
// from the outside (chiplinter, scene-graph utility scripts) can use it.
import * as BABYLON_CORE from '@babylonjs/core';

// Babylon.js classes (used directly inside this bundle for laser etch etc.)
import { DynamicTexture } from '@babylonjs/core/Materials/Textures/dynamicTexture';
import { StandardMaterial } from '@babylonjs/core/Materials/standardMaterial';
import { MeshBuilder } from '@babylonjs/core/Meshes/meshBuilder';
import { Vector3 } from '@babylonjs/core/Maths/math.vector';
import { Color3 } from '@babylonjs/core/Maths/math.color';
import type { Scene } from '@babylonjs/core/scene';
import type { AbstractMesh } from '@babylonjs/core/Meshes/abstractMesh';

interface ViewerInstance {
  loadModel: (url: string) => Promise<void>;
  clearScene: () => void;
  frameModel: (fillFraction?: number) => void;
  getScene: () => Scene | null;
  getEngine: () => any;
  getCamera: () => any;
  getShadowGenerator: () => any;
  addContentRoot: (node: any, options?: any) => void;
  removeContentRoot: (node: any) => void;
  toggleDebugLayer: () => void;
  showDebugLayer: () => void;
  hideDebugLayer: () => void;
  setGroundVisible: (visible: boolean) => void;
  setBottomLight: (on: boolean) => void;
  getBottomLightState: () => boolean;
  goHome: () => void;
  setProjectionMode: (ortho: boolean) => void;
  getProjectionMode: () => 'perspective' | 'orthographic';
  /** 3d-viewer-design §8 — toggle world-origin or mesh-local axes. The
   *  screen-space corner triad (§8c) is always on and has no toggle. */
  toggleAxes: (target: 'world' | 'mesh-local', enabled?: boolean) => boolean;
  getAxesState: () => { world: boolean; meshLocal: boolean };
  /** Layers detected from the naming convention (docs/CONTENT-CONVENTIONS.md);
   *  empty when the loaded GLB has fewer than two named layers. */
  getLayers: () => { name: string; label: string; visible: boolean }[];
  setLayerVisible: (name: string, visible: boolean) => boolean;
  destroy: () => void;
}

function init(container: HTMLElement, options?: {
  zUp?: boolean;
  modelUrl?: string;
  showViewCube?: boolean;
  showGround?: boolean;
  environmentUrl?: string;
  initialViewMode?: 'perspective' | 'orthographic';
}): ViewerInstance {
  const opts = options || {};

  const component = new ThreeDViewer({
    target: container,
    props: {
      zUp: opts.zUp !== undefined ? opts.zUp : true,
      modelUrl: opts.modelUrl,
      showViewCube: opts.showViewCube !== undefined ? opts.showViewCube : true,
      showGround: opts.showGround !== undefined ? opts.showGround : false,
      environmentUrl: opts.environmentUrl,
      initialViewMode: opts.initialViewMode || 'perspective',
    },
  });

  return {
    loadModel: (url: string) => component.loadModel(url),
    clearScene: () => component.clearScene(),
    frameModel: (fillFraction?: number) => component.frameModel(fillFraction),
    getScene: () => component.getScene(),
    getEngine: () => component.getEngine(),
    getCamera: () => component.getCamera(),
    getShadowGenerator: () => component.getShadowGenerator(),
    addContentRoot: (node: any, opts?: any) => component.addContentRoot(node, opts),
    removeContentRoot: (node: any) => component.removeContentRoot(node),
    toggleDebugLayer: () => component.toggleDebugLayer(),
    showDebugLayer: () => component.showDebugLayer(),
    hideDebugLayer: () => component.hideDebugLayer(),
    setGroundVisible: (visible: boolean) => component.setGroundVisible(visible),
    setBottomLight: (on: boolean) => component.setBottomLight(on),
    getBottomLightState: () => component.getBottomLightState(),
    goHome: () => component.goHome(),
    setProjectionMode: (ortho: boolean) => component.setProjectionMode(ortho),
    getProjectionMode: () => component.getProjectionMode(),
    toggleAxes: (target: 'world' | 'mesh-local', enabled?: boolean) => component.toggleAxes(target, enabled),
    getAxesState: () => component.getAxesState(),
    getLayers: () => component.getLayers(),
    setLayerVisible: (name: string, visible: boolean) => component.setLayerVisible(name, visible),
    destroy: () => component.$destroy(),
  };
}

/**
 * Rotate all __root__ nodes from Y-up (kicad-cli GLB) to Z-up (adom viewer).
 * Call after loadModel() resolves.
 */
function rotateYUpToZUp(scene: Scene | null): void {
  if (!scene) return;
  scene.meshes.forEach((m: AbstractMesh) => {
    if (m.name === '__root__') {
      m.rotation = new Vector3(Math.PI / 2, 0, 0);
      m.computeWorldMatrix(true);
    }
  });
  // Force all children to recompute
  scene.meshes.forEach((m: AbstractMesh) => {
    m.computeWorldMatrix(true);
  });
}

interface LaserEtchOptions {
  bodySize: { x: number; y: number; z?: number };
  partName?: string;
  manufacturer?: string;
  markingLines?: string[];
}

/**
 * Add laser etch chip markings on the IC body top surface.
 * Must be called after loadModel() + rotateYUpToZUp() + frameModel().
 * Returns the created plane mesh, or null if failed.
 */
function addLaserEtch(scene: Scene | null, options: LaserEtchOptions): any {
  if (!scene) return null;

  const MM = 0.001; // GLB is in meters
  const bodySize = options.bodySize;
  const bw = bodySize.x * MM;
  const bd = bodySize.y * MM;
  const longSide = Math.max(bw, bd);
  const shortSide = Math.min(bw, bd);

  // Build marking lines
  const markingLines = options.markingLines ? options.markingLines.slice() : [];
  if (markingLines.length === 0) {
    if (options.manufacturer) markingLines.push(options.manufacturer.toUpperCase());
    if (options.partName) markingLines.push(options.partName.toUpperCase());
    const now = new Date();
    markingLines.push(String(now.getFullYear()).slice(-2) + String(now.getMonth() + 1).padStart(2, '0'));
  }
  if (markingLines.length === 0) return null;

  // Find IC body top Z from loaded meshes (Z-up coordinate system)
  // Skip scene infrastructure meshes
  const skipNames = new Set(['skyBox', 'shadowGround', 'ViewerRoot', 'laserEtch', 'ground']);
  let topZ = -Infinity;
  scene.meshes.forEach((m: AbstractMesh) => {
    if (skipNames.has(m.name)) return;
    if (m.name.startsWith('viewCube')) return;
    if (m.getTotalVertices && m.getTotalVertices() > 0) {
      m.computeWorldMatrix(true);
      const bi = m.getBoundingInfo();
      if (bi.boundingBox.maximumWorld.z > topZ) {
        topZ = bi.boundingBox.maximumWorld.z;
      }
    }
  });
  if (!isFinite(topZ)) return null;

  // DynamicTexture for laser etch text
  const texW = 1024;
  const texH = Math.round(1024 * (shortSide / longSide));
  const dtex = new DynamicTexture('laserEtchTex', { width: texW, height: texH }, scene, true);
  const ctx = dtex.getContext();

  ctx.clearRect(0, 0, texW, texH);
  ctx.fillStyle = 'rgba(210, 212, 215, 0.8)';
  ctx.textAlign = 'center';
  ctx.textBaseline = 'middle';

  const lineCount = markingLines.length;
  const usableH = texH * 0.65;
  const lineH = usableH / lineCount;
  const fontSize = Math.min(Math.round(lineH * 0.7), Math.round(texW * 0.09));
  ctx.font = `${fontSize}px monospace`;

  const startY = (texH - usableH) / 2 + lineH / 2;
  for (let i = 0; i < lineCount; i++) {
    ctx.fillText(markingLines[i], texW / 2, startY + lineH * i);
  }

  // Pin 1 dot
  ctx.beginPath();
  ctx.arc(texW * 0.07, texH * 0.09, fontSize * 0.18, 0, Math.PI * 2);
  ctx.fill();

  dtex.update();

  // Material — self-illuminating silver text
  const mat = new StandardMaterial('etchMat', scene);
  mat.diffuseTexture = dtex;
  mat.diffuseTexture.hasAlpha = true;
  mat.useAlphaFromDiffuseTexture = true;
  mat.emissiveColor = new Color3(0.82, 0.83, 0.84);
  mat.backFaceCulling = false;
  mat.zOffset = -2;

  // Plane on IC body top (Z-up: plane faces +Z by default)
  // In right-handed Z-up, CreatePlane makes an XY plane with normal along Z
  const plane = MeshBuilder.CreatePlane('laserEtch', {
    width: longSide * 0.85,
    height: shortSide * 0.85,
    sideOrientation: 2, // DOUBLESIDE
  }, scene);
  plane.position = new Vector3(0, 0, topZ + 0.00002);
  plane.material = mat;

  return plane;
}

// Required by the CDN-loaded Babylon Inspector — it expects to bind to
// `window.BABYLON`. We expose the full @babylonjs/core namespace.
if (!(window as any).BABYLON) {
  (window as any).BABYLON = BABYLON_CORE;
}

// Expose on window. Distinct global from window.Adom3DViewer (Colby's bundle)
// so apps can have both loaded side-by-side during migration. The BABYLON
// sub-namespace re-exports the FULL @babylonjs/core surface so consumers
// can use any Babylon class (TransformNode, PBRMaterial, Quaternion, Matrix,
// CSG, Mesh, ...) without each app needing its own import side.
(window as any).Adom3DViewerBabylon9 = {
  init,
  rotateYUpToZUp,
  addLaserEtch,
  BABYLON: BABYLON_CORE,
};
