<template>
  <div class="relative w-full" :class="{ 'bg-white': !transparent }">
    <div
      class="relative h-full w-full overflow-hidden"
      :class="{ 'bg-gray-100': !transparent }"
    >
      <div
        class="relative"
        :style="{
          transformOrigin: 'top left',
          transform: `translate(${canvasPos[0]}px, ${canvasPos[1]}px)`,
        }"
      >
        <canvas
          ref="canvas"
          class="h-full w-full border-none"
          :class="{ 'bg-white': !transparent }"
          :style="{
            width: `${canvasDim[0] * canvasZoom}px`,
            height: `${canvasDim[1] * canvasZoom}px`,
          }"
        />
        <div
          class="pointer-events-none absolute left-0 top-0 h-full w-full"
          v-if="!editable"
          :style="{
            transformOrigin: 'top left',
            transform: `scale(${canvasZoom})`,
            width: `${canvasDim[0]}px`,
            height: `${canvasDim[1]}px`,
          }"
        >
          <slot />
        </div>
      </div>
    </div>
    <PlannerToolbar v-if="editable">
      <PlannerTool
        v-for="toolItem of toolbar"
        :title="toolItem.title"
        :icon="toolItem.icon"
        :multiple="!!toolItem.children?.length"
        :selected="tool === toolItem.tool"
        @click="selectTool(toolItem.tool, toolItem.subTool)"
      >
        <template v-if="toolItem.children?.length">
          <PlannerTool
            v-for="subItem of toolItem.children"
            :title="subItem.title"
            :icon="subItem.icon"
            :selected="
              tool === subItem.tool &&
              (!subItem.subTool || subItem.subTool === subTool)
            "
            @click.stop="selectTool(subItem.tool, subItem.subTool)"
          />
        </template>
      </PlannerTool>
    </PlannerToolbar>

    <PlannerSidebars v-if="editable">
      <PlannerLayerPanel
        :layers="localFloorDocument.layers"
        @layer-name="commitLayerName"
        @object-name="commitObjectName"
        @select-object="clickedOnObject"
        @select-layer="clickedOnLayer"
      />
      <PlannerPropertyPanel
        :layers="localFloorDocument.layers"
        @update="updateObjectProperty"
      />
    </PlannerSidebars>
  </div>
</template>

<script setup lang="ts">
import {
  PencilSquareIcon,
  PencilIcon,
  HomeIcon,
  ArrowsPointingOutIcon,
  ArrowDownOnSquareIcon,
  ScissorsIcon,
  XMarkIcon,
} from '@heroicons/vue/24/outline';
import { useDebounceFn } from '@vueuse/shared';
import { onBeforeUnmount, onMounted, ref, shallowRef } from 'vue';
import { HousePlanner } from '../../modules/house-planner';
import {
  Line,
  RepositionEvent,
  ToolEvent,
  Vec2,
  Vec2Box,
} from '../../modules/house-planner/interfaces';
import type { HousePlannerCanvasTools } from '../../modules/house-planner/tools';
import deepUnref from '../../utils/deep-unref';
import { FloorDocument } from './interfaces/floor-document.interface';
import { ToolbarTool } from './interfaces/toolbar.interfaces';
import PlannerLayerPanel from './PlannerLayerPanel.vue';
import PlannerPropertyPanel from './PlannerPropertyPanel.vue';
import PlannerSidebars from './PlannerSidebars.vue';
import PlannerTool from './PlannerTool.vue';
import PlannerToolbar from './PlannerToolbar.vue';

const canvas = ref();
const module = shallowRef(new HousePlanner());
const tool = ref<string | undefined>('move');
const subTool = ref<string | undefined>(undefined);
const canvasDim = ref<Vec2>([1920, 1080]);
const canvasPos = ref<Vec2>([0, 0]);
const canvasZoom = ref<number>(1);

const props = withDefaults(
  defineProps<{
    floorDocument: FloorDocument;
    editable: boolean;
    grid?: boolean;
    transparent?: boolean;
    headless?: boolean;
  }>(),
  { editable: true, grid: true, headless: false, transparent: false }
);

const emit = defineEmits<{
  (
    e: 'update',
    document: FloorDocument,
    rooms: Line[],
    boundingBox?: Vec2Box
  ): void;
  (e: 'edited'): void;
  (e: 'zoom', scale: number): void;
}>();

const localFloorDocument = ref(deepUnref(props.floorDocument));
const emitUpdate = () =>
  module.value.manager &&
  emit(
    'update',
    localFloorDocument.value,
    module.value.manager.getAllObjectsOfType('room') as Line[],
    module.value.manager.getBoundingBox()
  );
