<template>
	<div class="container">
		<div ref="threeContainer" class="three-container"></div>
	</div>
</template>

<script setup>
	import {
		onMounted,
		ref
	} from 'vue'
	import * as THREE from 'three'
	import {
		OrbitControls
	} from 'three/examples/jsm/controls/OrbitControls.js'

	const threeContainer = ref(null)

	onMounted(() => {
		const container = threeContainer.value
		const W = container.clientWidth,
			H = 400

		// Scene, Camera, Renderer
		const scene = new THREE.Scene()
		scene.background = new THREE.Color(0xf5f5f5)
		const camera = new THREE.PerspectiveCamera(60, W / H, 0.1, 1000)
		camera.position.set(0, 20, 30)
		const renderer = new THREE.WebGLRenderer({
			antialias: true
		})
		renderer.setSize(W, H)
		renderer.shadowMap.enabled = true
		container.appendChild(renderer.domElement)

		// Lights
		scene.add(new THREE.AmbientLight(0xffffff, 0.5))
		const dirL = new THREE.DirectionalLight(0xffffff, 0.8)
		dirL.position.set(-10, 20, 10)
		dirL.castShadow = true
		scene.add(dirL)

		// Floor
		const ground = new THREE.Mesh(
			new THREE.BoxGeometry(50, 0.02, 50),
			new THREE.MeshStandardMaterial({
				color: 0xdddddd
			})
		)
		ground.position.y = -0.01
		ground.receiveShadow = true
		scene.add(ground)

		// Materials
		const wallMat = new THREE.MeshStandardMaterial({
			color: 0xf8f8f8,
			roughness: 0.7,
			metalness: 0.1,
			side: THREE.DoubleSide
		})
		const lintelMat = new THREE.MeshStandardMaterial({
			color: 0xe0e0e0,
			roughness: 0.6,
			metalness: 0.1
		})
		const doorMat = new THREE.MeshStandardMaterial({
			color: 0x8b4513,
			roughness: 0.6,
			metalness: 0.2
		})
		const bedFrameMat = new THREE.MeshStandardMaterial({
			color: 0x555555,
			roughness: 0.5,
			metalness: 0.8
		})
		const mattressMat = new THREE.MeshStandardMaterial({
			color: 0xffffff,
			roughness: 0.8,
			metalness: 0.1
		})
		const tvMat = new THREE.MeshStandardMaterial({
			color: 0x000000,
			roughness: 0.4,
			metalness: 0.3
		})

		let selectedRoom = null

		function markOriginal(mesh) {
			mesh.userData.originalMaterial = mesh.material.clone()
		}

		function createRoom({
			w,
			h,
			d,
			x,
			z,
			door
		}) {
			const room = new THREE.Group()
			room.position.set(x, h / 2, z)
			room.userData.isRoom = true
			room.userData.selected = false

			const planeH = new THREE.PlaneGeometry(w, h)
			const planeD = new THREE.PlaneGeometry(d, h)

			// South with door
			const upH = h - door.height - door.sill
			const upWall = new THREE.Mesh(new THREE.PlaneGeometry(w, upH), wallMat.clone())
			upWall.position.set(0, door.height + door.sill + upH / 2 - h / 2, d / 2)
			markOriginal(upWall);
			room.add(upWall)

			const sideW = (w - door.width) / 2
			const leftWall = new THREE.Mesh(new THREE.PlaneGeometry(sideW, door.height), wallMat.clone())
			leftWall.position.set(-w / 2 + sideW / 2, door.sill + door.height / 2 - h / 2, d / 2)
			markOriginal(leftWall);
			room.add(leftWall)
			const rightWall = leftWall.clone()
			rightWall.position.x = w / 2 - sideW / 2
			markOriginal(rightWall);
			room.add(rightWall)

			const lintel = new THREE.Mesh(new THREE.BoxGeometry(door.width, door.lintelThk, 0.1), lintelMat
			.clone())
			lintel.position.set(0, door.sill + door.height + door.lintelThk / 2 - h / 2, d / 2 + 0.05)
			markOriginal(lintel);
			room.add(lintel)

			const doorGroup = new THREE.Group()
			const doorMesh = new THREE.Mesh(new THREE.BoxGeometry(door.width, door.height, 0.05), doorMat.clone())
			markOriginal(doorMesh);
			doorMesh.position.set(door.width / 2, door.height / 2 - h / 2, 0)
			doorGroup.add(doorMesh)
			doorGroup.position.set(-w / 2 + sideW + 0.01, 0, d / 2 + 0.025)
			doorGroup.userData.isDoor = true
			doorGroup.userData.open = false
			room.add(doorGroup)

			// North, East, West walls
			const north = new THREE.Mesh(planeH, wallMat.clone())
			north.position.set(0, 0, -d / 2)
			north.rotation.y = Math.PI
			markOriginal(north);
			room.add(north)
			const east = new THREE.Mesh(planeD, wallMat.clone())
			east.position.set(w / 2, 0, 0)
			east.rotation.y = -Math.PI / 2
			markOriginal(east);
			room.add(east)
			const west = east.clone()
			west.position.x = -w / 2
			west.rotation.y = Math.PI / 2
			markOriginal(west);
			room.add(west)

			// Single Bed flush
			const bedW = w * 0.6,
				bedH = 0.5,
				bedD = d * 0.4
			const bed = new THREE.Group()
			bed.userData.isBed = true
			const frame = new THREE.Mesh(new THREE.BoxGeometry(bedW, 0.1, bedD), bedFrameMat.clone())
			frame.position.y = -h / 2 + 0.05
			markOriginal(frame);
			bed.add(frame)
			const mat = new THREE.Mesh(new THREE.BoxGeometry(bedW * 0.95, bedH, bedD * 0.95), mattressMat.clone())
			mat.position.y = -h / 2 + bedH / 2 + 0.05
			markOriginal(mat);
			bed.add(mat)
			bed.position.set(0, 0, d / 2 - bedD / 2 - 0.05)
			room.add(bed)

			// TV flush opposite
			const tvWidth = bedW * 0.6
			const tvHeight = tvWidth * 9 / 16
			const tv = new THREE.Mesh(new THREE.BoxGeometry(tvWidth, tvHeight, 0.05), tvMat.clone())
			tv.userData.isTV = true
			markOriginal(tv);
			tv.position.set(0, bedH + tvHeight / 2, -d / 2 + 0.05)
			room.add(tv)

			room.traverse(o => {
				if (o.isMesh) {
					o.castShadow = true
					o.receiveShadow = true
				}
			})
			return room
		}

		// Build grid
		const building = new THREE.Group()
		const cols = 5,
			rows = 4,
			sx = 6,
			sz = 8
		for (let i = 0; i < 20; i++) {
			const col = i % cols,
				row = Math.floor(i / cols)
			const x = -((cols - 1) * sx) / 2 + col * sx
			const z = ((rows - 1) * sz) / 2 - row * sz
			building.add(createRoom({
				w: 4,
				h: 3,
				d: 5,
				x,
				z,
				door: {
					width: 1,
					height: 2,
					sill: 0.1,
					lintelThk: 0.2
				}
			}))
		}
		scene.add(building)

		const controls = new OrbitControls(camera, renderer.domElement)
		controls.enableDamping = true

		const ray = new THREE.Raycaster(),
			mouse = new THREE.Vector2()
		renderer.domElement.addEventListener('click', e => {
			const rect = renderer.domElement.getBoundingClientRect()
			mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1
			mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1
			ray.setFromCamera(mouse, camera)
			const hits = ray.intersectObjects(building.children, true)
			if (!hits.length) return

			// Door
			let obj = hits[0].object
			const doorGrp = obj.userData.isDoor ? obj : obj.parent.userData.isDoor ? obj.parent : null
			if (doorGrp) {
				const open = !doorGrp.userData.open
				doorGrp.rotation.y = open ? -Math.PI / 2 : 0
				doorGrp.userData.open = open
				return
			}

			// Room selection
			let tgt = hits[0].object
			while (tgt && !tgt.userData.isRoom) tgt = tgt.parent
			if (!tgt) return

			// Deselect previous
			if (selectedRoom && selectedRoom !== tgt) {
				selectedRoom.userData.selected = false
				selectedRoom.traverse(n => {
					if (n.isMesh && n.userData.originalMaterial) {
						n.material = n.userData.originalMaterial.clone()
					}
				})
			}

			// Toggle
			const sel = !tgt.userData.selected
			tgt.userData.selected = sel
			tgt.traverse(n => {
				if (n.isMesh && n.userData.originalMaterial) {
					let matClone = n.userData.originalMaterial.clone()
					if (sel && !n.userData.isTV && !n.userData.isDoor) matClone.color.set(0x3377ff)
					n.material = matClone
				}
			})
			selectedRoom = sel ? tgt : null
		})

		const animate = () => {
			requestAnimationFrame(animate);
			controls.update();
			renderer.render(scene, camera)
		}
		animate()
	})
</script>

<style scoped>
	.container {
		width: 100%;
		height: 400px
	}

	.three-container {
		width: 100%;
		height: 100%
	}
</style>