<template>
  <div class="b-panel-list-container">
    <transition-group
      name="slide"
      tag="div"
      class="b-panel-list"
      @before-enter="addAnimateClass"
      @before-leave="beforeLeave"
    >
      <template v-for="(panel, n) of panelsStack">
        <panel
          :key="n"
          :ref="`panel-${panel.id}`"
          :index="n"
          :panel="panel"
          :title="panel.title"
          :nocard="panel.nocard"
          :icon="panel.icon"
          :css-class="panel.cssClass"
          :payload="panel.payload"
          :expandable="panelsStack.length > 1"
          :isFullSize="isFullSize"
          :collapsed="panel.isCollapsed"
          :closeable="panel.isCloseable"
          :animate="panel.isAnimate"
          @open="openPanel($event[0], $event[1], n + 1)"
          @expand="expandPanel(panel)"
          @fullsize="fullsizePanel(panel)"
          @collapse="collapsePanel(panel)"
          @close="closePanel(panel)"
          @open-menu="$emit('open-menu', panel.type, panel.payload)"
          @onboarding-progress="$emit('onboarding-progress', $event)"
          @onboarding-step-viewed="$emit('onboarding-step-viewed', $event)"
        >
          <template #before-header>
            <slot name="before-header" :panel="panel" :index="n" :payload="panel.payload"></slot>
          </template>
          <slot
            :name="panel.type"
            :panel="panel"
            :payload="panel.payload"
            :multiple="isOpenMultiple"
            :open="(title, icon, type, payload) => openPanel(type, payload, n + 1)"
            :close="() => closePanel(panel)"
            :expand="() => expandPanel(panel)"
            :fullsize="() => fullsizePanel(panel)">
            <component v-if="panel.components.default" :is="panel.components.default" :panel="panel" :payload="panel.payload" />
          </slot>
          <template v-if="$scopedSlots[`${panel.type}.title`] || panel.components.title" #title>
            <slot
              :name="`${panel.type}.title`"
              :panel="panel"
              :payload="panel.payload"
              :multiple="isOpenMultiple"
              :open="(title, icon, type, payload) => openPanel(type, payload, n + 1)"
              :close="() => closePanel(panel)"
              :expand="() => expandPanel(panel)"
              :fullsize="() => fullsizePanel(panel)">
              <component v-if="panel.components.title" :is="panel.components.title" :panel="panel" :payload="panel.payload" />
            </slot>
          </template>
          <template v-if="$scopedSlots[`${panel.type}.buttons`] || panel.components.buttons" #buttons>
            <slot
              :name="`${panel.type}.buttons`"
              :panel="panel"
              :payload="panel.payload"
              :multiple="isOpenMultiple"
              :open="(title, icon, type, payload) => openPanel(type, payload, n + 1)"
              :close="() => closePanel(panel)"
              :expand="() => expandPanel(panel)"
              :fullsize="() => fullsizePanel(panel)">
              <component v-if="panel.components.buttons" :is="panel.components.buttons" :panel="panel" :payload="panel.payload" />
            </slot>
          </template>
          <template v-if="$scopedSlots[`${panel.type}.header`] || panel.components.header" #header>
            <slot
              :name="`${panel.type}.header`"
              :panel="panel"
              :payload="panel.payload"
              :multiple="isOpenMultiple"
              :open="(title, icon, type, payload) => openPanel(type, payload, n + 1)"
              :close="() => closePanel(panel)"
              :expand="() => expandPanel(panel)"
              :fullsize="() => fullsizePanel(panel)">
              <component v-if="panel.components.header" :is="panel.components.header" :panel="panel" :payload="panel.payload" />
            </slot>
          </template>
        </panel>
      </template>
    </transition-group>
  </div>
</template>
<style lang="scss">
@import "bootstrap/scss/_functions.scss";
@import "bootstrap/scss/_variables.scss";
@import "bootstrap/scss/mixins/_breakpoints.scss";

.b-panel-list-container {
  width: 100%;
  height: 100%;
  position: relative;
  flex-grow: 1;
}
.b-panel-list {
  align-items: stretch;
  //background: var(--bs-body-bg);
  bottom: 0;
  height: 100%;
  left: 0;
  overflow-x: auto;
  position: absolute;
  right: 0;
  top: 0;

  @include media-breakpoint-up(md) {
    display: flex;
  }
}

