<script lang="ts">
  import MapCard from '$lib/components/MapCard.svelte';
  import PlayerCompareForm from '$lib/components/PlayerCompareForm.svelte';
  import HasToolAccess from '$lib/components/HasToolAccess.svelte';
  import {
    type MapMeta,
    type StarInfo,
    type BeatLeaderScore,
    type Difficulty,
    type BeatLeaderPlayerProfile,
    loadMetaForHashes,
    loadStarsForHashes,
    normalizeDifficultyName,
    parseTimeset,
    getCutoffEpochFromMonths,
    toPlaylistJson,
    downloadPlaylist,
    fetchAllRecentScoresForDiff,
    TOOL_REQUIREMENTS
  } from '$lib/utils/plebsaber-utils';

  type SongItem = {
    hash: string;
    difficulties: Difficulty[];
    timeset: number;
    leaderboardId?: string;
  };

  export let data: { player: BeatLeaderPlayerProfile | null; adminRank: number | null; adminPlayer: BeatLeaderPlayerProfile | null };

  const requirement = TOOL_REQUIREMENTS['compare-histories'];

  $: playerProfile = data?.player ?? null;
  $: adminRank = data?.adminRank ?? null;
  $: adminPlayer = data?.adminPlayer ?? null;

  let playerA = '';
  let playerB = '';
  let loading = false;
  let errorMsg: string | null = null;
  let results: SongItem[] = [];
  let loadingMeta = false;

  // Sorting and pagination state
  let sortDir: 'asc' | 'desc' = 'desc';
  let page = 1;
  let pageSize: number | string = 24;
  $: pageSizeNum = Number(pageSize) || 24;

  // Configurable lookback windows (months)
  const monthsA = 24; // default 24 months
  const monthsB = 24; // default 24 months

  // Derived lists - always sort by date
  $: sortedResults = [...results].sort((a, b) => {
    const cmp = a.timeset - b.timeset;
    return sortDir === 'asc' ? cmp : -cmp;
  });
  $: totalPages = Math.max(1, Math.ceil(sortedResults.length / pageSizeNum));
  $: page = Math.min(page, totalPages);
  $: pageItems = sortedResults.slice((page - 1) * pageSizeNum, (page - 1) * pageSizeNum + pageSizeNum);

  // Metadata caches
  let metaByHash: Record<string, MapMeta> = {};
  let starsByKey: Record<string, StarInfo> = {};
  let loadingStars = false;

  // Lazy load metadata when pageItems changes
  $: if (pageItems.length > 0) {
    loadPageMetadata(pageItems);
  }

  async function loadPageMetadata(items: SongItem[]): Promise<void> {
    const hashes = items.map(i => i.hash);
    
    // Load BeatSaver metadata
    loadingMeta = true;
    metaByHash = await loadMetaForHashes(hashes, metaByHash);
    loadingMeta = false;
    
    // Load star ratings
    loadingStars = true;
    starsByKey = await loadStarsForHashes(hashes, starsByKey, normalizeDifficultyName);
    loadingStars = false;
  }

  

  function handleDownloadPlaylist(): void {
    const payload = toPlaylistJson(
      results,
      'beatleader_compare_players',
      `A's recent songs not played by B. Generated ${new Date().toISOString()}`
    );
    downloadPlaylist(payload);
  }

  async function onCompare() {
    errorMsg = null;
    results = [];
    const a = playerA.trim();
    const b = playerB.trim();
    if (!a || !b) {
      errorMsg = 'Please enter both Player A and Player B IDs.';
      return;
    }

    loading = true;
    try {
      const cutoff = getCutoffEpochFromMonths(monthsA);
      const cutoffB = getCutoffEpochFromMonths(monthsB);
      const [aScores, bScores] = await Promise.all([
        fetchAllRecentScoresForDiff(a, cutoff, 'ExpertPlus', 15),
        fetchAllRecentScoresForDiff(b, cutoffB, 'ExpertPlus', 100)
      ]);

      const bLeaderboardIds = new Set<string>();
      const bExpertPlusKeys = new Set<string>(); // `${hashLower}|ExpertPlus|${modeName}`
      for (const s of bScores) {
        const rawHash = s.leaderboard?.song?.hash ?? undefined;
        const hashLower = rawHash ? String(rawHash).toLowerCase() : undefined;
        const lbIdRaw = (s.leaderboard as any)?.id ?? (s.leaderboard as any)?.leaderboardId;
        const lbId = lbIdRaw != null ? String(lbIdRaw) : undefined;
        const bDiffValue = s.leaderboard?.difficulty?.value ?? undefined;
        const bModeName = s.leaderboard?.difficulty?.modeName ?? 'Standard';
        const bDiffName = normalizeDifficultyName(bDiffValue);
        if (bDiffName !== 'ExpertPlus') continue; // ignore non-ExpertPlus for B
        if (lbId) bLeaderboardIds.add(lbId);
        if (hashLower) bExpertPlusKeys.add(`${hashLower}|ExpertPlus|${bModeName}`);
      }

      
      const runSeen = new Set<string>(); // avoid duplicates within this run

      const candidates: SongItem[] = [];
      for (const entry of aScores) {
        const t = parseTimeset(entry.timeset);
        if (!t || t < cutoff) continue;

        const rawHash = entry.leaderboard?.song?.hash ?? undefined;
        const diffValue = entry.leaderboard?.difficulty?.value ?? undefined;
        const modeName = entry.leaderboard?.difficulty?.modeName ?? 'Standard';
        const leaderboardIdRaw = (entry.leaderboard as any)?.id ?? (entry.leaderboard as any)?.leaderboardId;
        const leaderboardId = leaderboardIdRaw != null ? String(leaderboardIdRaw) : undefined;
        if (!rawHash) continue;
        const hashLower = String(rawHash).toLowerCase();
        const diffName = normalizeDifficultyName(diffValue);
        if (diffName !== 'ExpertPlus') continue; // Only compare ExpertPlus for A
        // B has played ExpertPlus of same map+mode if matching leaderboard or key exists
        if ((leaderboardId && bLeaderboardIds.has(leaderboardId)) || bExpertPlusKeys.has(`${hashLower}|${diffName}|${modeName}`)) continue;
        

        const key = `${rawHash}|${diffName}|${modeName}`;
        if (runSeen.has(key)) continue;
        runSeen.add(key);

        candidates.push({
          hash: rawHash,
          difficulties: [{ name: diffName, characteristic: modeName ?? 'Standard' }],
          timeset: t,
          leaderboardId
        });
      }

      candidates.sort((x, y) => y.timeset - x.timeset);
      const limited = candidates; // return all; pagination handled client-side

      results = limited;
      page = 1;
      // Metadata will be loaded lazily via reactive statement when pageItems changes
    } catch (err) {
      errorMsg = err instanceof Error ? err.message : 'Unknown error';
    } finally {
      loading = false;
    }
  }

