<template>
  <div class="entity-container-wrapper"
      :class="{'with-children': hasChildren, collapsed: isCollapsed, hidden: !value?.name?.length}">
    <div class="row item entity-container"
        :class="{ 'with-children': hasChildren, collapsed: isCollapsed, blink: justUpdated }">
      <div class="adjuster" :class="{'col-12': !hasChildren, 'col-11': hasChildren}">
        <component
          :is="component"
          :value="value"
          :parent="parent"
          :children="computedChildren"
          :loading="loading"
          ref="instance"
          :error="error || value?.reachable == false"
          @click="onClick"
          @input="$emit('input', $event)"
          @loading="$emit('loading', $event)"
        />
      </div>

      <div class="col-1 collapse-toggler" @click.stop="toggleCollapsed" v-if="hasChildren">
        <i class="fas"
          :class="{'fa-chevron-down': isCollapsed, 'fa-chevron-up': !isCollapsed}" />
      </div>
    </div>

    <div class="children fade-in" v-if="hasChildren && !isCollapsed">
      <div class="child" v-for="entity in computedChildren" :key="entity.id">
        <Entity
         :value="entity"
         :parent="value"
         :children="childrenByParentId(entity.id)"
         :loading="loading"
         :level="level + 1"
         @show-modal="$emit('show-modal', $event)"
         @input="$emit('input', entity)" />
      </div>
    </div>
  </div>
</template>

<script>
import { defineAsyncComponent, shallowRef } from 'vue'
import EntityMixin from "./EntityMixin"
import { bus } from "@/bus";

export default {
  name: "Entity",
  mixins: [EntityMixin],
  emits: ['input', 'loading', 'update', 'show-modal'],

  data() {
    return {
      component: null,
      justUpdated: false,
    }
  },

  computed: {
    computedChildren() {
      return Object.values(this.children || {}).filter((child) => child)
    },

    hasChildren() {
      return !!this.computedChildren.length
    },

    isCollapsed() {
      if (!this.hasChildren)
        return true

      return this.collapsed
    },

    instance() {
      return this.$refs.instance
    },
  },

  methods: {
    valuesEqual(a, b) {
      a = {...a}
      b = {...b}
      for (const key of ['updated_at', 'data']) {
        delete a[key]
        delete b[key]
      }

      return this.objectsEqual(a, b)
    },

    childrenByParentId(parentId) {
      return Object.values(this.allEntities || {}).
        filter(
          (entity) => entity
            && entity.parent_id === parentId
            && !entity.is_configuration
        ).
        reduce((obj, entity) => {
          obj[entity.id] = entity
          return obj
        }, {})
    },

    onClick(event) {
      event.stopPropagation()

      if (
        event.target.classList.contains('label') ||
        event.target.classList.contains('head')
      ) {
        // When clicking on the name or icon of the entity, stop the event
        // propagation and toggle the collapsed state instead.
        this.toggleCollapsed()
      } else {
        // Otherwise, propagate the event upwards as a request to show the
        // entity details modal.
        this.$emit('show-modal', this.value.id)
      }
    },

    onEntityUpdate(entity) {
      // Check if any of the children have been updated
      const entityId = entity?.id
      const isChildUpdate = (
        entityId != null &&
        this.children &&
        entityId in this.children
      )

      if (!isChildUpdate)
        return

      this.setJustUpdated()
    },

    toggleCollapsed() {
      this.collapsed = !this.collapsed
      // Propagate the collapsed state to the wrapped component if applicable
      if (this.instance)
        this.instance.collapsed = !this.instance.collapsed
    },

    setJustUpdated() {
      this.justUpdated = true
      const self = this;
      setTimeout(() => self.justUpdated = false, 1000)
    },
  },

  mounted() {
    if (this.type !== 'Entity') {
      const type = this.type.split('_').map((t) =>
          t[0].toUpperCase() + t.slice(1)
      ).join('')

      this.$watch(
          () => this.value,
          (newValue, oldValue) => {
              if (this.valuesEqual(oldValue, newValue))
                return false

              this.setJustUpdated()
              this.$emit('update', {value: newValue})
          }
      )

      this.component = shallowRef(
        defineAsyncComponent(
          () => import(`@/components/panels/Entities/${type}`)
        )
      )
    }

    bus.onEntity(this.onEntityUpdate)
  },
}
</script>

<style lang="scss" scoped>
@import "common";

.entity-container-wrapper {
  &.with-children:not(.collapsed) {
    box-shadow: 0 3px 4px 0 $default-shadow-color;
  }
}

@mixin expanded-entity {
  background: $selected-bg;
  font-weight: bold;
  box-shadow: 0 0 3px 2px $default-shadow-color;
}

.entity-container {
  width: 100%;
  display: flex;
  align-items: center;
  position: relative;
  padding: 0 !important;
  border-bottom: $default-border-3;

  &.with-children:not(.collapsed) {
    @include expanded-entity();
  }

  &:hover {
    background: $hover-bg;
  }

  .adjuster {
    cursor: pointer;
  }
}

:deep(.entity-container) {
  .entity.expanded {
    @include expanded-entity();
  }

  .head {
    .name {
      display: inline-flex;

      &:hover {
        color: $hover-fg;
      }
    }

    .label {
      margin-left: 0.5em;
    }

    .icon:hover {
      color: $hover-fg;
    }
  }
}

.blink {
  animation: blink-animation 1s steps(20, start);
}

@keyframes blink-animation {
  0% {
    background: initial
  }

  50% {
    background: $active-bg;
  }

  100% {
    background: initial
  }
}
</style>