$an-time: .1s;
$double-an-time: $an-time * 2;

.animate.slide-enter-active {
  min-width: 0 !important;
  /* подвоюємо час, для коректної швидкості анімації, у випадку якщо max-width застосовуємо тільки на половину */
  animation: $double-an-time linear slidein forwards;
}
.animate.slide-leave-active {
  min-width: 0 !important;
  /* подвоюємо час, для коректної швидкості анімації, у випадку якщо max-width застосовуємо тільки на половину */
  animation: $double-an-time linear slidein reverse;
}

@keyframes slidein {
  0% {
    max-width: 0;
  }
  100% {
    max-width: 100%;
  }
}

@media (max-width: 768px) {
  @keyframes slidein {
    0% {
      max-width: 100%;
    }
    100% {
      max-width: 100%;
    }
  }
}

.b-panel {
  transition: min-width $an-time linear, flex-grow $an-time linear;

  > div {
    //transition: opacity $an-time linear;
  }
}
//.slide-enter-active > div {
//  opacity: 0;
//}
//
//.opacity-null > div {
//  opacity: 0 !important;
//}
</style>
<script lang="ts">
import { Vue, Component, Prop, Watch } from 'vue-property-decorator';
import itfIcon from '../icon/Icon.vue';
import Panel from './Panel.vue';
import {hashToStack, stackToHash} from "@itfin/components/src/components/panels/helpers";
import {emitGlobalEvent, setRootPanelList} from "@itfin/components/src/components/panels";

interface VisualOptions {
  title: string;
  icon?: string;
}

export interface IPanel {
  id: number;
  title: string;
  icon: string;
  type: string;
  payload: any;
  nocard?: boolean;
  cssClass?: string;
  isCollapsed: boolean;
  isExpanded: boolean;
  isCloseable: boolean;
  isAnimate: boolean;
  open: (type: string, visOptions: VisualOptions, payload: any) => void;
  close: () => void;
  expand: () => void;
  on: (eventName: string, func: (event: string, ...args: any[]) => any) => void;
  off: (eventName: string, func: (event: string, ...args: any[]) => any) => void;
  once: (eventName: string, func: (event: string, ...args: any[]) => any) => void;
  emit: (event: string, ...args: any[]) => void;
  isMultiple: () => boolean;
  fullsize: () => void;
  getPanels: (type: string) => any;
  getPayload: () => any;
  setPayload: (value: any) => void;
  __events: Record<string, ((event: string, ...args: any[]) => any)[]>;
  permanentExpanded: boolean;
}

@Component({
  components: {
    itfIcon,
    Panel
  },
  directives: {
  },
  filters: {
  },
  provide() {
    return { panelList: this };
  }
})
export default class PanelList extends Vue {
  @Prop() firstPanel: IPanel;
  @Prop() panels: Record<string, Component>;
  @Prop({ default: () => {} }) searchPanel: (type: string) => boolean;
  @Prop({ type: String, default: 'path' }) routeType: string;

  panelsStack:IPanel[] = [];

  nextId:number = 0;

  @Watch('panels', { deep: true })
  onPanelsPropChanged(newPanels: Record<string, Component>) {
    this.panelsStack.forEach(stackedPanel => {
      const newPanel = newPanels[stackedPanel.type];
      if (newPanel && typeof newPanel.inAppOnboarding === 'function') {
        const updatedOnboarding = newPanel.inAppOnboarding(this.$t.bind(this));
        stackedPanel.inAppOnboarding = updatedOnboarding;
      }
    });
  }

  created() {
    setRootPanelList(this);
    if (this.firstPanel) {
      this.internalOpenPanel(this.firstPanel.type, this.firstPanel.payload);
    }
    this.parsePanelHash(); // щоб панелі змінювались при перезавантаженні
    window.addEventListener('popstate', this.handlePopState); // щоб панелі змінювались при навігації
  }

  beforeDestroy() {
    window.removeEventListener('popstate', this.handlePopState);
  }

  get isOpenMultiple() {
    return this.panelsStack.filter(p => !p.isCollapsed).length > 1;
  }

