<template>
    <g
        class="boardnode"
        :style="{ opacity: dragging?.id === node.id && hasDragged ? 0.5 : 1 }"
        :transform="`translate(${position.x},${position.y})`"
    >
        <transition name="actions" appear>
            <g v-if="selected && 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="
                            action.fillColor
                                ? typeof action.fillColor === 'function'
                                    ? action.fillColor(node)
                                    : action.fillColor
                                : fillColor
                        "
                        r="20"
                        :stroke-width="selectedAction?.id === action.id ? 4 : 0"
                        :stroke="outlineColor"
                    />
                    <text :fill="titleColor" class="material-icons">{{
                        typeof action.icon === "function" ? action.icon(node) : action.icon
                    }}</text>
                </g>
            </g>
        </transition>

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

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

                <circle
                    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"
                    :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
                    :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"
                    :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
                    :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="selected && selectedAction"
                :fill="titleColor"
                class="node-title"
                :y="size + 75"
                >Tap again to confirm</text
            >
        </transition>
    </g>
</template>

<script lang="ts">
import themes from "@/data/themes";
import { ProgressDisplay, Shape } from "@/game/enums";
import { layers } from "@/game/layers";
import player from "@/game/player";
import { BoardNode, BoardNodeAction, NodeLabel, NodeType } from "@/typings/features/board";
import { getNodeTypeProperty } from "@/util/features";
import { defineComponent, PropType } from "vue";

export default defineComponent({
    name: "BoardNode",
    data() {
        return {
            ProgressDisplay,
            Shape,
            hovering: false,
            sqrtTwo: Math.sqrt(2)
        };
    },
    emits: ["mouseDown", "endDragging"],
    props: {
        node: {
            type: Object as PropType<BoardNode>,
            required: true
        },
        nodeType: {
            type: Object as PropType<NodeType>,
            required: true
        },
        dragging: {
            type: Object as PropType<BoardNode>
        },
        dragged: {
            type: Object as PropType<{ x: number; y: number }>,
            required: true
        },
        hasDragged: {
            type: Boolean,
            default: false
        },
        receivingNode: {
            type: Boolean,
            default: false
        }
    },
    computed: {
        board() {
            return layers[this.nodeType.layer].boards!.data[this.nodeType.id];
        },
        selected() {
            return this.board.selectedNode === this.node;
        },
        selectedAction() {
            return this.board.selectedAction;
        },
        actions(): BoardNodeAction[] | null | undefined {
            return getNodeTypeProperty(this.nodeType, this.node, "actions");
        },
        draggable(): boolean {
            return getNodeTypeProperty(this.nodeType, this.node, "draggable");
        },
        position(): { x: number; y: number } {
            return this.draggable && this.dragging?.id === this.node.id
                ? {
                      x: this.node.position.x + Math.round(this.dragged.x / 25) * 25,
                      y: this.node.position.y + Math.round(this.dragged.y / 25) * 25
                  }
                : this.node.position;
        },
        shape(): Shape {
            return getNodeTypeProperty(this.nodeType, this.node, "shape");
        },
        size(): number {
            let size: number = getNodeTypeProperty(this.nodeType, this.node, "size");
            if (this.receivingNode) {
                size *= 1.25;
            } else if (this.hovering || this.selected) {
                size *= 1.15;
            }
            return size;
        },
        title(): string {
            return getNodeTypeProperty(this.nodeType, this.node, "title");
        },
        label(): NodeLabel | null | undefined {
            return getNodeTypeProperty(this.nodeType, this.node, "label");
        },
        progress(): number {
            return getNodeTypeProperty(this.nodeType, this.node, "progress") || 0;
        },
        backgroundColor(): string {
            return themes[player.theme].variables["--background"];
        },
        outlineColor(): string {
            return (
                getNodeTypeProperty(this.nodeType, this.node, "outlineColor") ||
                themes[player.theme].variables["--separator"]
            );
        },
        fillColor(): string {
            return (
                getNodeTypeProperty(this.nodeType, this.node, "fillColor") ||
                themes[player.theme].variables["--secondary-background"]
            );
        },
        progressColor(): string {
            return getNodeTypeProperty(this.nodeType, this.node, "progressColor") || "none";
        },
        titleColor(): string {
            return (
                getNodeTypeProperty(this.nodeType, this.node, "titleColor") ||
                themes[player.theme].variables["--color"]
            );
        },
        progressDisplay(): ProgressDisplay {
            return (
                getNodeTypeProperty(this.nodeType, this.node, "progressDisplay") ||
                ProgressDisplay.Outline
            );
        },
        canAccept(): boolean {
            if (this.dragging == null || !this.hasDragged) {
                return false;
            }
            return typeof this.nodeType.canAccept === "boolean"
                ? this.nodeType.canAccept
                : this.nodeType.canAccept(this.node, this.dragging);
        },
        actionDistance(): number {
            return getNodeTypeProperty(this.nodeType, this.node, "actionDistance");
        }
    },
    methods: {
        mouseDown(e: MouseEvent | TouchEvent) {
            this.$emit("mouseDown", e, this.node.id, this.draggable);
        },
        mouseUp() {
            if (!this.hasDragged) {
                this.nodeType.onClick?.(this.node);
            }
        },
        mouseEnter() {
            this.hovering = true;
        },
        mouseLeave() {
            this.hovering = false;
        },
        performAction(e: MouseEvent | TouchEvent, action: BoardNodeAction) {
            // If the onClick function made this action selected,
            // don't propagate the event (which will deselect everything)
            if (action.onClick(this.node) || this.board.selectedAction?.id === action.id) {
                e.preventDefault();
                e.stopPropagation();
            }
        },
        actionMouseUp(e: MouseEvent | TouchEvent, action: BoardNodeAction) {
            if (this.board.selectedAction?.id === action.id) {
                e.preventDefault();
                e.stopPropagation();
            }
        }
    },
    watch: {
        onDraggableChanged() {
            if (this.dragging === this.node && !this.draggable) {
                this.$emit("endDragging", this.node.id);
            }
        }
    }
});
</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:hover circle,
.action.selected circle {
    r: 25;
}

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

.action 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>