</script>

<section class="py-8">
  <h1 class="font-display text-3xl sm:text-4xl">Compare Play Histories</h1>
  <p class="mt-2 text-muted max-w-2xl">
    Maps Player A has played that Player B has not.
    This tool can be useful for finding songs to <code>!bsr</code> in Twitch streams, so we can recommend songs that we've played that the streamer has not.
  </p>
 
  <HasToolAccess player={playerProfile} requirement={requirement} {adminRank} adminPlayer={adminPlayer}>
    <PlayerCompareForm bind:playerA bind:playerB {loading} hasResults={results.length > 0} oncompare={onCompare} currentPlayer={playerProfile}>
      <svelte:fragment slot="extra-buttons">
        {#if results.length > 0}
          <button type="button" class="rounded-md border border-white/10 px-3 py-2 text-sm" on:click={handleDownloadPlaylist}>Download .bplist</button>
        {/if}
      </svelte:fragment>
    </PlayerCompareForm>

    {#if errorMsg}
      <div class="mt-4 text-danger">{errorMsg}</div>
    {/if}

    {#if results.length > 0}
      <div class="mt-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
        <div class="text-sm text-muted">
          {results.length} songs
        </div>
        <div class="flex items-center gap-4 text-sm text-muted flex-wrap justify-end">
          <label class="flex items-center gap-2">
            <select class="neon-select" bind:value={sortDir}>
              <option value="desc">Newest First</option>
              <option value="asc">Oldest First</option>
            </select>
          </label>
          <label class="flex items-center gap-2">Page size
            <select class="neon-select" bind:value={pageSize}>
              <option value={12}>12</option>
              <option value={24}>24</option>
              <option value={36}>36</option>
              <option value={48}>48</option>
            </select>
          </label>
        </div>
      </div>

      {#if loadingMeta}
        <div class="mt-2 text-xs text-muted">Loading covers…</div>
      {/if}
      {#if loadingStars}
        <div class="mt-2 text-xs text-muted">Loading star ratings…</div>
      {/if}

      <div class="mt-4 grid gap-4 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4">
        {#each pageItems as item}
          <article class="card-surface overflow-hidden">
            <MapCard
              hash={item.hash}
              coverURL={metaByHash[item.hash]?.coverURL}
              songName={metaByHash[item.hash]?.songName}
              mapper={metaByHash[item.hash]?.mapper}
              stars={starsByKey[`${item.hash}|${item.difficulties[0]?.name ?? 'ExpertPlus'}|${item.difficulties[0]?.characteristic ?? 'Standard'}`]?.stars}
              timeset={item.timeset}
              diffName={item.difficulties[0]?.name ?? 'ExpertPlus'}
              modeName={item.difficulties[0]?.characteristic ?? 'Standard'}
              leaderboardId={item.leaderboardId}
              beatsaverKey={metaByHash[item.hash]?.key}
              score={metaByHash[item.hash]?.score}
            />
          </article>
        {/each}
      </div>

      {#if totalPages > 1}
        <div class="mt-6 flex items-center justify-center gap-2">
          <button class="rounded-md border border-white/10 px-3 py-1 text-sm disabled:opacity-50" on:click={() => (page = Math.max(1, page - 1))} disabled={page === 1}>
            Prev
          </button>
          <span class="text-sm text-muted">Page {page} / {totalPages}</span>
          <button class="rounded-md border border-white/10 px-3 py-1 text-sm disabled:opacity-50" on:click={() => (page = Math.min(totalPages, page + 1))} disabled={page === totalPages}>
            Next
          </button>
        </div>
      {/if}
    {/if}
  </HasToolAccess>
</section>

<style>
  .text-danger { color: #dc2626; }

  /* Neon select dropdown */
  .neon-select {
    border-radius: 0.375rem;
    border: 1px solid rgba(34, 211, 238, 0.3);
    background: linear-gradient(180deg, rgba(15,23,42,0.9), rgba(11,15,23,0.95));
    padding: 0.25rem 0.5rem;
    font-size: 0.875rem;
    color: rgba(148, 163, 184, 1);
    transition: all 0.2s ease;
    box-shadow: 0 0 8px rgba(34, 211, 238, 0.15);
    cursor: pointer;
  }
  .neon-select:hover {
    border-color: rgba(34, 211, 238, 0.5);
    color: rgba(255, 255, 255, 0.9);
    box-shadow: 0 0 16px rgba(34, 211, 238, 0.25);
  }
  .neon-select:focus {
    outline: none;
    border-color: rgba(34, 211, 238, 0.7);
    box-shadow: 0 0 20px rgba(34, 211, 238, 0.35), 0 0 0 2px rgba(34, 211, 238, 0.1);
  }
  .neon-select option {
    background: #0f172a;
    color: #fff;
  }

</style>