  expandPanel(panel: IPanel) {
    const newStack = [...this.panelsStack];
    const index = newStack.findIndex(p => p.id === panel.id);
    newStack[index].isCollapsed = false;
    newStack[index].isExpanded = true;
    this.panelsStack = newStack;
    this.ensureOnlyTwoOpenPanels(panel.id);
    this.setPanelHash();
  }

  ensureOnlyTwoOpenPanels(keepOpenId: number) {
    const newStack = [...this.panelsStack];
    const openPanels = newStack.filter((panel) => !panel.isCollapsed);
    if (openPanels.length > 1) {
      const keepOpenIds = [keepOpenId];
      const [panel1, panel2] = openPanels.filter((panel) => panel.id !== keepOpenId);
      const indexKeep = newStack.findIndex(p => p.id === keepOpenId);
      const index1 = newStack.findIndex(p => p === panel1);
      const index2 = panel2 && newStack.findIndex(p => p === panel2);
      if (index1 - 1 === indexKeep || index1 + 1 === indexKeep) {
        keepOpenIds.push(panel1.id);
      }
      if (panel2 && (index2 - 1 === indexKeep || index2 + 1 === indexKeep)) {
        keepOpenIds.push(panel2.id);
      }
      if (keepOpenIds.length === 1) {
        if (!openPanels.find(p => p.id === keepOpenId).isExpanded) {
          if (newStack.length - 1 === indexKeep) {
            keepOpenIds.push(newStack[indexKeep - 1].id);
          } else {
            keepOpenIds.push(newStack[indexKeep + 1].id);
          }
        }
      }
      for (const panel of newStack) {
        panel.isCollapsed = !keepOpenIds.includes(panel.id);
      }
    }
    this.panelsStack = newStack;
  }

  async internalOpenPanel(type: string, payload: any = {}, openIndex?: number, noEvents = false, noExpand = false) {
    let panel = this.panels[type];
    if (!panel) {
      panel = await this.searchPanel(type, this.panels);
      if (!panel) {
        console.error(`Panel type "${type}" not found`);
        return;
      }
      panel.type = type;
    }
    if (typeof panel.caption !== 'function') {
      throw new Error('Panel component must have a "caption" function');
    }
    const newPanel:any = {
      id: this.nextId++,
      nocard: panel.nocard,
      cssClass: panel.cssClass,
      title: panel.caption(this.$t.bind(this), payload),
      icon: panel.icon ? panel.icon(this.$t.bind(this), payload) : null,
      components: {
        default: panel.default ?? undefined,
        buttons: panel.buttons ?? undefined,
        header: panel.header ?? undefined,
        title: panel.title ?? undefined,
      },
      type,
      payload,
      isCollapsed: false,
      isCloseable: true,
      __events: {},
      inAppOnboarding: panel.inAppOnboarding ? panel.inAppOnboarding(this.$t.bind(this)) : null,
    };
    if (!this.panelsStack.length || openIndex === 0) {
      newPanel.isCloseable = false;
    }
    let newStack = [...this.panelsStack];
    if (panel.permanentExpanded && newStack.length) {
      for (const panel of newStack) {
        panel.isCollapsed = true;
      }
    }
    let isAnimation = true;
    if (typeof openIndex !== 'undefined') {
      isAnimation = newStack.length === openIndex;
      newStack = newStack.slice(0, openIndex);
    }
    if (newStack.length > 0 && !newStack.find(p => !p.isCollapsed) && !noExpand) {
      // якщо немає відкритих панелей, то перша панель має бути розгорнута
      newStack[0].isCollapsed = false;
    }
    this.panelsStack = newStack;
    return new Promise(res => {
      this.$nextTick(() => { // щоб панелі змінювались при редагуванні
        const n = newStack.length;
        newPanel.isAnimate = isAnimation;
        newPanel.permanentExpanded = !!panel.permanentExpanded;
        newPanel.emit = (event, ...args) => this.emitEvent(event, ...args);
        newPanel.open = (type, payload, index?:number) => this.openPanel(type, payload, index ?? n + 1);
        newPanel.close = () => this.closePanel(newPanel);
        newPanel.expand = () => this.expandPanel(newPanel);
        newPanel.getTitle = () => newPanel.title;
        newPanel.getIcon = () => newPanel.icon;
        newPanel.setTitle = (title: string) => { newPanel.title = title; this.updateTitle() };
        newPanel.setIcon = (icon: string) => { newPanel.icon = icon; };
        newPanel.on = (eventName: string|string[], func: (event: string, ...args: any[]) => any) => {
          const eventNames = Array.isArray(eventName) ? eventName : [eventName];
          for (const evName of eventNames) {
            if (!newPanel.__events[evName]) {
              newPanel.__events[evName] = [];
            }
            newPanel.__events[evName].push(func);
          }
        };
        newPanel.off = (eventName, func: (event: string, ...args: any[]) => any) => {
          if (newPanel.__events[eventName]) {
            newPanel.__events[eventName] = newPanel.__events[eventName].filter(f => f !== func);
          }
        };
        newPanel.once = (eventName, func: (event: string, ...args: any[]) => any) => {
          const wrapper = (...args) => {
            func(eventName, ...args);
            newPanel.off(eventName, wrapper);
          };
          newPanel.on(eventName, wrapper);
        };
        newPanel.isMultiple = () => this.isOpenMultiple;
        newPanel.fullsize = () => this.fullsizePanel(newPanel);
        newPanel.getPanels = (type) => this.getPanels(type);
        newPanel.getPayload = () => newPanel.payload;
        newPanel.setPayload = (value: any) => {
          newPanel.payload = value;
          this.setPanelHash();
        }
        newPanel.rebuildAllOnboardingPopovers = (refreshDelay?: number) => this.rebuildAllOnboardingPopovers(refreshDelay);
        newPanel.rebuildPanelOnboardingPopovers = (panelId: string | number, refreshDelay?: number) => {
          this.rebuildPanelOnboardingPopovers(panelId, refreshDelay);
        }
        newStack.push(newPanel);
        this.panelsStack = newStack;
        this.ensureOnlyTwoOpenPanels(newPanel.id);
        this.emitEvent('panel.open', newPanel);
        if (!noEvents) {
          this.emitEvent('panels.changed', this.panelsStack);
        }
        return res(newPanel);
      })
    });
  }

