<template>
  <div class="page-builder">
    <div class="flex gap-4 px-6 relative">
      <a
          @click="add"
          class="text-sm cursor-pointer flex items-center justify-center gap-1 rounded-[99px] border border-brand-600 bg-brand-500 px-3.5 py-2 font-semibold text-white hover:bg-brand-600 absolute right-[24px] top-[-55px]"
      >
        <PlusIcon class="h-5 w-5"></PlusIcon>
        <span>Add Module</span>
      </a>
      <ModuleSelectionModal
          ref="modalRef"
          :modules="availableModules"
          @addModule="addModule">
      </ModuleSelectionModal>
      <VModal ref="modalDeleteRef" entity="module" :callback="removeModule"></VModal>
      <div ref="sidebarRef" class="flex w-[356px] flex-col gap-2 pb-10 overflow-y-auto h-[90vh]">
        <div
            v-for="(parent, sectionIndex) in modelValue.sections"
            :key="`section-${sectionIndex}-${parent.title}`"
            class="border-b border-gray-200 pb-2"
            draggable="true"
            @dragstart="onDragStart(sectionIndex, $event)"
            @dragover="onDragOver(sectionIndex, $event)"
            @drop="onDrop(sectionIndex, $event)"
            :class="{'opacity-50': draggingIndex === sectionIndex, 'bg-gray-100': dragOverIndex === sectionIndex && draggingIndex !== sectionIndex}"
        >
          <div class="flex items-center justify-between px-2 py-4">
            <span class="text-lg font-semibold text-brand-800 flex items-center gap-1 cursor-pointer">
              <DragHandler class="mb-[1px]" /> {{ parent.title }} <TrashICon class="ml-1 w-[18px] h-[18px] cursor-pointer text-gray-900 hover:text-brand-600" @click="modalDeleteRef.open(sectionIndex)"></TrashICon>
            </span>
            <div class="relative flex items-center gap-3">
              <div class="cursor-pointer" @click="updateState(sectionIndex)">
                <ChevronRight class="h-5 w-5" v-if="!openStates[sectionIndex]"/>
                <ChevronDown class="h-5 w-5" v-else/>
              </div>
            </div>
          </div>
          <div class="flex flex-col gap-2" v-if="openStates[sectionIndex]">
            <template v-for="(component, index) in parent.components">
              <div
                  @click="selectComponent(sectionIndex, component, index)"
                  class="flex cursor-pointer items-center justify-between rounded-lg py-1.5 pl-6 pr-2 hover:bg-gray-100"
                  :class="{'bg-gray-200 hover:bg-gray-200': selected?.sectionIndex === sectionIndex && selected?.componentIndex === index}"
              >
                <div class="flex flex-col">
                  <div class="text-xs text-gray-600">Sub-module</div>
                  <div class="text-sm font-medium text-gray-900">
                    {{ component.name }}
                  </div>
                </div>
                <div class="flex items-center justify-between gap-3 px-[10px]">
                  <ChevronRight class="h-5 w-5"/>
                </div>
              </div>
            </template>
          </div>
        </div>
      </div>
      <div class="flex flex-1 flex-col mb-10">
        <div class="mb-4 px-6 py-5 rounded-xl bg-gray-50" v-if="!selected">
          <div class="text-lg pb-6 font-semibold text-gray-900 border-b border-gray-200">Settings</div>
          <div class="flex flex-col gap-1 pt-6 text-gray-600">
            <input-wrapper
                field="breadcrumb"
                label-text="Breadcrumb section"
                class="w-full">
              <select id="breadcrumb" v-model="modelValue.breadcrumb" name="url">
                <option
                    :value="b"
                    v-for="(b) in breadcrumbs">
                  {{ b }}
                </option>
              </select>
            </input-wrapper>
            <p>This sets the section name shown in the breadcrumb (e.g. Home / Who We Are / Page). Leave blank to skip the section.</p>
          </div>
        </div>
        <div class="rounded-xl bg-gray-50 px-6 py-5" v-if="!selected">
          <Instructions/>
        </div>
        <transition name="preview-fade">
          <div
              v-if="previewVisible"
              :style="overlayStyle"
              class="fixed z-[9999] pointer-events-none overflow-hidden rounded-md shadow-lg bg-gray-100 border border-gray-300 diagonal-stripes"
          >
            <img :src="previewSrc" alt="preview" class="w-full h-full object-contain" />
          </div>
        </transition>
        <div v-if="selected && currentStyle" class="flex flex-col items-start gap-2 w-full bg-gray-50 px-6 py-2 rounded-xl mb-4">
          <p class="text-lg font-semibold text-gray-900">Styles</p>
          <div class="mt-3 w-full">
            <div
                class="flex gap-3 overflow-x-auto overflow-y-hidden p-1 items-center flex-nowrap"
                style="-webkit-overflow-scrolling: touch;"
            >
              <template v-for="(styleDef, key) in stylesList" :key="key">
                <div
                    class="flex-none cursor-pointer rounded-md border p-1 bg-white transform transition-all duration-150"
                    :class="{'ring-2 ring-brand-500 z-20': key === currentStyle}"
                    style="width: 96px;"
                    @click="selectStyle(key)"
                    @mouseenter="showPreview($event, styleDef?.example)"
                    @mouseleave="hidePreview()"
                >
                  <img :src="styleDef?.example" alt="" class="w-full h-[64px] object-cover rounded-sm" />
                  <div class="text-xs text-center mt-1 truncate">{{ capitalize(key.replace('_', ' ')) }}</div>
                </div>
              </template>
            </div>
          </div>
        </div>
        <div class="h-[70vh] overflow-y-auto" v-if="selected">
          <component
              :is="currentComponent"
              :key="`${selected?.sectionIndex} - ${selected?.componentIndex} ${Math.random().toString(36).substring(2, 10)}`"
              :data="selected"
              :sites="sites"
              :forms="forms"
              @update="save"
          ></component>
        </div>
      </div>
    </div>
    <slot>
      <div class="flex justify-between space-x-xsSpace pt-xsSpace gap-4 sticky bottom-0 w-full bg-gray-200 py-2 px-6">
        <a @click="close"
           class="w-[200px] py-[9px] bg-white rounded-full border border-gray-300 shadow-xs text-md font-semibold hover:bg-gray-100 text-gray-700 text-center cursor-pointer">
          Cancel
        </a>
        <a @click.prevent="save"
           class="w-full py-[9px] rounded-full shadow-xs text-md font-semibold text-white bg-brand-600 hover:bg-brand-700 border border-brand-600 text-center cursor-pointer"
        >Save changes</a>
      </div>
    </slot>
  </div>
