<script setup lang="ts">
import { Handle, Position } from '@vue-flow/core'
import type { GraphFxNodeData, GraphFxPort } from '../lib/graphfxToVueFlow'
import { getInputHandleId, getOutputHandleId } from '../lib/graphfxToVueFlow'
import { displayIoValue } from '../lib/editorHelpers'

const props = defineProps<{
  data: GraphFxNodeData
}>()

function typeClass(port: GraphFxPort) {
  return `io-type-${port.type.toLowerCase()}`
}

function hasCustomLabel(port: GraphFxPort) {
  return port.label && port.label !== port.name
}

function labelValue(port: GraphFxPort) {
  return hasCustomLabel(port) ? port.label : ''
}

function previewUrl(direction: 'in' | 'out', port: GraphFxPort) {
  return props.data.imagePreviews?.[`${direction}:${port.name}`] || port.previewUrl || ''
}

function hasSelectOptions(port: GraphFxPort) {
  return Boolean(port.options?.length)
}

function updateNodeId(event: Event) {
  const target = event.target as HTMLInputElement
  props.data.actions?.renameNodeId(props.data.id, target.value)
}

function updateLabel(direction: 'in' | 'out', port: GraphFxPort, event: Event) {
  const target = event.target as HTMLInputElement
  props.data.actions?.updateIoLabel(props.data.id, direction, port.name, target.value)
}

function updateInput(port: GraphFxPort, event: Event) {
  const target = event.target as HTMLInputElement | HTMLSelectElement
  const value = port.type === 'Boolean' ? (target as HTMLInputElement).checked : target.value
  props.data.actions?.updateInputValue(props.data.id, port.name, value)
}

function selectImage(port: GraphFxPort, event: Event) {
  const target = event.target as HTMLSelectElement
  if (!target.value) return
  props.data.actions?.selectInputImage(props.data.id, port.name, target.value)
}

function selectedImage(port: GraphFxPort) {
  return props.data.selectedImages?.[port.name] || ''
}

function deleteNode() {
  props.data.actions?.deleteNode(props.data.id)
}

function duplicateNode() {
  props.data.actions?.duplicateNode(props.data.id)
}

function disconnectInput(port: GraphFxPort) {
  props.data.actions?.disconnectInput(props.data.id, port.name)
}

function inputType(port: GraphFxPort) {
  if (port.type === 'Number') return 'number'
  if (port.type === 'Color') return 'color'
  return 'text'
}

function connectedText(port: GraphFxPort) {
  return port.connectedDisplay || port.output || 'connected'
}
</script>

