<script>
  import { onMount, onDestroy } from 'svelte';

  const spinnerFrames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];

  const scenarios = [
    {
      command: 'hf download openai/gpt-oss-20b',
      phase1: {
        spinning: 'Preparing download...',
        children: [{ text: 'Resolved main @ a1b2c3d', isLast: true }],
      },
      phase2: {
        ready: 'Downloaded 14 files (13.5 GB)',
        children: [
          { text: 'config.json', isLast: false },
          { text: 'tokenizer.json', isLast: false },
          { text: 'model.safetensors', isLast: false },
          {
            text: 'Cached to ~/.cache/huggingface/hub',
            isLast: true,
          },
        ],
      },
    },
    {
      command: 'hf upload username/mini-gpt .',
      phase1: {
        spinning: 'Preparing commit...',
        children: [
          { text: '8 files hashed', isLast: false },
          { text: 'Repo created on the Hub', isLast: true },
        ],
      },
      phase2: {
        ready: 'Uploaded to username/mini-gpt',
        children: [
          { text: 'README.md', isLast: false },
          { text: 'model.safetensors (142 MB)', isLast: false },
          { text: 'Commit c3f4d1e pushed to main', isLast: true },
        ],
      },
    },
    {
      command:
        'hf jobs run --flavor a10g-small python:3.12 python infer.py',
      phase1: {
        spinning: 'Provisioning a10g-small...',
        children: [
          { text: 'GPU allocated', isLast: false },
          { text: 'Image pulled', isLast: true },
        ],
      },
      phase2: {
        ready: 'Job complete',
        children: [
          { text: 'Loaded weights in 4.2s', isLast: false },
          { text: 'Generated 128 tokens', isLast: false },
          { text: 'Logs at ~/.hf/jobs/job_9f2k3.log', isLast: true },
        ],
      },
    },
    {
      command:
        'hf repos create username/diffuser-demo --type space --space-sdk gradio',
      phase1: {
        spinning: 'Creating Space...',
        children: [
          { text: 'Initialized Gradio template', isLast: false },
          { text: 'CI build triggered', isLast: true },
        ],
      },
      phase2: {
        ready: 'Space live',
        children: [
          {
            text: 'https://huggingface.co/spaces/username/diffuser-demo',
            isLast: true,
          },
        ],
      },
    },
  ];

  let spinnerFrame = $state(0);
  let lines = $state([]);
  let running = true;

  const wait = (ms) => new Promise((r) => setTimeout(r, ms));

  async function typeInto(idx, text, speed = 28) {
    for (let i = 1; i <= text.length; i++) {
      if (!running) return;
      lines[idx].typed = text.slice(0, i);
      await wait(speed);
    }
  }

  function pushCommand(text) {
    lines.push({ kind: 'command', text, typed: '', showCaret: true });
    return lines.length - 1;
  }

  function pushPhase(variant, text) {
    lines.push({ kind: 'phase', variant, text });
    return lines.length - 1;
  }

  function pushChild(text, isLast) {
    lines.push({ kind: 'child', text, isLast });
  }

  async function playScenario(scenario) {
    lines = [];
    await wait(500);
    if (!running) return;

    const c = pushCommand(scenario.command);
    await typeInto(c, scenario.command);
    lines[c].showCaret = false;
    await wait(400);

    const p1 = pushPhase('spinning', scenario.phase1.spinning);
    await wait(750);
    for (const child of scenario.phase1.children) {
      pushChild(child.text, child.isLast);
      await wait(500);
    }
    lines[p1].variant = 'done-hollow';
    await wait(300);

    pushPhase('ready', scenario.phase2.ready);
    await wait(460);
    for (const child of scenario.phase2.children) {
      pushChild(child.text, child.isLast);
      await wait(460);
    }

    await wait(3200);
  }

  async function run() {
    while (running) {
      for (const scenario of scenarios) {
        if (!running) return;
        await playScenario(scenario);
      }
    }
  }

  let spinnerInterval;

  onMount(() => {
    spinnerInterval = setInterval(() => {
      spinnerFrame = (spinnerFrame + 1) % spinnerFrames.length;
    }, 90);
    run();
  });

  onDestroy(() => {
    running = false;
    clearInterval(spinnerInterval);
  });

  function splitChildText(text) {
    const pattern = /(https?:\/\/\S+|~\/[^\s]+)/g;
    const parts = [];
    let last = 0;
    let m;
    while ((m = pattern.exec(text)) !== null) {
      if (m.index > last)
        parts.push({ text: text.slice(last, m.index), accent: false });
      parts.push({ text: m[0], accent: true });
      last = m.index + m[0].length;
    }
    if (last < text.length)
      parts.push({ text: text.slice(last), accent: false });
    return parts;
  }
</script>

<div
  class="frame-bg frame-shadow w-[760px] max-w-[calc(100vw-48px)] rounded-[20px] p-[3px]"
>
  <div
    class="w-full min-h-[540px] bg-[#fbfbf9] rounded-[17px] overflow-hidden font-mono text-[18px] leading-[1.85] text-[#232323]"
  >
    <div class="flex gap-2 pt-4 px-[18px] pb-[6px]">
      <span class="w-3 h-3 rounded-full bg-[#d7d7d3]"></span>
      <span class="w-3 h-3 rounded-full bg-[#d7d7d3]"></span>
      <span class="w-3 h-3 rounded-full bg-[#d7d7d3]"></span>
    </div>

    <div class="pt-[18px] px-7 pb-7 min-h-[460px]" aria-live="polite">
      {#each lines as line, i (i)}
        <div
          class="flex items-baseline gap-2.5 whitespace-pre-wrap break-words animate-fade-in {line.kind ===
          'child'
            ? 'pl-[2.2ch]'
            : ''}"
        >
          {#if line.kind === 'command'}
            <span class="inline-block w-[1ch] text-[#6a6a66]">›</span>
            <span class="text-[#222220]"
              >{line.typed}{#if line.showCaret}<span
                  class="inline-block w-[0.55ch] ml-px text-[#222220] animate-blink"
                  aria-hidden="true">▎</span
                >{/if}</span
            >
          {:else if line.kind === 'phase'}
            {#if line.variant === 'spinning'}
              <span class="inline-block w-[1ch] text-center text-[#5f5f5c]"
                >{spinnerFrames[spinnerFrame]}</span
              >
              <span class="text-[#333331]">{line.text}</span>
            {:else if line.variant === 'done-hollow'}
              <span class="inline-block w-[1ch] text-center text-[#6b6b68]"
                >○</span
              >
              <span class="text-[#333331]">{line.text}</span>
            {:else if line.variant === 'ready'}
              <span
                class="inline-block w-[1ch] text-center text-[#0f7a3a] animate-ready-pulse"
                >●</span
              >
              <span class="text-[#0f5a2a] font-semibold">{line.text}</span>
            {/if}
          {:else if line.kind === 'child'}
            <span class="inline-block w-[1ch] text-[#b3b3ad]"
              >{line.isLast ? '└' : '├'}</span
            >
            <span class="inline-block w-[1ch] text-[#17a34a]">✓</span>
            <span class="text-[#474744]"
              >{#each splitChildText(line.text) as part}{#if part.accent}<span
                    class="text-[#8b5cf6]">{part.text}</span
                  >{:else}{part.text}{/if}{/each}</span
            >
          {/if}
        </div>
      {/each}
    </div>
  </div>
</div>
