<script lang="ts">
  import { fade } from 'svelte/transition';
  import type { Choice } from '../types.js';

  interface Props {
    text: string;
    avatarX: number;
    avatarY: number;
    choices?: Choice[];
    showNext?: boolean;
    stepIndex?: number;
    totalSteps?: number;
    onNext?: () => void;
    onClose?: () => void;
    onChoose?: (choice: Choice) => void;
  }

  let {
    text,
    avatarX,
    avatarY,
    choices = [],
    showNext = false,
    stepIndex = -1,
    totalSteps = 0,
    onNext,
    onClose,
    onChoose
  }: Props = $props();

  const showProgress = $derived(stepIndex >= 0 && totalSteps > 0);

  const BUBBLE_WIDTH = 268;
  const AVATAR_SIZE = 80;
  const GAP = 10;

  const isAvatarNearTop = $derived(avatarY < BUBBLE_WIDTH + GAP + 8);

  const left = $derived(Math.max(8, Math.min(avatarX, window.innerWidth - BUBBLE_WIDTH - 8)));
  const bottom = $derived(isAvatarNearTop ? null : `${window.innerHeight - avatarY + GAP}px`);
  const top = $derived(isAvatarNearTop ? `${avatarY + AVATAR_SIZE + GAP}px` : null);

  // Typewriter
  let displayed = $state('');
  const finished = $derived(displayed.length === text.length);
  let typeTimer: ReturnType<typeof setTimeout> | null = null;

  function renderMarkdown(raw: string): string {
    return raw
      .replaceAll(/^# (.+)$/gm, '<strong class="block text-sm font-bold mb-1">$1</strong>')
      .replaceAll(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
      .replaceAll(/\*(.+?)\*/g, '<em>$1</em>')
      .replaceAll(
        /`(.+?)`/g,
        '<code class="text-[11px] rounded px-1 font-mono" style="background: var(--color-layer-3); color: var(--color-text);">$1</code>'
      )
      .replaceAll(/\*/g, '')
      .replaceAll(/_/g, '')
      .replaceAll(/\n+/g, '<br>');
  }

  $effect(() => {
    // Track only `text` as a dependency.
    // Never read `displayed` inside the effect — += would add it as a dep
    // and cause an infinite loop. Use slice(0, i) for pure writes instead.
    const target = text;

    displayed = '';
    if (typeTimer) clearTimeout(typeTimer);

    let i = 0;
    function tick() {
      if (i < target.length) {
        displayed = target.slice(0, ++i);
        typeTimer = setTimeout(tick, 26);
      }
    }
    // Defer first tick so no reads happen during the synchronous effect body
    typeTimer = setTimeout(tick, 0);

    return () => {
      if (typeTimer) clearTimeout(typeTimer);
    };
  });
</script>

<div
  class="pointer-events-auto fixed z-99999 rounded-md border p-2"
  style:width="{BUBBLE_WIDTH}px"
  style:left="{left}px"
  style:bottom
  style:top
  style:background="var(--color-layer-0)"
  style:border-color="var(--color-outline)"
>
  {#if isAvatarNearTop}
    <!-- Tail pointing up toward avatar -->
    <div
      class="absolute -top-2 h-3.5 w-3.5 rotate-45 border-t border-l"
      style:left="{Math.min(
				Math.max(avatarX - left + AVATAR_SIZE / 2 - 25, 12),
				BUBBLE_WIDTH - 28
			)}px"
      style:background="var(--color-layer-0)"
      style:border-color="var(--color-outline)"
    >
    </div>
  {:else}
    <!-- Tail pointing down toward avatar -->
    <div
      class="absolute -bottom-2 h-3.5 w-3.5 rotate-45 border-r border-b"
      style:left="{Math.min(
				Math.max(avatarX - left + AVATAR_SIZE / 2 - 25, 12),
				BUBBLE_WIDTH - 28
			)}px"
      style:background="var(--color-layer-0)"
      style:border-color="var(--color-outline)"
    >
    </div>
  {/if}

  <div class="mb-2 min-h-[1.4em] text-sm leading-relaxed" style="color: var(--color-text)">
    <!-- eslint-disable-next-line svelte/no-at-html-tags -->
    {@html renderMarkdown(displayed)}
  </div>

  {#if choices.length > 0}
    <div class="flex flex-col gap-1.5">
      {#each choices as choice, i (choice.label)}
        {#if finished}
          <button
            in:fade={{ duration: 200, delay: i * 250 }}
            class="cursor-pointer rounded-lg px-3 py-1.5 text-left text-sm font-medium transition-colors"
            style:background="var(--color-layer-1)"
            style:border-color="var(--color-outline)"
            style:color="var(--color-text)"
            onclick={() => onChoose?.(choice)}
          >
            {choice.label}
          </button>
        {/if}
      {/each}
    </div>
  {/if}

  <div class="mt-2 flex items-center justify-between gap-2">
    <button
      class="cursor-pointer text-xs transition-colors"
      style="color: var(--color-outline)"
      onclick={onClose}
    >
      ✕ close
    </button>
    <div class="flex items-center gap-2">
      {#if showProgress}
        <span class="text-xs tabular-nums" style="color: var(--color-outline)">
          {stepIndex + 1} / {totalSteps}
        </span>
      {/if}
      {#if showNext && finished}
        <button
          class="cursor-pointer rounded-lg px-3 py-1 text-xs font-semibold transition-colors"
          style:background="var(--color-outline)"
          style:color="var(--color-layer-0)"
          onclick={onNext}
        >
          Next →
        </button>
      {/if}
    </div>
  </div>
</div>
