<script lang="ts">
  import { onDestroy } from "svelte";
  import Sortable from "sortablejs";
  import { settingsState } from "@/seqta/utils/listeners/SettingsState";
  import {
    restoreCustomMenuActive,
    sidebarState,
  } from "./sidebarState.svelte";
  import SidebarItem from "./SidebarItem.svelte";
  import type {
    SidebarDrillFrame,
    SidebarItem as SidebarItemModel,
  } from "./types";

  type Props = {
    menuEl: HTMLElement;
  };

  let { menuEl }: Props = $props();

  let listEl = $state<HTMLElement | null>(null);
  let coverEl: HTMLElement | null = null;
  let sortable: Sortable | null = null;
  let dragging = $state(false);

  const BACK_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" aria-hidden="true"><g style="fill: currentcolor;"><path d="M15.422 16.078l-1.406 1.406-6-6 6-6 1.406 1.406-4.594 4.594z"></path></g></svg>`;

  function itemVisible(key: string): boolean {
    return (
      (settingsState.menuitems as Record<string, { toggle?: boolean }>)[key]
        ?.toggle !== false
    );
  }

  function onActivate(item: SidebarItemModel) {
    sidebarState.activateItem(item, menuEl);
  }

  function closeEdit() {
    sidebarState.setEditMode(false);
    void import("@/seqta/utils/Openers/menuOptionsState").then((mod) => {
      mod.setMenuOptionsOpen(false);
    });
  }

  const drillFolders = $derived(
    sidebarState.drillStack.map((frame) => ({
      frame,
      folder: sidebarState.findByKey(frame.key),
    })),
  );

  function onToggleVisible(key: string, visible: boolean) {
    sidebarState.setItemVisibility(key, visible);
  }

  function destroySortable() {
    sortable?.destroy();
    sortable = null;
    dragging = false;
    document.documentElement.style.removeProperty("--bsplus-drag-width");
  }

  function syncSortableFromState() {
    if (!sortable || dragging) return;
    const order = sidebarState.editRootItems.map((item) => item.key);
    sortable.sort(order, true);
  }

  function restoreDefault() {
    sidebarState.restoreDefaultOrder();
    queueMicrotask(syncSortableFromState);
  }

  $effect(() => {
    const editing = sidebarState.editMode;
    const list = listEl;

    destroySortable();
    if (!editing || !list) return;

    sortable = Sortable.create(list, {
      animation: 220,
      easing: "cubic-bezier(0.22, 1, 0.36, 1)",
      draggable: ".bsplus-sidebar-item.draggable",
      filter: ".toggle, .toggle input, .bsplus-sidebar-edit-header, .bsplus-sidebar-edit-actions",
      preventOnFilter: false,
      dataIdAttr: "data-key",
      ghostClass: "bsplus-sortable-ghost",
      chosenClass: "bsplus-sortable-chosen",
      dragClass: "bsplus-sortable-drag",
      forceFallback: true,
      fallbackOnBody: true,
      fallbackTolerance: 3,
      swapThreshold: 0.65,
      direction: "vertical",
      onStart: (evt) => {
        dragging = true;
        const width = evt.item.getBoundingClientRect().width;
        document.documentElement.style.setProperty(
          "--bsplus-drag-width",
          `${Math.round(width)}px`,
        );
        // Fallback clone is created just after onStart; pin size + drop
        // `.active` so theme/active rules don't expand it to full width.
        requestAnimationFrame(() => {
          const dragEl = document.querySelector(
            ".bsplus-sortable-drag",
          ) as HTMLElement | null;
          if (!dragEl) return;
          dragEl.style.width = `${Math.round(width)}px`;
          dragEl.style.maxWidth = `${Math.round(width)}px`;
          dragEl.classList.remove("active");
        });
      },
      onEnd: (evt) => {
        dragging = false;
        document.documentElement.style.removeProperty("--bsplus-drag-width");
        if (evt.from !== evt.to) return;
        if (evt.oldIndex == null || evt.newIndex == null) return;
        if (evt.oldIndex === evt.newIndex) return;
        sidebarState.applyMenuOrder(sortable?.toArray() ?? []);
      },
    });

    return () => {
      destroySortable();
    };
  });

  $effect(() => {
    const editing = sidebarState.editMode;
    const container = document.getElementById("container");

    if (editing && container && !coverEl) {
      const cover = document.createElement("div");
      cover.className = "notMenuCover bsplus-sidebar-edit-cover";
      cover.addEventListener("click", closeEdit);
      container.appendChild(cover);
      menuEl.style.zIndex = "20";
      menuEl.classList.add("bsplus-sidebar-edit-mode");
      coverEl = cover;
    }

    if (!editing) {
      coverEl?.remove();
      coverEl = null;
      menuEl.style.removeProperty("z-index");
      menuEl.classList.remove("bsplus-sidebar-edit-mode");
    }

    return () => {
      coverEl?.remove();
      coverEl = null;
      menuEl.style.removeProperty("z-index");
    };
  });

  // SEQTA strips `.active` from `#menu li` after clicks — re-apply from state.
  // Do NOT MutationObserver class changes here: restore writes `.active`, SEQTA
  // strips it again, and the feedback loop freezes the tab.
  $effect(() => {
    void sidebarState.activeKey;
    void sidebarState.isDrilling;
    restoreCustomMenuActive();
  });

  // Drill `.sub` is position:absolute inside this scrollport — if the list was
  // scrolled down (e.g. Folios/Goals near the bottom), the panel sits under the
  // logo until we reset. Also reset when going back up the stack.
  $effect(() => {
    void sidebarState.drillStack.length;
    void sidebarState.enterFrameKey;
    const root = document.getElementById("bsplus-sidebar-root");
    if (!root) return;
    root.scrollTop = 0;
    requestAnimationFrame(() => {
      root.scrollTop = 0;
    });
  });

  onDestroy(() => {
    destroySortable();
    coverEl?.remove();
    coverEl = null;
  });
