<script lang="ts" context="module">
  interface PanelData {
    id: string;
    ref: Readable<HTMLElement | null>;
  }
  export type StateDefinition = {
    // State
    selectedIndex: number | null;
    orientation: "vertical" | "horizontal";
    activation: "auto" | "manual";

    tabs: (HTMLElement | null)[];
    panels: PanelData[];

    // State mutators
    setSelectedIndex(index: number): void;
    registerTab(tab: HTMLElement | null): void;
    unregisterTab(tab: HTMLElement | null): void;
    registerPanel(panel: PanelData): void;
    unregisterPanel(panel: PanelData): void;
  };

  const TABS_CONTEXT_NAME = "headlessui-tabs-context";

  export function useTabsContext(component: string): Readable<StateDefinition> {
    let context: Writable<StateDefinition> | undefined =
      getContext(TABS_CONTEXT_NAME);

    if (context === undefined) {
      throw new Error(
        `<${component} /> is missing a parent <TabGroup /> component.`
      );
    }

    return context;
  }
</script>

<script lang="ts">
  import {
    createEventDispatcher,
    getContext,
    onMount,
    setContext,
  } from "svelte";

  import { Readable, writable, Writable } from "svelte/store";
  import { forwardEventsBuilder } from "$lib/internal/forwardEventsBuilder";
  import { get_current_component } from "svelte/internal";
  import type { SupportedAs } from "$lib/internal/elements";
  import type { HTMLActionArray } from "$lib/hooks/use-actions";
  import Render from "$lib/utils/Render.svelte";
  const forwardEvents = forwardEventsBuilder(get_current_component(), [
    "change",
  ]);

  export let as: SupportedAs = "div";
  export let use: HTMLActionArray = [];
  export let defaultIndex = 0;
  export let vertical = false;
  export let manual = false;

  let selectedIndex: StateDefinition["selectedIndex"] = null;
  let tabs: StateDefinition["tabs"] = [];
  let panels: StateDefinition["panels"] = [];

  const dispatch = createEventDispatcher();

  let api: Writable<StateDefinition> = writable({
    selectedIndex,
    orientation: vertical ? "vertical" : "horizontal",
    activation: manual ? "manual" : "auto",
    tabs,
    panels,
    setSelectedIndex(index: number) {
      if (selectedIndex === index) return;
      selectedIndex = index;
      dispatch("change", index);
    },
    registerTab(tab: typeof tabs[number]) {
      if (!tabs.includes(tab)) tabs = [...tabs, tab];
    },
    unregisterTab(tab: typeof tabs[number]) {
      tabs = tabs.filter((t) => t !== tab);
    },
    registerPanel(panel: typeof panels[number]) {
      if (!panels.includes(panel)) panels = [...panels, panel];
    },
    unregisterPanel(panel: typeof panels[number]) {
      panels = panels.filter((p) => p !== panel);
    },
  });
  setContext(TABS_CONTEXT_NAME, api);

  $: api.update((obj) => {
    return {
      ...obj,
      selectedIndex,
      orientation: vertical ? "vertical" : "horizontal",
      activation: manual ? "manual" : "auto",
      tabs,
      panels,
    };
  });

  onMount(() => {
    if (tabs.length <= 0) return;
    if (selectedIndex !== null) return;

    let mountedTabs = tabs.filter(Boolean) as HTMLElement[];
    let focusableTabs = mountedTabs.filter(
      (tab) => !tab.hasAttribute("disabled")
    );
    if (focusableTabs.length <= 0) return;

    // Underflow
    if (defaultIndex < 0) {
      selectedIndex = mountedTabs.indexOf(focusableTabs[0]);
    }

    // Overflow
    else if (defaultIndex > mountedTabs.length) {
      selectedIndex = mountedTabs.indexOf(
        focusableTabs[focusableTabs.length - 1]
      );
    }

    // Middle
    else {
      let before = mountedTabs.slice(0, defaultIndex);
      let after = mountedTabs.slice(defaultIndex);

      let next = [...after, ...before].find((tab) =>
        focusableTabs.includes(tab)
      );
      if (!next) return;

      selectedIndex = mountedTabs.indexOf(next);
    }
  });

  $: slotProps = { selectedIndex };
</script>

<Render
  {...$$restProps}
  {as}
  {slotProps}
  use={[...use, forwardEvents]}
  name={"TabGroup"}
>
  <slot {...slotProps} />
</Render>