</template>
<script setup>
import {ref, markRaw, computed, nextTick} from "vue";
import ChevronRight from "@/assets/img/icons/chevron-right.svg";
import ChevronDown from "@/assets/img/icons/chevron-down.svg";
import Instructions from "@/components/builders/Instructions.vue";
import VItems from "@/components/builders/Items.vue";
import VLinks from "@/components/builders/Links.vue";
import VHeader from "@/components/builders/Header.vue";
import VLogos from "@/components/builders/Logos.vue";
import VCollectionCarousel from "@/components/builders/CollectionCarousel.vue";
import VNewsGrid from "@/components/builders/NewsGrid.vue";
import VVideoGrid from "@/components/builders/VideoGrid.vue";
import VParagraph from "@/components/builders/Paragraph.vue";
import VImageBlock from "@/components/builders/ImageBlock.vue";
import InputWrapper from "@/components/common/InputWrapper.vue";
import ContactUs from "@/components/builders/ContactUs.vue";
import PlusIcon from "@/assets/img/icons/plus.svg";
import DragHandler from "@/assets/img/icons/drag-handle.svg";
import HorizontalTabs from "@/components/builders/HorizontalTabs.vue";
import ModuleSelectionModal from "@/components/common/ModuleSelectionModal.vue";
import {capitalize} from "@/components/helpers/common";
import availableModules from "@/components/helpers/availableModules";
import dragDropService from "@/components/helpers/dragDropService";
import pageBuilderService from "@/components/helpers/pageBuilderService";
import TrashICon from "@/assets/img/icons/trash-01.svg";
import VModal from "@/components/common/Modal.vue";

const emit = defineEmits(["save", "close", "update:modelValue"]);

const props = defineProps({
  modelValue: {
    required: true,
    type: Object,
  },
  sites: {
    type: Array,
    default: () => {
      return []
    }
  },
  forms: {
    type: Array,
    default: () => {
      return []
    }
  },
  breadcrumbs: {
    type: Array,
    default: () => {
      return []
    }
  },
});

pageBuilderService.initOriginalSections(props.modelValue.sections || []);
const openStates = ref(JSON.parse(window.localStorage.getItem("pageBuilderOpenStates")));
const sections = ref(props.modelValue?.sections ?? []);

