<script lang="ts">
  import { createEventDispatcher, tick } from 'svelte';
  import { noteTitle, noteBody, highlightRegex } from '../search';
  import type { Note } from '../types';

  export let filtered: Note[] = [];
  export let selectedId: string | null = null;
  export let query = '';

  // DOM cap: filtering is in-memory over everything, but rendering thousands of
  // rows would blow the <10 ms keystroke budget. Selection stays within the cap.
  const MAX_ROWS = 100;

  const dispatch = createEventDispatcher<{ open: string }>();

  let listEl: HTMLElement;

  $: visible = filtered.length > MAX_ROWS ? filtered.slice(0, MAX_ROWS) : filtered;
  $: rx = highlightRegex(query);

  $: if (selectedId) {
    void tick().then(() => {
      listEl?.querySelector('.row.selected')?.scrollIntoView({ block: 'nearest' });
    });
  }

  function esc(s: string): string {
    return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }

  function hl(s: string): string {
    if (!rx) return esc(s);
    rx.lastIndex = 0;
    let out = '';
    let last = 0;
    let m: RegExpExecArray | null;
    while ((m = rx.exec(s))) {
      out += esc(s.slice(last, m.index)) + '<mark>' + esc(m[0]) + '</mark>';
      last = m.index + m[0].length;
      if (m.index === rx.lastIndex) rx.lastIndex++;
    }
    return out + esc(s.slice(last));
  }

  function when(ms: number): string {
    const d = new Date(ms);
    const now = new Date();
    if (d.toDateString() === now.toDateString())
      return d.toLocaleTimeString(undefined, { hour: '2-digit', minute: '2-digit' });
    if (d.getFullYear() === now.getFullYear())
      return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
    return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
  }
</script>

<div class="list" bind:this={listEl} role="listbox" aria-label="Notes">
  {#each visible as n (n.id)}
    <div
      class="row"
      class:selected={n.id === selectedId}
      role="option"
      aria-selected={n.id === selectedId}
      tabindex="-1"
      on:click={() => dispatch('open', n.id)}
      on:keydown={(e) => e.key === 'Enter' && dispatch('open', n.id)}
    >
      <span class="title">{@html hl(noteTitle(n.content))}</span>
      <span class="snippet">{@html hl(noteBody(n.content).slice(0, 120))}</span>
      <span class="meta">
        {#each n.tags as t}<span class="tag">{t}</span>{/each}
        <span class="date">{when(n.modified_at)}</span>
      </span>
    </div>
  {:else}
    <div class="none">{query.trim() ? 'No matches — Enter creates this note' : 'No notes yet'}</div>
  {/each}
  {#if filtered.length > MAX_ROWS}
    <div class="none">…and {filtered.length - MAX_ROWS} more — keep typing to narrow down</div>
  {/if}
</div>

<style>
  .list {
    min-height: 100%;
  }
  .row {
    display: flex;
    align-items: baseline;
    gap: 10px;
    padding: 6px 12px;
    line-height: 1.7;
    border-bottom: 1px solid var(--sep);
    cursor: default;
    white-space: nowrap;
    overflow: hidden;
  }
  .row.selected {
    background: var(--sel-bg);
  }
  .title {
    font-weight: 600;
    flex-shrink: 0;
    max-width: 55%;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .snippet {
    flex: 1;
    color: var(--fg-dim);
    font-size: 12.5px;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .meta {
    margin-left: auto;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    gap: 6px;
  }
  .tag {
    background: var(--accent-bg);
    color: var(--accent);
    font-size: 11px;
    padding: 1px 7px;
    border-radius: 9px;
  }
  .date {
    color: var(--fg-dim);
    font-size: 12px;
    font-variant-numeric: tabular-nums;
  }
  .none {
    padding: 16px;
    color: var(--fg-dim);
    text-align: center;
  }
</style>
