<script lang="ts">
  import { config, autoSave } from "../stores/config";
  import { breakActivities, getCategoryLabel } from "../utils/activities";
  import ToggleSwitch from "./ToggleSwitch.svelte";
  import { pressable } from "../utils/animate";

  const categories = ["eyes", "stretch", "breathing", "movement"] as const;
  let expandedCategory = $state<string | null>(null);
  let newActivityText = $state("");
  let newActivityCategory = $state<string>("eyes");
  let dropdownOpen = $state(false);
  let dropdownRef = $state<HTMLElement>(undefined!);
  let dropdownTriggerRef = $state<HTMLElement>(undefined!);
  let focusedOptionIndex = $state(-1);

  // Close dropdown on outside click
  function handleOutsideClick(e: MouseEvent) {
    if (dropdownRef && !dropdownRef.contains(e.target as Node)) {
      dropdownOpen = false;
    }
  }

  $effect(() => {
    if (dropdownOpen) {
      document.addEventListener("mousedown", handleOutsideClick);
      // Set initial focused option to current selection
      focusedOptionIndex = categories.indexOf(newActivityCategory as typeof categories[number]);
      return () => document.removeEventListener("mousedown", handleOutsideClick);
    }
  });

  // Focus the highlighted option when index changes
  $effect(() => {
    if (dropdownOpen && focusedOptionIndex >= 0) {
      const options = dropdownRef?.querySelectorAll<HTMLElement>('[role="option"]');
      options?.[focusedOptionIndex]?.focus();
    }
  });

  function handleDropdownKeydown(e: KeyboardEvent) {
    if (!dropdownOpen) {
      // Open on ArrowDown/Up/Enter/Space when closed
      if (["ArrowDown", "ArrowUp", "Enter", " "].includes(e.key)) {
        e.preventDefault();
        dropdownOpen = true;
      }
      return;
    }

    switch (e.key) {
      case "Escape":
        e.preventDefault();
        dropdownOpen = false;
        dropdownTriggerRef?.focus();
        break;
      case "ArrowDown":
        e.preventDefault();
        focusedOptionIndex = Math.min(focusedOptionIndex + 1, categories.length - 1);
        break;
      case "ArrowUp":
        e.preventDefault();
        focusedOptionIndex = Math.max(focusedOptionIndex - 1, 0);
        break;
      case "Home":
        e.preventDefault();
        focusedOptionIndex = 0;
        break;
      case "End":
        e.preventDefault();
        focusedOptionIndex = categories.length - 1;
        break;
      case "Enter":
      case " ":
        e.preventDefault();
        if (focusedOptionIndex >= 0) {
          newActivityCategory = categories[focusedOptionIndex];
          dropdownOpen = false;
          dropdownTriggerRef?.focus();
        }
        break;
      case "Tab":
        dropdownOpen = false;
        break;
    }
  }

  function markChanged() {
    autoSave();
  }

  function toggleCategory(cat: string) {
    expandedCategory = expandedCategory === cat ? null : cat;
  }

  function isBuiltinDisabled(text: string): boolean {
    return $config.disabled_builtin_activities.includes(text);
  }

  function isBuiltinFavorite(text: string): boolean {
    return $config.favorite_builtin_activities.includes(text);
  }

  function toggleBuiltinEnabled(text: string) {
    if (isBuiltinDisabled(text)) {
      $config.disabled_builtin_activities = $config.disabled_builtin_activities.filter((t) => t !== text);
    } else {
      $config.disabled_builtin_activities = [...$config.disabled_builtin_activities, text];
    }
    markChanged();
  }

  function toggleBuiltinFavorite(text: string) {
    if (isBuiltinFavorite(text)) {
      $config.favorite_builtin_activities = $config.favorite_builtin_activities.filter((t) => t !== text);
    } else {
      $config.favorite_builtin_activities = [...$config.favorite_builtin_activities, text];
    }
    markChanged();
  }

  function addCustomActivity() {
    const trimmed = newActivityText.trim();
    if (!trimmed || trimmed.length > 500) return;
    if ($config.custom_activities.length >= 100) return;

    const id = `custom-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
    $config.custom_activities = [
      ...$config.custom_activities,
      { id, category: newActivityCategory, text: trimmed, is_favorite: false, enabled: true },
    ];
    newActivityText = "";
    markChanged();
  }

  function removeCustomActivity(id: string) {
    $config.custom_activities = $config.custom_activities.filter((a) => a.id !== id);
    markChanged();
  }

  function toggleCustomEnabled(id: string) {
    $config.custom_activities = $config.custom_activities.map((a) =>
      a.id === id ? { ...a, enabled: !a.enabled } : a,
    );
    markChanged();
  }

  function toggleCustomFavorite(id: string) {
    $config.custom_activities = $config.custom_activities.map((a) =>
      a.id === id ? { ...a, is_favorite: !a.is_favorite } : a,
    );
    markChanged();
  }

  // Get counts per category
  function builtinCount(cat: string): number {
    return breakActivities.filter((a) => a.category === cat).length;
  }
  function customCount(cat: string): number {
    return $config.custom_activities.filter((a) => a.category === cat).length;
  }
  function disabledCount(cat: string): number {
    return breakActivities
      .filter((a) => a.category === cat)
      .filter((a) => isBuiltinDisabled(a.text)).length
      + $config.custom_activities
        .filter((a) => a.category === cat)
        .filter((a) => !a.enabled).length;
  }

  // ── Svelte action: blocks mousedown from reaching parent dragScroll ──
  // Uses native addEventListener (not Svelte delegation) so stopPropagation
  // fires BEFORE the parent's node-level mousedown handler in bubble phase.
  function blockParentDrag(node: HTMLElement) {
    function stop(e: MouseEvent) { e.stopPropagation(); }
    node.addEventListener("mousedown", stop);
    return { destroy() { node.removeEventListener("mousedown", stop); } };
  }

  // ── Svelte action: inner drag-scroll with momentum, overscroll, custom scrollbar ──
  const prefersReducedMotion = typeof window !== "undefined"
    ? window.matchMedia("(prefers-reduced-motion: reduce)").matches
    : false;

  function innerDragScroll(node: HTMLElement) {
    const scrollEl = node.querySelector(".activity-scroll") as HTMLElement;
    const content = scrollEl?.querySelector(".scroll-content") as HTMLElement;
    if (!scrollEl || !content) return { destroy() {} };

    // Reduced motion: skip custom scroll physics, allow normal scroll
    if (prefersReducedMotion) {
      // Still block wheel propagation for isolation
      function onWheel(e: WheelEvent) {
        e.stopPropagation();
        const { scrollTop, scrollHeight, clientHeight } = scrollEl;
        const atTop = scrollTop <= 0 && e.deltaY < 0;
        const atBottom = scrollTop + clientHeight >= scrollHeight && e.deltaY > 0;
        if (!atTop && !atBottom) {
          e.preventDefault();
          scrollEl.scrollTop += e.deltaY;
        }
      }
      node.addEventListener("wheel", onWheel, { passive: false });
      return { destroy() { node.removeEventListener("wheel", onWheel); } };
    }

    // Create custom scrollbar thumb
    const thumb = document.createElement("div");
    thumb.style.cssText =
      "position:absolute;right:2px;top:0;width:3px;border-radius:1.5px;" +
      "background:rgba(255,255,255,0.15);opacity:0;transition:opacity 0.3s ease;" +
      "z-index:10;pointer-events:none;";
    node.appendChild(thumb);

    let hideTimer: ReturnType<typeof setTimeout> | null = null;
    let coastFrame = 0;
    let overscroll = 0;

    function getMaxScroll() {
      return scrollEl.scrollHeight - scrollEl.clientHeight;
    }

    function showThumb() {
      const { scrollTop, scrollHeight, clientHeight } = scrollEl;
      if (scrollHeight <= clientHeight) { thumb.style.opacity = "0"; return; }
      const h = Math.max(20, (clientHeight / scrollHeight) * clientHeight);
      const t = (scrollTop / (scrollHeight - clientHeight)) * (clientHeight - h);
      thumb.style.height = `${h}px`;
      thumb.style.transform = `translateY(${t}px)`;
      thumb.style.opacity = "1";
      if (hideTimer) clearTimeout(hideTimer);
      hideTimer = setTimeout(() => { thumb.style.opacity = "0"; }, 1000);
    }

    function setOverscroll(amount: number) {
      overscroll = amount;
      if (Math.abs(amount) < 0.5) {
        content.style.transform = "";
        overscroll = 0;
      } else {
        content.style.transform = `translateY(${-amount}px)`;
      }
    }

    function springBack() {
      const from = overscroll;
      if (Math.abs(from) < 0.5) { setOverscroll(0); return; }
      const start = performance.now();
      function frame() {
        const t = Math.min(1, (performance.now() - start) / 500);
        const ease = 1 - Math.pow(1 - t, 3); // cubic ease-out
        setOverscroll(from * (1 - ease));
        showThumb();
        if (t < 1) coastFrame = requestAnimationFrame(frame);
        else { content.style.transform = ""; overscroll = 0; }
      }
      coastFrame = requestAnimationFrame(frame);
    }

    function forceReset() {
      cancelAnimationFrame(coastFrame);
      content.style.transform = "";
      overscroll = 0;
    }

    function onDown(e: MouseEvent) {
      e.stopPropagation();
      if (e.button !== 0) return;
      const tag = (e.target as HTMLElement).tagName;
      if (["BUTTON", "INPUT", "LABEL", "SELECT"].includes(tag)) return;

      forceReset();
      const startY = e.clientY;
      const startScroll = scrollEl.scrollTop;
      let lastY = e.clientY;
      let lastTime = Date.now();
      const vSamples: number[] = [];

      scrollEl.style.cursor = "grabbing";
      showThumb();

      function onMove(ev: MouseEvent) {
        ev.preventDefault();
        ev.stopPropagation();
        const y = ev.clientY;
        const now = Date.now();
        const dt = now - lastTime;
        if (dt > 0) {
          vSamples.push((lastY - y) / dt);
          if (vSamples.length > 5) vSamples.shift();
        }
        lastY = y;
        lastTime = now;

        const desired = startScroll - (y - startY);
        const max = getMaxScroll();

        if (desired < 0) {
          scrollEl.scrollTop = 0;
          setOverscroll(desired * 0.3);
        } else if (desired > max) {
          scrollEl.scrollTop = max;
          setOverscroll((desired - max) * 0.3);
        } else {
          scrollEl.scrollTop = desired;
          if (overscroll !== 0) setOverscroll(0);
        }
        showThumb();
      }

      function onUp(ev: MouseEvent) {
        ev.stopPropagation();
        scrollEl.style.cursor = "";
        window.removeEventListener("mousemove", onMove, true);
        window.removeEventListener("mouseup", onUp, true);

        if (overscroll !== 0) { springBack(); return; }

        // Momentum coast
        const avgV = vSamples.length > 0
          ? vSamples.reduce((a, b) => a + b, 0) / vSamples.length : 0;
        const v0 = Math.max(-4, Math.min(4, avgV));
        if (Math.abs(v0) < 0.005) return;

        const tau = 300;
        const coastStart = performance.now();
        const scrollStart2 = scrollEl.scrollTop;
        const totalDist = v0 * tau;

        function coast() {
          const t = performance.now() - coastStart;
          const decay = Math.exp(-t / tau);
          const offset = totalDist * (1 - decay);
          const target = scrollStart2 + offset;
          const max = getMaxScroll();

          if (target < 0) {
            scrollEl.scrollTop = 0;
            const bounce = Math.min(30, Math.abs(v0 * decay) * 40);
            setOverscroll(-bounce);
            springBack();
            return;
          }
          if (target > max) {
            scrollEl.scrollTop = max;
            const bounce = Math.min(30, Math.abs(v0 * decay) * 40);
            setOverscroll(bounce);
            springBack();
            return;
          }

          scrollEl.scrollTop = target;
          showThumb();
          if (Math.abs(v0 * decay) > 0.0005) {
            coastFrame = requestAnimationFrame(coast);
          }
        }
        coastFrame = requestAnimationFrame(coast);
      }

      window.addEventListener("mousemove", onMove, true);
      window.addEventListener("mouseup", onUp, true);
    }

    function onWheel(e: WheelEvent) {
      e.stopPropagation();
      const { scrollTop, scrollHeight, clientHeight } = scrollEl;
      const atTop = scrollTop <= 0 && e.deltaY < 0;
      const atBottom = scrollTop + clientHeight >= scrollHeight && e.deltaY > 0;
      if (!atTop && !atBottom) {
        e.preventDefault();
        scrollEl.scrollTop += e.deltaY;
        showThumb();
      }
    }

    node.addEventListener("mousedown", onDown);
    node.addEventListener("wheel", onWheel, { passive: false });

    return {
      destroy() {
        node.removeEventListener("mousedown", onDown);
        node.removeEventListener("wheel", onWheel);
        forceReset();
        if (hideTimer) clearTimeout(hideTimer);
        thumb.remove();
      },
    };
  }
</script>

<div class="space-y-3">
  <!-- Add custom activity -->
  <div class="flex gap-2 items-center">
    <div class="flex-1">
      <input
        type="text"
        placeholder="Add custom activity..."
        aria-label="New activity text"
        maxlength={500}
        class="w-full rounded-xl border border-[#161616] bg-black px-3 py-2.5 text-[13px]
               text-white outline-none placeholder:text-[#2a2a2a] focus:border-[#333]"
        bind:value={newActivityText}
        onkeydown={(e) => { if (e.key === "Enter") addCustomActivity(); }}
      />
    </div>
    <!-- Custom category dropdown -->
    <!-- svelte-ignore a11y_no_static_element_interactions -->
    <div class="relative" bind:this={dropdownRef} onkeydown={handleDropdownKeydown} role="presentation">
      <button
        bind:this={dropdownTriggerRef}
        use:pressable
        class="flex items-center gap-1.5 rounded-xl border border-[#161616] bg-black px-3 py-2.5 text-[13px] text-[#8a8a8a]
               hover:border-[#333] hover:text-white transition-colors"
        onclick={() => { dropdownOpen = !dropdownOpen; }}
        aria-haspopup="listbox"
        aria-expanded={dropdownOpen}
        aria-label="Category: {getCategoryLabel(newActivityCategory)}"
      >
        <span>{getCategoryLabel(newActivityCategory)}</span>
        <svg aria-hidden="true" class="w-3 h-3 opacity-50 transition-transform duration-200 {dropdownOpen ? 'rotate-180' : ''}" viewBox="0 0 12 12" fill="none">
          <path d="M3 4.5L6 7.5L9 4.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
        </svg>
      </button>
      {#if dropdownOpen}
        <div class="absolute top-full left-0 mt-1 z-50 min-w-[140px] rounded-xl border border-[#222] bg-[#111] shadow-xl shadow-black/50 overflow-hidden"
             role="listbox" aria-label="Activity category">
          {#each categories as cat, i}
            <button
              class="w-full text-left px-3.5 py-2.5 text-[13px] transition-colors outline-none
                     {newActivityCategory === cat ? 'text-white bg-[#1a1a1a]' : 'text-[#8a8a8a] hover:bg-[#1a1a1a] hover:text-white'}
                     {focusedOptionIndex === i ? 'bg-[#1a1a1a] text-white' : ''}"
              role="option"
              aria-selected={newActivityCategory === cat}
              tabindex={focusedOptionIndex === i ? 0 : -1}
              onclick={() => { newActivityCategory = cat; dropdownOpen = false; dropdownTriggerRef?.focus(); }}
            >
              {getCategoryLabel(cat)}
            </button>
          {/each}
        </div>
      {/if}
    </div>
    <button
      use:pressable
      class="flex items-center justify-center w-10 h-10 rounded-xl border border-[#161616] text-[18px] text-[#8a8a8a]
             hover:border-[#333] hover:text-white transition-colors disabled:opacity-30"
      onclick={addCustomActivity}
      disabled={!newActivityText.trim()}
      aria-label="Add custom activity"
    >
      +
    </button>
  </div>

  <!-- Category buttons -->
  <div class="flex flex-wrap gap-1.5">
    {#each categories as cat}
      {@const total = builtinCount(cat) + customCount(cat)}
      {@const disabled = disabledCount(cat)}
      {@const isExpanded = expandedCategory === cat}
      <button
        use:pressable
        class="rounded-xl px-3 py-2 text-[11px] tracking-wider transition-all duration-200
               {isExpanded
                 ? 'bg-[#1a1a1a] text-white border border-[#333]'
                 : 'bg-[#0a0a0a] text-[#8a8a8a] border border-[#161616] hover:border-[#333] hover:text-white'}"
        onclick={() => toggleCategory(cat)}
        aria-expanded={isExpanded}
        aria-controls="activity-panel-{cat}"
      >
        <span class="uppercase">{getCategoryLabel(cat)}</span>
        <span class="ml-1.5 text-[10px] {isExpanded ? 'text-[#8a8a8a]' : 'text-[#8a8a8a] opacity-60'}">
          {total - disabled}/{total}
        </span>
      </button>
    {/each}
  </div>

  <!-- Animated accordion for activity list -->
  {#each categories as cat}
    {@const isExpanded = expandedCategory === cat}
    {@const catBuiltins = breakActivities.filter((a) => a.category === cat)}
    {@const catCustoms = $config.custom_activities.filter((a) => a.category === cat)}
    <div class="accordion-wrapper" class:accordion-open={isExpanded} id="activity-panel-{cat}" role="region" aria-label="{getCategoryLabel(cat)} activities">
      <div class="accordion-inner">
        <div class="rounded-xl border border-[#161616] bg-[#0a0a0a] overflow-hidden" use:blockParentDrag>
          {#if catCustoms.length > 0}
            <div class="px-3 pt-2.5 pb-1">
              <div class="text-[9px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">Custom</div>
            </div>
          {/if}

          <div class="relative" use:innerDragScroll>
            <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
            <div class="activity-scroll max-h-[200px] overflow-y-auto overflow-x-hidden" tabindex="0" role="group" aria-label="{getCategoryLabel(cat)} activity list">
              <div class="scroll-content">
                {#each catCustoms as activity (activity.id)}
                  <div class="flex items-center gap-1 px-1.5 py-0.5 group hover:bg-[#111]">
                    <button
                      class="flex items-center justify-center min-w-[32px] min-h-[32px] text-[13px] transition-opacity {activity.is_favorite ? 'opacity-100' : 'opacity-30 hover:opacity-60'}"
                      onclick={() => toggleCustomFavorite(activity.id)}
                      aria-label="{activity.is_favorite ? 'Remove from favorites' : 'Add to favorites'}"
                    >
                      ★
                    </button>
                    <span class="flex-1 text-[12px] {activity.enabled ? 'text-white' : 'text-[#8a8a8a] line-through'}">
                      {activity.text}
                    </span>
                    <button
                      class="flex items-center justify-center min-w-[32px] min-h-[32px] text-[#8a8a8a] hover:text-[#f85149] opacity-0 group-hover:opacity-100 focus-visible:opacity-100 transition-all text-[11px]"
                      onclick={() => removeCustomActivity(activity.id)}
                      aria-label="Remove {activity.text}"
                    >
                      ✕
                    </button>
                    <ToggleSwitch
                      checked={activity.enabled}
                      label="Enable {activity.text}"
                      onchange={() => toggleCustomEnabled(activity.id)}
                    />
                  </div>
                {/each}

                {#if catCustoms.length > 0 && catBuiltins.length > 0}
                  <div class="px-3 pt-2 pb-1">
                    <div class="text-[9px] font-medium tracking-[0.15em] text-[#8a8a8a] uppercase">Built-in</div>
                  </div>
                {/if}

                {#each catBuiltins as activity (activity.text)}
                  <div class="flex items-center gap-1 px-1.5 py-0.5 hover:bg-[#111]">
                    <button
                      class="flex items-center justify-center min-w-[32px] min-h-[32px] text-[13px] transition-opacity {isBuiltinFavorite(activity.text) ? 'opacity-100' : 'opacity-30 hover:opacity-60'}"
                      onclick={() => toggleBuiltinFavorite(activity.text)}
                      aria-label="{isBuiltinFavorite(activity.text) ? 'Remove from favorites' : 'Add to favorites'}"
                    >
                      ★
                    </button>
                    <span class="flex-1 text-[12px] {!isBuiltinDisabled(activity.text) ? 'text-[#8a8a8a]' : 'text-[#8a8a8a] line-through opacity-60'}">
                      {activity.text}
                    </span>
                    <ToggleSwitch
                      checked={!isBuiltinDisabled(activity.text)}
                      label="Enable {activity.text}"
                      onchange={() => toggleBuiltinEnabled(activity.text)}
                    />
                  </div>
                {/each}
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  {/each}
</div>

<style>
  /* Accordion: CSS grid row transition for height animation */
  .accordion-wrapper {
    display: grid;
    grid-template-rows: 0fr;
    transition: grid-template-rows 0.3s cubic-bezier(0.22, 0.03, 0.26, 1);
  }
  .accordion-wrapper.accordion-open {
    grid-template-rows: 1fr;
  }
  .accordion-inner {
    overflow: hidden;
    min-height: 0;
  }

  /* Hide native scrollbar completely */
  .activity-scroll {
    overscroll-behavior: contain;
    scrollbar-width: none; /* Firefox */
  }
  .activity-scroll::-webkit-scrollbar {
    display: none; /* Chrome/Edge */
  }
</style>