const draggingIndex = ref(null);
const dragOverIndex = ref(null);

let selected = ref(null);
const modalRef = ref(null);
const modalDeleteRef = ref(null);
const sidebarRef = ref(null);
const componentMaps = ref({
  sliders: markRaw(VItems),
  vertical_tabs: markRaw(VItems),
  links: markRaw(VLinks),
  header: markRaw(VHeader),
  callout: markRaw(VHeader),
  link_grid: markRaw(VLinks),
  logos: markRaw(VLogos),
  carousel: markRaw(VCollectionCarousel),
  news_grid: markRaw(VNewsGrid),
  video_grid: markRaw(VVideoGrid),
  paragraph: markRaw(VParagraph),
  image_block: markRaw(VImageBlock),
  timeline_items: markRaw(VItems),
  bullet_points: markRaw(VLinks),
  pricing: markRaw(VLinks),
  feature_items: markRaw(VLinks),
  faqs: markRaw(VLinks),
  link_cards: markRaw(VItems),
  contact_us: markRaw(ContactUs),
  horizontal_tabs: markRaw(HorizontalTabs),
  testimonial_slider: markRaw(VItems),
});

if (!openStates.value) {
  openStates.value = {};

  sections.value.forEach((item, index) => {
    openStates.value[index] = true;
  });

  window.localStorage.setItem("pageBuilderOpenStates", JSON.stringify(openStates.value));
}

const updateState = (sectionIndex) => {
  openStates.value[sectionIndex] = !openStates.value[sectionIndex];
  window.localStorage.setItem("pageBuilderOpenStates", JSON.stringify(openStates.value));
};

const selectComponent = (sectionIndex, component, index) => {
  selected.value = {
    page: props.modelValue,
    component: component,
    componentIndex: index,
    sectionIndex: sectionIndex,
  };
};

const close = () => {
  emit("close", props.modelValue);
};

const stripIsNewFromSections = (sections = []) => {
  return (sections || []).map(s => {
    const copy = JSON.parse(JSON.stringify(s));
    if (copy && typeof copy === 'object') {
      delete copy._isNew;
    }
    return copy;
  });
};

const save = (redirect = true, newModel = null) => {
  const cleanedSections = stripIsNewFromSections(props.modelValue.sections || []);
  const cleanedModel = { ...props.modelValue, sections: cleanedSections };
  emit("save", { page: newModel ? newModel : cleanedModel, redirect: redirect });

  pageBuilderService.initOriginalSections(cleanedSections);
};

defineExpose({ save, close });

const currentComponent = computed(() => {
  if (!selected.value?.component?.type) {
    return '';
  }

  return componentMaps.value[selected.value?.component?.type];
});

const add = () => {
  modalRef.value.open();
};

const addModule = (module) => {
  const newSections = pageBuilderService.addModule(props.modelValue.sections || [], module);
  const newModel = { ...props.modelValue, sections: newSections };
  sections.value = newSections;
  openStates.value[newSections.length - 1] = true;
  window.localStorage.setItem("pageBuilderOpenStates", JSON.stringify(openStates.value));
  emit("update:modelValue", newModel);
  modalRef.value?.close?.();

  nextTick(() => {
    const el = sidebarRef.value;
    if (!el) return;
    // smooth scroll to bottom (new module is appended)
    if (el.scrollTo) {
      el.scrollTo({ top: el.scrollHeight, behavior: "smooth" });
    } else {
      el.scrollTop = el.scrollHeight;
    }
  });

  save(true, newModel);
};

const updateStyle = (sectionIndex, styleName) => {
  const currentSections = props.modelValue.sections || [];
  const newSections = pageBuilderService.updateStyle(currentSections, sectionIndex, styleName);
  sections.value = newSections;
  const newModel = { ...props.modelValue, sections: newSections };
  emit("update:modelValue", newModel);

  // adjust selected component if needed (preserve previous behavior)
  if (selected.value && selected.value.sectionIndex === sectionIndex) {
    const compIndex = selected.value.componentIndex;
    const comps = newSections[sectionIndex]?.components || [];
    const newComp = comps[compIndex] ?? comps[0] ?? null;
    selected.value = {
      ...selected.value,
      componentIndex: newComp ? (comps.indexOf(newComp) >= 0 ? comps.indexOf(newComp) : 0) : null,
      component: newComp,
    };
  }
};