  updateTitle() {
    const titles = this.panelsStack.map(p => p.getTitle()).filter(Boolean).reverse();
    this.$root.$options.head.titleChunk = titles.join(' / ');
    this.$meta().refresh();
  }

  async openPanel(type: string, payload: any, openIndex?: number) {
    await this.internalOpenPanel(type, payload, openIndex);
    this.setPanelHash();
    if(openIndex) this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
  }

  emitEvent(event: string, ...args: any[]) {
    emitGlobalEvent(event, ...args);
    for (const panel of this.panelsStack) {
      if (panel.__events[event]) {
        for (const func of panel.__events[event]) {
          func(event, ...args);
        }
      }
    }
  }

  closePanel(panel: IPanel) {
    this.panelsStack = this.panelsStack.filter(p => p !== panel);
    let openPanel = this.panelsStack.find(p => !p.isCollapsed);
    if (!openPanel) {
      openPanel = this.panelsStack[this.panelsStack.length - 1];
      openPanel.isCollapsed = false;
    }
    const openPanelIndex = this.panelsStack.findIndex(p => p === openPanel);
    if (openPanelIndex > 0 && !openPanel?.permanentExpanded && !openPanel.isExpanded) {
      this.panelsStack[openPanelIndex - 1].isCollapsed = false;
    }
    this.ensureOnlyTwoOpenPanels(openPanel.id);
    this.setPanelHash();
    this.emitEvent('panels.closed', panel);
    this.emitEvent('panels.changed', this.panelsStack);
    this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
  }

  fullsizePanel(panel: IPanel) {
    const newStack = [...this.panelsStack];
    for (const p of newStack) {
      p.isLastOpened = !p.isCollapsed && p !== panel;
      p.isCollapsed = p !== panel;
      if (!p.isCollapsed) {
        p.isExpanded = true;
      }
    }
    this.panelsStack = newStack;
    this.setPanelHash();
    this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
  }

  get isFullSize() {
    return this.panelsStack.filter(p => !p.isCollapsed).length === 1;
  }

