<template>
    <g
        class="boardnode"
        :class="node.type"
        :style="{ opacity: dragging?.id === node.id && hasDragged ? 0.5 : 1 }"
        :transform="`translate(${position.x},${position.y})`"
    >
        <transition name="actions" appear>
            <g v-if="isSelected && actions">
                <!-- TODO move to separate file -->
                <g
                    v-for="(action, index) in actions"
                    :key="action.id"
                    class="action"
                    :class="{ selected: selectedAction?.id === action.id }"
                    :transform="`translate(
                            ${
                                (-size - 30) *
                                Math.sin(((actions.length - 1) / 2 - index) * actionDistance)
                            },
                            ${
                                (size + 30) *
                                Math.cos(((actions.length - 1) / 2 - index) * actionDistance)
                            }
                        )`"
                    @mousedown="e => performAction(e, action)"
                    @touchstart="e => performAction(e, action)"
                    @mouseup="e => actionMouseUp(e, action)"
                    @touchend.stop="e => actionMouseUp(e, action)"
                >
                    <circle
                        :fill="getNodeProperty(action.fillColor, node)"
                        r="20"
                        :stroke-width="selectedAction?.id === action.id ? 4 : 0"
                        :stroke="outlineColor"
                    />
                    <text :fill="titleColor" class="material-icons">{{
                        getNodeProperty(action.icon, node)
                    }}</text>
                </g>
            </g>
        </transition>

        <g
            class="node-container"
            @mouseenter="isHovering = true"
            @mouseleave="isHovering = false"
            @mousedown="mouseDown"
            @touchstart="mouseDown"
            @mouseup="mouseUp"
            @touchend="mouseUp"
        >
            <g v-if="shape === Shape.Circle">
                <circle
                    v-if="canAccept"
                    class="receiver"
                    :r="size + 8"
                    :fill="backgroundColor"
                    :stroke="receivingNode ? '#0F0' : '#0F03'"
                    :stroke-width="2"
                />

                <circle
                    class="body"
                    :r="size"
                    :fill="fillColor"
                    :stroke="outlineColor"
                    :stroke-width="4"
                />

                <circle
                    class="progressFill"
                    v-if="progressDisplay === ProgressDisplay.Fill"
                    :r="Math.max(size * progress - 2, 0)"
                    :fill="progressColor"
                />
                <circle
                    v-else
                    :r="size + 4.5"
                    class="progressRing"
                    fill="transparent"
                    :stroke-dasharray="(size + 4.5) * 2 * Math.PI"
                    :stroke-width="5"
                    :stroke-dashoffset="
                        (size + 4.5) * 2 * Math.PI - progress * (size + 4.5) * 2 * Math.PI
                    "
                    :stroke="progressColor"
                />
            </g>
            <g v-else-if="shape === Shape.Diamond" transform="rotate(45, 0, 0)">
                <rect
                    v-if="canAccept"
                    class="receiver"
                    :width="size * sqrtTwo + 16"
                    :height="size * sqrtTwo + 16"
                    :transform="`translate(${-(size * sqrtTwo + 16) / 2}, ${
                        -(size * sqrtTwo + 16) / 2
                    })`"
                    :fill="backgroundColor"
                    :stroke="receivingNode ? '#0F0' : '#0F03'"
                    :stroke-width="2"
                />

                <rect
                    class="body"
                    :width="size * sqrtTwo"
                    :height="size * sqrtTwo"
                    :transform="`translate(${(-size * sqrtTwo) / 2}, ${(-size * sqrtTwo) / 2})`"
                    :fill="fillColor"
                    :stroke="outlineColor"
                    :stroke-width="4"
                />

                <rect
                    v-if="progressDisplay === ProgressDisplay.Fill"
                    class="progressFill"
                    :width="Math.max(size * sqrtTwo * progress - 2, 0)"
                    :height="Math.max(size * sqrtTwo * progress - 2, 0)"
                    :transform="`translate(${-Math.max(size * sqrtTwo * progress - 2, 0) / 2}, ${
                        -Math.max(size * sqrtTwo * progress - 2, 0) / 2
                    })`"
                    :fill="progressColor"
                />
                <rect
                    v-else
                    class="progressDiamond"
                    :width="size * sqrtTwo + 9"
                    :height="size * sqrtTwo + 9"
                    :transform="`translate(${-(size * sqrtTwo + 9) / 2}, ${
                        -(size * sqrtTwo + 9) / 2
                    })`"
                    fill="transparent"
                    :stroke-dasharray="(size * sqrtTwo + 9) * 4"
                    :stroke-width="5"
                    :stroke-dashoffset="
                        (size * sqrtTwo + 9) * 4 - progress * (size * sqrtTwo + 9) * 4
                    "
                    :stroke="progressColor"
                />
            </g>

            <text :fill="titleColor" class="node-title">{{ title }}</text>
        </g>

        <transition name="fade" appear>
            <g v-if="label">
                <text
                    :fill="label.color || titleColor"
                    class="node-title"
                    :class="{ pulsing: label.pulsing }"
                    :y="-size - 20"
                    >{{ label.text }}
                </text>
            </g>
        </transition>

        <transition name="fade" appear>
            <text
                v-if="isSelected && selectedAction"
                :fill="titleColor"
                class="node-title"
                :y="size + 75"
                >Tap again to confirm</text
            >
        </transition>
    </g>
</template>

<script setup lang="ts">
import themes from "@/data/themes";
import {
    BoardNode,
    GenericBoardNodeAction,
    GenericNodeType,
    getNodeProperty,
    ProgressDisplay,
    Shape
} from "@/features/board";
import { Visibility } from "@/features/feature";
import settings from "@/game/settings";
import { computed, ref, toRefs, unref, watch } from "vue";

const sqrtTwo = Math.sqrt(2);

const props = toRefs(
    defineProps<{
        node: BoardNode;
        nodeType: GenericNodeType;
        dragging?: BoardNode;
        dragged?: {
            x: number;
            y: number;
        };
        hasDragged?: boolean;
        receivingNode?: boolean;
        selectedNode?: BoardNode | null;
        selectedAction?: GenericBoardNodeAction | null;
    }>()
);
const emit = defineEmits<{
    (e: "mouseDown", event: MouseEvent | TouchEvent, node: number, isDraggable: boolean): void;
    (e: "endDragging", node: number): void;
}>();

const isHovering = ref(false);
const isSelected = computed(() => unref(props.selectedNode) === unref(props.node));
const isDraggable = computed(() =>
    getNodeProperty(props.nodeType.value.draggable, unref(props.node))
);

watch(isDraggable, value => {
    const node = unref(props.node);
    if (unref(props.dragging) === node && !value) {
        emit("endDragging", node.id);
    }
});

const actions = computed(() => {
    const node = unref(props.node);
    return getNodeProperty(props.nodeType.value.actions, node)?.filter(
        action => getNodeProperty(action.visibility, node) !== Visibility.None
    );
});

const position = computed(() => {
    const node = unref(props.node);
    const dragged = unref(props.dragged);

    return getNodeProperty(props.nodeType.value.draggable, node) &&
        unref(props.dragging)?.id === node.id &&
        dragged
        ? {
              x: node.position.x + Math.round(dragged.x / 25) * 25,
              y: node.position.y + Math.round(dragged.y / 25) * 25
          }
        : node.position;
});

const shape = computed(() => getNodeProperty(props.nodeType.value.shape, unref(props.node)));
const title = computed(() => getNodeProperty(props.nodeType.value.title, unref(props.node)));
const label = computed(() => getNodeProperty(props.nodeType.value.label, unref(props.node)));
const size = computed(() => getNodeProperty(props.nodeType.value.size, unref(props.node)));
const progress = computed(
    () => getNodeProperty(props.nodeType.value.progress, unref(props.node)) || 0
);
const backgroundColor = computed(() => themes[settings.theme].variables["--background"]);
const outlineColor = computed(
    () =>
        getNodeProperty(props.nodeType.value.outlineColor, unref(props.node)) ||
        themes[settings.theme].variables["--outline"]
);
const fillColor = computed(
    () =>
        getNodeProperty(props.nodeType.value.fillColor, unref(props.node)) ||
        themes[settings.theme].variables["--raised-background"]
);
const progressColor = computed(() =>
    getNodeProperty(props.nodeType.value.progressColor, unref(props.node))
);
const titleColor = computed(
    () =>
        getNodeProperty(props.nodeType.value.titleColor, unref(props.node)) ||
        themes[settings.theme].variables["--foreground"]
);
const progressDisplay = computed(() =>
    getNodeProperty(props.nodeType.value.progressDisplay, unref(props.node))
);
const canAccept = computed(
    () =>
        unref(props.dragging) != null &&
        unref(props.hasDragged) &&
        getNodeProperty(props.nodeType.value.canAccept, unref(props.node))
);
const actionDistance = computed(() =>
    getNodeProperty(props.nodeType.value.actionDistance, unref(props.node))
);

function mouseDown(e: MouseEvent | TouchEvent) {
    emit("mouseDown", e, props.node.value.id, isDraggable.value);
}

function mouseUp() {
    if (!props.hasDragged?.value) {
        props.nodeType.value.onClick?.(props.node.value);
    }
}

function performAction(e: MouseEvent | TouchEvent, action: GenericBoardNodeAction) {
    // If the onClick function made this action selected,
    // don't propagate the event (which will deselect everything)
    if (action.onClick(unref(props.node)) || unref(props.selectedAction)?.id === action.id) {
        e.preventDefault();
        e.stopPropagation();
    }
}

function actionMouseUp(e: MouseEvent | TouchEvent, action: GenericBoardNodeAction) {
    if (unref(props.selectedAction)?.id === action.id) {
        e.preventDefault();
        e.stopPropagation();
    }
}
</script>

<style scoped>
.boardnode {
    cursor: pointer;
    transition-duration: 0s;
}

.node-title {
    text-anchor: middle;
    dominant-baseline: middle;
    font-family: monospace;
    font-size: 200%;
    pointer-events: none;
}

.progressRing {
    transform: rotate(-90deg);
}

.action:not(.boardnode):hover circle,
.action:not(.boardnode).selected circle {
    r: 25;
}

.action:not(.boardnode):hover text,
.action:not(.boardnode).selected text {
    font-size: 187.5%; /* 150% * 1.25 */
}

.action:not(.boardnode) text {
    text-anchor: middle;
    dominant-baseline: central;
}

.fade-enter-from,
.fade-leave-to {
    opacity: 0;
}

.pulsing {
    animation: pulsing 2s ease-in infinite;
}

@keyframes pulsing {
    0% {
        opacity: 0.25;
    }

    50% {
        opacity: 1;
    }

    100% {
        opacity: 0.25;
    }
}
</style>

<style>
.actions-enter-from .action,
.actions-leave-to .action {
    transform: translate(0, 0);
}

.grow-enter-from .node-container,
.grow-leave-to .node-container {
    transform: scale(0);
}
</style>