</script>

<!--
  Direct `#menu > ul.logo-link` child so theme selectors like
  `#menu > ul > li.item` / `#menu .sub` / `:has(> ul > li.hasChildren.active)` match.
-->
<ul
  id="bsplus-sidebar-root"
  class="logo-link bsplus-sidebar-list"
  class:noscroll={sidebarState.isDrilling}
  class:drilling={sidebarState.isDrilling}
  class:drill-entering={sidebarState.enterFrameKey != null}
  class:drill-returning={sidebarState.drillReturning}
  class:compact={sidebarState.compact}
  class:edit-mode={sidebarState.editMode}
  class:is-sorting={dragging}
  aria-label="Main"
  bind:this={listEl}
>
  {#if sidebarState.editMode}
    <li class="item bsplus-sidebar-edit-header" aria-hidden="true">
      <!-- svelte-ignore a11y_label_has_associated_control -->
      <label><span class="label">Edit Sidebar</span></label>
    </li>
    {#each sidebarState.editRootItems as item (item.key)}
      <SidebarItem
        {item}
        active={sidebarState.activeKey === item.key}
        compact={false}
        editMode={true}
        visible={itemVisible(item.key)}
        {onActivate}
        {onToggleVisible}
      />
    {/each}
    <li class="item bsplus-sidebar-edit-actions">
      <button type="button" class="edit-btn" onclick={restoreDefault}>
        Restore Default
      </button>
      <button type="button" class="edit-btn primary" onclick={closeEdit}>
        Save
      </button>
    </li>
  {:else if drillFolders.length}
    <!-- Keep root rows in the DOM so sidebar-animation.scss can slide siblings.
         Never mark root leaves active while drilling — theme decorations
         (Beach palm/sand) would show through the transparent .sub panel. -->
    {#each sidebarState.visibleRootItems as item (item.key)}
      {#if item.key === drillFolders[0].frame.key}
        {@render drillLevel(drillFolders, 0)}
      {:else}
        <SidebarItem
          {item}
          active={false}
          compact={false}
          editMode={false}
          visible={true}
          {onActivate}
        />
      {/if}
    {/each}
  {:else}
    {#each sidebarState.visibleRootItems as item (item.key)}
      <SidebarItem
        {item}
        active={sidebarState.activeKey === item.key}
        compact={sidebarState.compact}
        editMode={false}
        visible={true}
        {onActivate}
      />
    {/each}
  {/if}
</ul>

{#snippet drillLevel(
  frames: Array<{ frame: SidebarDrillFrame; folder: SidebarItemModel | null }>,
  depth: number,
)}
  {@const current = frames[depth]}
  {@const isLast = depth === frames.length - 1}
  {@const nextKey = isLast ? null : frames[depth + 1].frame.key}
  {@const folder = current.folder}
  <li
    class="item hasChildren bsplus-sidebar-item"
    class:active={true}
    data-key={current.frame.key}
    data-path={folder?.path ?? undefined}
    style:--item-colour={folder?.itemColour || undefined}
  >
    <!-- svelte-ignore a11y_label_has_associated_control -->
    <label>
      {#if folder?.iconHtml}
        {@html folder.iconHtml}
      {/if}
      <span class="label">{current.frame.label}</span>
    </label>
    <div
      class="sub"
      class:bsplus-sub-enter={sidebarState.enterFrameKey === current.frame.key}
      onanimationend={(e) => {
        if (e.target !== e.currentTarget) return;
        if (sidebarState.enterFrameKey === current.frame.key) {
          sidebarState.clearEnterFrame(current.frame.key);
        }
      }}
    >
      <div class="nav">
        <!-- svelte-ignore a11y_no_static_element_interactions -->
        <div
          class="back"
          role="button"
          tabindex="0"
          aria-label="Back"
          onclick={(e) => {
            e.preventDefault();
            e.stopPropagation();
            sidebarState.goBack();
          }}
          onkeydown={(e) => {
            if (e.key === "Enter" || e.key === " ") {
              e.preventDefault();
              e.stopPropagation();
              sidebarState.goBack();
            }
          }}
        >
          {@html BACK_SVG}
          <div class="backLabel">{current.frame.label}</div>
        </div>
      </div>
      <ul>
        {#if isLast}
          {#each current.frame.items as item (item.key)}
            <SidebarItem
              {item}
              active={sidebarState.activeKey === item.key}
              compact={false}
              editMode={false}
              visible={true}
              drillEnter={sidebarState.enterFrameKey === current.frame.key}
              {onActivate}
            />
          {/each}
        {:else}
          {#each current.frame.items as item (item.key)}
            {#if item.key === nextKey}
              {@render drillLevel(frames, depth + 1)}
            {:else}
              <SidebarItem
                {item}
                active={sidebarState.activeKey === item.key}
                compact={false}
                editMode={false}
                visible={true}
                {onActivate}
              />
            {/if}
          {/each}
        {/if}
      </ul>
    </div>
  </li>
{/snippet}

<style>
  .bsplus-sidebar-list {
    list-style: none;
    margin: 0;
    padding: 0 0 16px;
    flex: 1 1 auto;
    min-height: 0;
    width: 100%;
    height: 100%;
    overflow-y: auto;
    overflow-x: hidden;
    position: relative;
    box-sizing: border-box;
    color: var(--text-color, #fff);
    scrollbar-width: thin;
    z-index: 2;
  }

  .bsplus-sidebar-list.noscroll,
  .bsplus-sidebar-list.drilling {
    overflow: hidden;
  }

  .bsplus-sidebar-list.compact {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .bsplus-sidebar-list.is-sorting {
    cursor: grabbing;
    user-select: none;
  }

  .bsplus-sidebar-edit-header {
    pointer-events: none;
    width: 85%;
    margin: 8px auto 4px;
    font-weight: 700;
    font-size: 16px;
  }

  .bsplus-sidebar-edit-header > label {
    display: block;
    padding: 12px 16px 4px;
  }

  .bsplus-sidebar-edit-actions {
    display: flex;
    gap: 8px;
    width: calc(100% - 12px);
    margin: 12px 6px 16px;
    padding: 0;
    list-style: none;
    cursor: default;
    box-sizing: border-box;
  }

  .edit-btn {
    flex: 1 1 0;
    min-width: 0;
    padding: 10px 8px;
    border: none;
    border-radius: 10px;
    background: rgba(0, 0, 0, 0.2);
    color: inherit;
    font-family: inherit;
    font-size: 13px;
    font-weight: 700;
    line-height: 1.2;
    white-space: nowrap;
    cursor: pointer;
    transition:
      background-color 0.2s ease,
      transform 0.2s ease;
  }

  .edit-btn:hover {
    background: rgba(0, 0, 0, 0.32);
    transform: scale(1.02);
  }

  .edit-btn:active {
    transform: scale(0.98);
  }

  .edit-btn.primary {
    background: rgba(0, 0, 0, 0.4);
  }

  /* SortableJS classes (applied to our items; must be :global). */
  .bsplus-sidebar-list :global(.bsplus-sortable-ghost) {
    opacity: 0.35 !important;
    background: rgba(255, 255, 255, 0.08) !important;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
  }

  .bsplus-sidebar-list :global(.bsplus-sortable-chosen) {
    background: rgba(0, 0, 0, 0.28) !important;
  }

  .bsplus-sidebar-list :global(.bsplus-sortable-drag),
  :global(.bsplus-sortable-drag) {
    opacity: 1 !important;
    cursor: grabbing !important;
    z-index: 10000 !important;
    width: var(--bsplus-drag-width, 240px) !important;
    max-width: var(--bsplus-drag-width, 240px) !important;
    box-sizing: border-box !important;
    box-shadow:
      0 14px 32px rgba(0, 0, 0, 0.35),
      0 0 0 1px rgba(255, 255, 255, 0.12) !important;
    transform: scale(1.03);
    background: rgba(0, 0, 0, 0.45) !important;
  }
</style>