const currentStyle = computed(() => {
  const sectionIndex = selected.value?.sectionIndex;
  const section = props.modelValue?.sections?.[sectionIndex];
  if (!section) return null;

  const mod = availableModules?.[section.title];
  if (!mod || typeof mod !== "object") return null;

  return section?.style || pageBuilderService.detectStyleForSection(section);
});

const currentStyleObject = computed(() => {
  const sectionIndex = selected.value?.sectionIndex;
  const section = props.modelValue?.sections?.[sectionIndex];
  if (!section) return null;

  const mod = availableModules?.[section.title];
  if (!mod || typeof mod !== 'object') return null;

  const styleKey = section?.style || pageBuilderService.detectStyleForSection(section);
  if (!styleKey) return null;

  const styleDef = mod[styleKey];
  if (!styleDef) return null;

  return JSON.parse(JSON.stringify(styleDef));
});

const removeModule = (sectionIndex) => {
  const current = props.modelValue.sections || [];
  const result = pageBuilderService.removeModule(current, sectionIndex, openStates.value, selected.value);

  // apply returned values
  sections.value = result.sections;
  openStates.value = result.openStates;
  window.localStorage.setItem("pageBuilderOpenStates", JSON.stringify(openStates.value));

  const newModel = { ...props.modelValue, sections: result.sections };
  emit("update:modelValue", newModel);

  // apply selected changes returned by service
  selected.value = result.selected;
};

const onDragStart = (index, event) => {
  dragDropService.startDrag(event, index, draggingIndex);
};

const onDragOver = (index, event) => {
  event.preventDefault();
  dragDropService.dragOver(index, draggingIndex, dragOverIndex);
};

const onDrop = (targetIndex, event) => {
  event.preventDefault();
  const current = props.modelValue.sections || [];
  const result = dragDropService.reorderOnDrop(event, targetIndex, current, openStates.value, selected.value);

  // apply returned values
  sections.value = result.sections;
  openStates.value = result.openStates;
  window.localStorage.setItem("pageBuilderOpenStates", JSON.stringify(openStates.value));
  const newModel = { ...props.modelValue, sections: result.sections };
  emit("update:modelValue", newModel);
  selected.value = result.selected;

  // reset indicators
  dragOverIndex.value = null;
  draggingIndex.value = null;
};

const previewVisible = ref(false);
const previewSrc = ref('');
const previewRect = ref({ top: 0, left: 0, width: 0, height: 0 });
const previewScale = 2.5; // 2x

const showPreview = (event, srcOverride) => {
  const el = event?.currentTarget;
  if (!el) return;
  const rect = el.getBoundingClientRect();
  previewRect.value = {
    top: 380,
    left: rect.left,
    width: 250,
    height: 100,
  };
  previewSrc.value = srcOverride || el.src || currentStyleObject?.example || '';
  previewVisible.value = true;
};

const hidePreview = () => {
  previewVisible.value = false;
};

const overlayStyle = computed(() => {
  if (!previewVisible.value) return { display: 'none' };
  const sw = previewRect.value.width * previewScale;
  const sh = previewRect.value.height * previewScale;
  const top = previewRect.value.top - (sh - previewRect.value.height) / 2;
  const left = previewRect.value.left - (sw - previewRect.value.width) / 2;
  return {
    top: `${Math.max(4, top)}px`,
    left: `${Math.max(4, left)}px`,
    width: `${sw}px`,
    height: `${sh}px`,
  };
});

const stylesList = computed(() => {
  const sectionIndex = selected.value?.sectionIndex;
  const section = props.modelValue?.sections?.[sectionIndex];
  if (!section) return {};
  return availableModules?.[section.title] || {};
});

const selectStyle = (styleName) => {
  if (!selected.value) return;
  updateStyle(selected.value.sectionIndex, styleName);
};
</script>
<style scoped>
.preview-fade-enter-active,
.preview-fade-leave-active {
  transition: opacity 200ms ease, transform 200ms ease;
}
.preview-fade-enter-from,
.preview-fade-leave-to {
  opacity: 0;
  transform: scale(0.98);
}
.preview-fade-enter-to,
.preview-fade-leave-from {
  opacity: 1;
  transform: scale(1);
}

.diagonal-stripes {
  padding: 8px;
  background: repeating-linear-gradient(
      45deg,
      #bdbdbd 0px,
      #bdbdbd 0.5px,
      rgb(229 231 235 / 1) 1px,
      rgb(229 231 235 / 1) 10px
  );
}
</style>