<template>
  <article class="graphfx-node">
    <header class="graphfx-node__header">
      <div>
        <h3>{{ data.name }}</h3>
        <input
          class="graphfx-node__id nodrag"
          :value="data.id"
          aria-label="Node id"
          title="Node id"
          @change="updateNodeId"
          @pointerdown.stop
          @keydown.stop
        />
      </div>

      <details class="graphfx-node__menu nodrag" @pointerdown.stop @click.stop>
        <summary title="Node actions">⋯</summary>
        <div class="graphfx-node__menu-popover">
          <button type="button" class="secondary" @click="duplicateNode">Duplicate</button>
          <button type="button" class="danger" @click="deleteNode">Delete</button>
        </div>
      </details>
    </header>

    <section class="graphfx-node__ports" :class="{ 'graphfx-node__ports--empty': !data.inputs.length && !data.outputs.length }">
      <div class="graphfx-node__column graphfx-node__column--inputs">
        <div v-if="!data.inputs.length" class="graphfx-node__empty">no inputs</div>
        <div
          v-for="port in data.inputs"
          :key="port.name"
          class="graphfx-node__port graphfx-node__port--input"
          :class="[typeClass(port), { 'graphfx-node__port--connected': port.output, 'graphfx-node__port--labeled': hasCustomLabel(port) }]"
          :title="`${port.name} · ${port.type}${port.output ? ` · ${port.output}` : ''}`"
        >
          <Handle
            type="target"
            :id="getInputHandleId(data.id, port.name)"
            :position="Position.Left"
            class="graphfx-node__handle graphfx-node__handle--input"
          />

          <div class="graphfx-node__port-head">
            <span class="graphfx-node__port-name">{{ port.name }}</span>
            <span v-if="hasCustomLabel(port)" class="graphfx-node__label-chip">{{ port.label }}</span>
            <span class="graphfx-node__port-type">{{ port.type }}</span>
          </div>

          <img v-if="port.type === 'Image' && previewUrl('in', port)" class="graphfx-node__image-preview" :src="previewUrl('in', port)" :alt="`${port.name} preview`" />

          <div v-if="port.output" class="graphfx-node__connected-value nodrag" @pointerdown.stop>
            <span :title="port.output">← {{ connectedText(port) }}</span>
            <button type="button" class="ghost danger" title="Disconnect input" @click="disconnectInput(port)">×</button>
          </div>

          <template v-else>
            <select
              v-if="port.type === 'Image'"
              class="graphfx-node__value-input nodrag"
              :value="selectedImage(port)"
              :disabled="!data.imageOptions?.length"
              title="Use uploaded image as this input"
              @change="selectImage(port, $event)"
              @pointerdown.stop
              @keydown.stop
            >
              <option value="">{{ data.imageOptions?.length ? 'choose uploaded image' : 'upload images first' }}</option>
              <option v-for="image in data.imageOptions" :key="image.id" :value="image.id">{{ image.name }}</option>
            </select>

            <select
              v-else-if="hasSelectOptions(port)"
              class="graphfx-node__value-input nodrag"
              :value="displayIoValue(port.value)"
              title="Input value"
              @change="updateInput(port, $event)"
              @pointerdown.stop
              @keydown.stop
            >
              <option v-for="option in port.options" :key="option" :value="option">{{ option }}</option>
            </select>

            <label v-else-if="port.type === 'Boolean'" class="graphfx-node__checkbox nodrag" @pointerdown.stop>
              <input :checked="Boolean(port.value)" type="checkbox" @change="updateInput(port, $event)" />
              {{ Boolean(port.value) ? 'true' : 'false' }}
            </label>

            <input
              v-else
              class="graphfx-node__value-input nodrag"
              :type="inputType(port)"
              :value="displayIoValue(port.value)"
              title="Input value"
              @change="updateInput(port, $event)"
              @pointerdown.stop
              @keydown.stop
            />
          </template>

          <details class="graphfx-node__label-editor nodrag" @pointerdown.stop @click.stop>
            <summary title="Edit input label">label</summary>
            <input
              class="graphfx-node__label-input"
              :value="labelValue(port)"
              placeholder="optional label"
              title="Input label"
              @change="updateLabel('in', port, $event)"
              @keydown.stop
            />
          </details>
        </div>
      </div>

      <div class="graphfx-node__column graphfx-node__column--outputs">
        <div v-if="!data.outputs.length" class="graphfx-node__empty">no outputs</div>
        <div
          v-for="port in data.outputs"
          :key="port.name"
          class="graphfx-node__port graphfx-node__port--output"
          :class="[typeClass(port), { 'graphfx-node__port--labeled': hasCustomLabel(port) }]"
          :title="`${port.name} · ${port.type}`"
        >
          <div class="graphfx-node__port-head graphfx-node__port-head--right">
            <span class="graphfx-node__port-type">{{ port.type }}</span>
            <span v-if="hasCustomLabel(port)" class="graphfx-node__label-chip">{{ port.label }}</span>
            <span class="graphfx-node__port-name">{{ port.name }}</span>
          </div>

          <img v-if="port.type === 'Image' && previewUrl('out', port)" class="graphfx-node__image-preview" :src="previewUrl('out', port)" :alt="`${port.name} preview`" />

          <details class="graphfx-node__label-editor nodrag" @pointerdown.stop @click.stop>
            <summary title="Edit output label">label</summary>
            <input
              class="graphfx-node__label-input"
              :value="labelValue(port)"
              placeholder="optional label"
              title="Output label"
              @change="updateLabel('out', port, $event)"
              @keydown.stop
            />
          </details>

          <Handle
            type="source"
            :id="getOutputHandleId(data.id, port.name)"
            :position="Position.Right"
            class="graphfx-node__handle graphfx-node__handle--output"
          />
        </div>
      </div>
    </section>
  </article>
</template>