  expandPanel(panel: IPanel) {
    const newStack = [...this.panelsStack];
    const index = newStack.findIndex(p => p.id === panel.id);
    newStack[index].isCollapsed = false;
    for (const p of newStack) {
      p.isExpanded = false;
    }
    this.panelsStack = newStack;
    this.ensureOnlyTwoOpenPanels(panel.id);
    this.setPanelHash();
    this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
  }

  collapsePanel(panel: IPanel) {
    const newStack = [...this.panelsStack];
    const currenctIndex = newStack.findIndex(p => p.id === panel.id);
    const lastOpenedIndex = newStack.findIndex(p => p.isLastOpened);
    if (lastOpenedIndex !== -1) { // якщо зебрежена остання відкрита панель
      newStack[lastOpenedIndex].isCollapsed = false;
    } else if (newStack[currenctIndex-1]) { // якщо після оновлення сторінки відсутнє значення "остання відкрита", то відкриваємо ту, що зліва
      newStack[currenctIndex-1].isCollapsed = false;
    }
    for (const p of newStack) {
      p.isExpanded = false;
    }
    this.panelsStack = newStack;
    this.ensureOnlyTwoOpenPanels(panel.id);
    this.setPanelHash();
    this.$nextTick(() => { this.rebuildAllOnboardingPopovers() });
  }

  getPanels(type) {
    return this.panelsStack.filter(panel => panel.type === type);
  }

  getCurrentStack() {
    return [...this.panelsStack];
  }

  setPanelHash() {
    const hash = stackToHash(this.panelsStack, this.routeType === 'path').replace(/^#/, '');
    if (this.routeType === 'path') {
      this.$router.push({ path: `${hash}` });
    } else {
      this.$router.push({ hash });
    }
    this.updateTitle();
  }

  async parsePanelHash() {
    const hash = this.routeType === 'path' ? location.pathname : location.hash;
    if (hash) {
      const panels = hashToStack(hash, this.routeType === 'path');
      const newStack = [];
      this.panelsStack = [];
      for (const panelIndex in panels) {
        const panel = panels[panelIndex];
        if (this.panelsStack[panelIndex] && this.panelsStack[panelIndex].type === panel.type) {
          // reuse panel
          this.panelsStack[panelIndex].payload = panel.payload;
          this.panelsStack[panelIndex].isCollapsed = panel.isCollapsed;
          this.panelsStack[panelIndex].isExpanded = false;
          newStack.push(this.panelsStack[panelIndex]);
        } else {
          const resPanel = await this.internalOpenPanel(panel.type, panel.payload, undefined, true, true);
          if (resPanel) {
            resPanel.isCollapsed = panel.isCollapsed;
            resPanel.isExpanded = false;
            resPanel.isAnimate = false;
            newStack.push(resPanel);
          }
        }
      }
      const hasExpanded = newStack.length > 1 && newStack.filter(p => !p.isCollapsed).length === 1;
      if (hasExpanded) {
        newStack[newStack.findIndex(p => !p.isCollapsed)].isExpanded = true;
      }
      this.panelsStack = newStack;
      this.emitEvent('panels.changed', this.panelsStack);
      this.updateTitle();
    }
  }

  handlePopState() {
    this.parsePanelHash();
    // виправляє проблему відкритої панелі при натисканні кнопки "назад" до першої панелі
    if (this.panelsStack.length === 2) {
      this.panelsStack = this.panelsStack.slice(0, 1);
    }
  }

  beforeLeave (element) {
    element.classList.add('opacity-null');
    this.addAnimateClass(element);
  }

  addAnimateClass(element) {
    if (element.__vue__.$options.propsData.animate) {
      element.classList.add('animate');
    }
  }

  rebuildPanelOnboardingPopovers(panelId: string | number, refreshDelay?: number) {
    const panelRef = this.$refs[`panel-${panelId}`];
    if (panelRef?.[0]) panelRef[0].rebuildOnboardingPopovers(refreshDelay);
  }

  rebuildAllOnboardingPopovers(refreshDelay?: number) {
    this.panelsStack.forEach((panel) => {
      this.rebuildPanelOnboardingPopovers(panel.id, refreshDelay);
    });
  }
}
</script>
