<template>
    <div class="absolute top-0 left-0 w-full h-full origin-center pointer-events-none *:pointer-events-auto *:select-none">
        <div>
            <CanvasNode v-for="node of canvas.nodes" :key="node.id" :node="node" :zoom="zoom" />
        </div>
        <template v-for="edge of canvas.edges">
            <div :key="edge.id" v-if="edge.label" class="absolute z-10"
                :style="{ transform: labelCenter(getNode(canvas.nodes, edge.fromNode)!, edge.fromSide, getNode(canvas.nodes, edge.toNode)!, edge.toSide) }">
                <div class="relative bg-light-20 dark:bg-dark-20 border border-light-35 dark:border-dark-35 px-4 py-2 -translate-x-[50%] -translate-y-[50%]">{{ edge.label }}</div>
            </div>
        </template>
        <svg class="absolute top-0 left-0 overflow-visible w-full h-full origin-top pointer-events-none">
            <CanvasEdge v-for="edge of canvas.edges" :key="edge.id"
                :path="path(getNode(canvas.nodes, edge.fromNode)!, edge.fromSide, getNode(canvas.nodes, edge.toNode)!, edge.toSide)"
                :color="edge.color" :label="edge.label" />
        </svg>
    </div>
</template>

<script setup lang="ts">
const { canvas, zoom } = defineProps<{
    canvas: CanvasContent
    zoom: number
}>();
</script>

<script lang="ts">
import { clamp } from '~/shared/general.utils';
import type { CanvasContent, CanvasNode } from '~/types/canvas';

function edgePos(side: 'bottom' | 'top' | 'left' | 'right', pos: { x: number, y: number }, offset: number): { x: number, y: number } {
    switch (side) {
        case "left":
            return {
                x: pos.x - offset,
                y: pos.y
            };
        case "right":
            return {
                x: pos.x + offset,
                y: pos.y
            };
        case "top":
            return {
                x: pos.x,
                y: pos.y - offset
            };
        case "bottom":
            return {
                x: pos.x,
                y: pos.y + offset
            }
    }
}
function getNode(nodes: CanvasNode[], id: string): CanvasNode | undefined
{
    return nodes.find(e => e.id === id);
}
function posFromDir(e: { minX: number, minY: number, maxX: number, maxY: number }, t: 'bottom' | 'top' | 'left' | 'right'): { x: number, y: number } {
    switch (t) {
        case "top":
            return { x: (e.minX + e.maxX) / 2, y: e.minY };
        case "right":
            return { x: e.maxX, y: (e.minY + e.maxY) / 2 };
        case "bottom":
            return { x: (e.minX + e.maxX) / 2, y: e.maxY };
        case "left":
            return { x: e.minX, y: (e.minY + e.maxY) / 2 };
    }
}
function getBbox(node: CanvasNode): { minX: number, minY: number, maxX: number, maxY: number } {
    return { minX: node.x, minY: node.y, maxX: node.x + node.width, maxY: node.y + node.height };
}
function path(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
    if(from === undefined || to === undefined)
    {
        return {
            path: '',
            from: {},
            to: {},
            toSide: '',
        }
    }
    const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
    return bezier(start, fromSide, end, toSide);
}
function bezier(from: { x: number, y: number }, fromSide: 'bottom' | 'top' | 'left' | 'right', to: { x: number, y: number }, toSide: 'bottom' | 'top' | 'left' | 'right'): any {
    const r = Math.hypot(from.x - to.x, from.y - to.y), o = clamp(r / 2, 70, 150), a = edgePos(fromSide, from, o), s = edgePos(toSide, to, o);
    return {
        path: `M${from.x},${from.y} C${a.x},${a.y} ${s.x},${s.y} ${to.x},${to.y}`,
        from: from,
        to: to,
        side: toSide,
    };
}
function labelCenter(from: CanvasNode, fromSide: 'bottom' | 'top' | 'left' | 'right', to: CanvasNode, toSide: 'bottom' | 'top' | 'left' | 'right'): string {
    const start = posFromDir(getBbox(from), fromSide), end = posFromDir(getBbox(to), toSide);
    const len = Math.hypot(start.x - end.x, start.y - end.y), offset = clamp(len / 2, 70, 150), b = edgePos(fromSide, start, offset), s = edgePos(toSide, end, offset);
    const center = getCenter(start, end, b, s, 0.5);
    return `translate(${center.x}px, ${center.y}px)`;
}
function getCenter(n: { x: number, y: number }, i: { x: number, y: number }, r: { x: number, y: number }, o: { x: number, y: number }, e: number): { x: number, y: number } {
    const a = 1 - e, s = a * a * a, l = 3 * e * a * a, c = 3 * e * e * a, u = e * e * e;
    return {
        x: s * n.x + l * r.x + c * o.x + u * i.x,
        y: s * n.y + l * r.y + c * o.y + u * i.y
    };
}
</script>