const debouncedUpdate = useDebounceFn(emitUpdate, 10000);

const toolbar: ToolbarTool[] = [
  {
    title: 'Move',
    icon: ArrowsPointingOutIcon,
    tool: 'move',
  },
  {
    title: 'Draw',
    icon: PencilSquareIcon,
    tool: 'line',
    subTool: 'line',
    children: [
      {
        title: 'Outlines',
        icon: PencilIcon,
        tool: 'line',
        subTool: 'line',
      },
      {
        title: 'Rooms',
        icon: HomeIcon,
        tool: 'line',
        subTool: 'room',
      },
      {
        title: 'Curves',
        icon: ArrowDownOnSquareIcon,
        tool: 'line',
        subTool: 'curve',
      },
    ],
  },
  {
    title: 'Cut',
    icon: ScissorsIcon,
    tool: 'cut',
    subTool: 'cut',
    children: [
      {
        title: 'Remove Segment',
        icon: XMarkIcon,
        tool: 'cut',
        subTool: 'remove-segment',
      },
    ],
  },
];

const selectTool = (newTool?: string, newSubTool?: string) => {
  if (newTool === tool.value && !newSubTool) {
    newTool = undefined;
  }
  (module.value.manager?.tools as HousePlannerCanvasTools).setTool(
    newTool,
    newSubTool
  );
};

const commitObjectName = (layerId: number, objectId: number, name: string) => {
  module.value.manager?.updateObjectProperties(layerId, objectId, { name });
};

const commitLayerName = (layerId: number, name: string) => {
  module.value.manager?.updateLayerProperties(layerId, { name });
};

const clickedOnObject = (layerId: number, objectId: number, add?: boolean) => {
  const object = module.value.manager?.getLayerObjectById(layerId, objectId);
  if (!object) return;
  (module.value.manager?.tools as HousePlannerCanvasTools).selectObject(
    object,
    add
  );
};

const clickedOnLayer = (layerId: number) => {
  const layer = module.value.manager?.getLayerById(layerId);
  if (!layer) return;
  (module.value.manager?.tools as HousePlannerCanvasTools).selectLayer(layer);
};

const updateObjectProperty = (
  layerId: number,
  objectId: number,
  key: string,
  value: unknown
) => {
  module.value.manager?.updateObjectProperties(layerId, objectId, {
    [key]: value,
  });
};

const updateLocalDocument = () => {
  localFloorDocument.value.layers = deepUnref(module.value.manager!.layers);
};

const events: Record<string, (e: CustomEvent) => void> = {
  'hpc:update': (e: CustomEvent) => {
    updateLocalDocument();
    emit('edited');
    debouncedUpdate();
  },
  'hpc:selectionchange': (e: CustomEvent) => {
    updateLocalDocument();
    emit('edited');
    debouncedUpdate();
  },
  'hpc:tool': (e: CustomEvent<ToolEvent>) => {
    tool.value = e.detail.primary;
    subTool.value = e.detail.secondary as string;
  },
  'hpc:position': (e: CustomEvent<RepositionEvent>) => {
    canvasPos.value = e.detail.position;
    canvasZoom.value = e.detail.zoom;
    emit('zoom', e.detail.zoom);
  },
  'hpc:save': (e: CustomEvent) => {
    updateLocalDocument();
    emit('edited');
    emitUpdate();
  },
};

defineExpose({
  updateLocalDocument() {
    updateLocalDocument();
    return localFloorDocument.value;
  },
  setViewRectangle(view?: Vec2Box) {
    if (!module.value) return;
    if (!view && !localFloorDocument.value?.boundingBox) return;
    module.value.manager?.setViewRectangle(
      view || localFloorDocument.value!.boundingBox!
    );
  },
  canvasDim,
  canvasPos,
  canvasZoom,
});

onMounted(() => {
  const [setZoom, setPos] = module.value.initialize(
    canvas.value,
    deepUnref(localFloorDocument.value.layers),
    [localFloorDocument.value.width, localFloorDocument.value.height],
    canvasPos.value,
    canvasZoom.value,
    props.editable,
    props.grid,
    props.headless,
    localFloorDocument.value.boundingBox
  );
  canvasPos.value = setPos;
  canvasZoom.value = setZoom;
  emit('zoom', setZoom);

  Object.keys(events).forEach((event) =>
    canvas.value.addEventListener(event, events[event])
  );
});

onBeforeUnmount(() => {
  Object.keys(events).forEach((event) =>
    canvas.value.removeEventListener(event, events[event])
  );
  module.value?.cleanUp();
});
</script>
