/**
 * Trail Lifecycle Tests
 *
 * Scenarios covering trails from creation through deletion,
 * including how they appear to users and cascade effects.
 */

import { describe, it, expect } from "vitest";
import * as queries from "../queries";
import {
  emit,
  emitDelete,
  ALICE,
  BOB,
  CAROL,
  DAVE,
  EVE,
  FRANK,
  GRACE,
  setCurrentUser,
  generateTid,
} from "./helpers";

const now = () => new Date().toISOString();

describe("Publishing a trail", () => {
  it("new trail with activity appears in the home feed and creator's profile", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "My First Trail",
      description: "A beginner's guide",
      stops: [
        { tid: stopTid, title: "Start Here", content: "Welcome!" },
        { tid: generateTid(), title: "Next Step", content: "Keep going!" },
      ],
      accentColor: "#ff5500",
      backgroundColor: "#fff8f0",
      createdAt: now(),
    });

    // Add non-author activity so it appears in home feed
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    // Appears in home feed (requires non-author activity)
    const trails = await queries.loadTrails();
    expect(trails).toHaveLength(1);
    expect(trails[0].title).toBe("My First Trail");
    expect(trails[0].creator.handle).toBe(ALICE.handle);
    expect(trails[0].stopsCount).toBe(2);

    // Appears on creator's profile (doesn't require activity)
    const publishedTrails = await queries.loadUserPublishedTrails(ALICE.handle);
    expect(publishedTrails).toHaveLength(1);
    expect(publishedTrails[0].title).toBe("My First Trail");

    // Detail page works
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.header.title).toBe("My First Trail");
    expect(detail.stops).toHaveLength(2);
  });

  it("trail without activity appears in home feed as fallback", async () => {
    // Create a trail with no activity
    await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Lonely Trail",
      description: "No one has walked this",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#eee",
      createdAt: now(),
    });

    // Appears in home feed even without activity
    const trails = await queries.loadTrails();
    expect(trails).toHaveLength(1);
    expect(trails[0].title).toBe("Lonely Trail");

    // Also appears on creator's profile
    const publishedTrails = await queries.loadUserPublishedTrails(ALICE.handle);
    expect(publishedTrails).toHaveLength(1);
  });
});

describe("Discovering trails with active walkers", () => {
  it("trail cards show up to 3 most recent walkers", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Popular Trail",
      description: "Many people walking",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ff0000",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

    // Four people walk - oldest first
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-01T10:00:00Z",
      updatedAt: "2024-01-01T10:00:00Z",
    });

    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-02T10:00:00Z",
      updatedAt: "2024-01-02T10:00:00Z",
    });

    await emit("app.sidetrail.walk", DAVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-03T10:00:00Z",
      updatedAt: "2024-01-03T10:00:00Z",
    });

    await emit("app.sidetrail.walk", EVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-04T10:00:00Z",
      updatedAt: "2024-01-04T10:00:00Z",
    });

    await emit("app.sidetrail.walk", FRANK.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-05T10:00:00Z",
      updatedAt: "2024-01-05T10:00:00Z",
    });

    await emit("app.sidetrail.walk", GRACE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-06T10:00:00Z",
      updatedAt: "2024-01-06T10:00:00Z",
    });

    const trails = await queries.loadTrails();
    const activeWalkers = await queries.loadTrailActiveWalkers(trails[0].uri);

    // Should show exactly 5, most recent first (limit is 5)
    expect(activeWalkers).toHaveLength(5);
    // Bob (oldest) should be excluded
    const handles = activeWalkers.map((w: { handle: string }) => w.handle);
    expect(handles).not.toContain(BOB.handle);
  });

  it("active walkers shows most recent activity first", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Activity Order Trail",
      description: "Testing order",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#00ff00",
      backgroundColor: "#f0fff0",
      createdAt: now(),
    });

    // Bob walks first, Carol walks later
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-01-01T10:00:00Z",
      updatedAt: "2024-01-01T10:00:00Z",
    });

    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: "2024-06-01T10:00:00Z",
      updatedAt: "2024-06-01T10:00:00Z",
    });

    const trails = await queries.loadTrails();
    const activeWalkers = await queries.loadTrailActiveWalkers(trails[0].uri);

    expect(activeWalkers[0].handle).toBe(CAROL.handle);
  });
});

describe("Viewing a trail's detail page", () => {
  it("shows walkers at their current stops", async () => {
    const stop1 = generateTid();
    const stop2 = generateTid();
    const stop3 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Multi-stop Trail",
      description: "See where everyone is",
      stops: [
        { tid: stop1, title: "Beginning", content: "Start here" },
        { tid: stop2, title: "Middle", content: "Keep going" },
        { tid: stop3, title: "End", content: "Almost there" },
      ],
      accentColor: "#0000ff",
      backgroundColor: "#f0f0ff",
      createdAt: now(),
    });

    // Bob at stop 1, Carol at stop 2, Dave finished (at stop 3)
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1],
      createdAt: now(),
      updatedAt: now(),
    });

    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1, stop2],
      createdAt: now(),
      updatedAt: now(),
    });

    await emit("app.sidetrail.walk", DAVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1, stop2, stop3],
      createdAt: now(),
      updatedAt: now(),
    });

    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);

    const atStop1 = await detail.stops[0].walkersHere;
    const atStop2 = await detail.stops[1].walkersHere;
    const atStop3 = await detail.stops[2].walkersHere;

    expect(atStop1.map((w) => w.handle)).toEqual([BOB.handle]);
    expect(atStop2.map((w) => w.handle)).toEqual([CAROL.handle]);
    expect(atStop3.map((w) => w.handle)).toEqual([DAVE.handle]);
  });

  it("shows all walkers including those who completed", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Walkers and Completers",
      description: "Mixed activity",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ff00ff",
      backgroundColor: "#fff0ff",
      createdAt: now(),
    });

    // Bob is actively walking
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    // Carol completed the trail
    await emit("app.sidetrail.completion", CAROL.did, {
      $type: "app.sidetrail.completion",
      trail: { uri: trail.uri, cid: trail.cid },
      createdAt: now(),
    });

    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    const walkers = await detail.walkers;
    const completions = await detail.completions;

    // Both appear in walkers (activity feed)
    expect(walkers).toHaveLength(2);
    const walkerHandles = walkers.map((w) => w.user.handle);
    expect(walkerHandles).toContain(BOB.handle);
    expect(walkerHandles).toContain(CAROL.handle);

    // Only Carol in completions
    expect(completions).toHaveLength(1);
    expect(completions[0].user.handle).toBe(CAROL.handle);
  });

  it("marks current user in walkers list", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "IsYou Test",
      description: "Testing current user marker",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#123456",
      backgroundColor: "#654321",
      createdAt: now(),
    });

    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    setCurrentUser(BOB);
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    const walkers = await detail.walkers;

    const bob = walkers.find((w) => w.user.handle === BOB.handle);
    const carol = walkers.find((w) => w.user.handle === CAROL.handle);

    expect(bob?.isYou).toBe(true);
    expect(carol?.isYou).toBe(false);
  });
});

describe("Deleting a trail", () => {
  it("trail disappears from home feed and author's profile", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Ephemeral Trail",
      description: "Will be deleted",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#aaa",
      backgroundColor: "#bbb",
      createdAt: now(),
    });

    // Add non-author activity so it appears in home feed
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    let trails = await queries.loadTrails();
    expect(trails).toHaveLength(1);

    await emitDelete("app.sidetrail.trail", trail.uri);

    trails = await queries.loadTrails();
    expect(trails).toHaveLength(0);

    const publishedTrails = await queries.loadUserPublishedTrails(ALICE.handle);
    expect(publishedTrails).toHaveLength(0);
  });

  it("detail page throws 'Trail not found' after deletion", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Soon Gone",
      description: "Will 404",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ccc",
      backgroundColor: "#ddd",
      createdAt: now(),
    });

    await emitDelete("app.sidetrail.trail", trail.uri);

    await expect(queries.loadTrailDetail(ALICE.handle, trail.rkey)).rejects.toThrow(
      "Trail not found",
    );
  });

  it("walkers' in-progress walks disappear when trail is deleted", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Disappearing Trail",
      description: "Walkers will be orphaned",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#222",
      createdAt: now(),
    });

    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    setCurrentUser(BOB);
    let walks = await queries.loadWalks();
    expect(walks).toHaveLength(1);

    // Trail author deletes the trail
    await emitDelete("app.sidetrail.trail", trail.uri);

    // Bob's walk is orphaned - doesn't appear in his walks
    walks = await queries.loadWalks();
    expect(walks).toHaveLength(0);
  });

  it("completions disappear from profiles when trail is deleted", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Completed Then Deleted",
      description: "Completion will be orphaned",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#333",
      backgroundColor: "#444",
      createdAt: now(),
    });

    await emit("app.sidetrail.completion", BOB.did, {
      $type: "app.sidetrail.completion",
      trail: { uri: trail.uri, cid: trail.cid },
      createdAt: now(),
    });

    let completedTrails = await queries.loadUserCompletedTrails(BOB.handle);
    expect(completedTrails).toHaveLength(1);

    await emitDelete("app.sidetrail.trail", trail.uri);

    completedTrails = await queries.loadUserCompletedTrails(BOB.handle);
    expect(completedTrails).toHaveLength(0);
  });

  it("walking badges disappear when trail is deleted", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Badge Trail",
      description: "Badge will disappear",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#abcdef",
      backgroundColor: "#fedcba",
      createdAt: now(),
    });

    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [],
      createdAt: now(),
      updatedAt: now(),
    });

    setCurrentUser(BOB);
    let badges = await queries.loadWalkingBadges();
    expect(badges).toHaveLength(1);

    await emitDelete("app.sidetrail.trail", trail.uri);

    badges = await queries.loadWalkingBadges();
    expect(badges).toHaveLength(0);
  });
});

describe("Hotness ranking", () => {
  it("trail with more recent activity ranks higher", async () => {
    const stopTidA = generateTid();
    const stopTidB = generateTid();

    // Two trails
    const trailA = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Trail A - Old Activity",
      description: "Walked a while ago",
      stops: [
        { tid: stopTidA, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#eee",
      createdAt: now(),
    });

    const trailB = await emit("app.sidetrail.trail", BOB.did, {
      $type: "app.sidetrail.trail",
      title: "Trail B - Recent Activity",
      description: "Just walked",
      stops: [
        { tid: stopTidB, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#222",
      backgroundColor: "#ddd",
      createdAt: now(),
    });

    // Trail A has older activity (7 days ago)
    const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailA.uri, cid: trailA.cid },
      visitedStops: [stopTidA],
      createdAt: sevenDaysAgo,
      updatedAt: sevenDaysAgo,
    });

    // Trail B has recent activity (now)
    await emit("app.sidetrail.walk", DAVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailB.uri, cid: trailB.cid },
      visitedStops: [stopTidB],
      createdAt: now(),
      updatedAt: now(),
    });

    const trails = await queries.loadTrails();

    // Trail B with more recent activity ranks higher
    expect(trails[0].title).toBe("Trail B - Recent Activity");
    expect(trails[1].title).toBe("Trail A - Old Activity");
  });

  it("each unique walker contributes to hotness", async () => {
    const stopTidA = generateTid();
    const stopTidB = generateTid();

    // Two trails
    const trailA = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Trail A - One Walker",
      description: "One person walking",
      stops: [
        { tid: stopTidA, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#eee",
      createdAt: now(),
    });

    const trailB = await emit("app.sidetrail.trail", BOB.did, {
      $type: "app.sidetrail.trail",
      title: "Trail B - Three Walkers",
      description: "Three people walking",
      stops: [
        { tid: stopTidB, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#222",
      backgroundColor: "#ddd",
      createdAt: now(),
    });

    // One walker on Trail A
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailA.uri, cid: trailA.cid },
      visitedStops: [stopTidA],
      createdAt: now(),
      updatedAt: now(),
    });

    // Three walkers on Trail B
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailB.uri, cid: trailB.cid },
      visitedStops: [stopTidB],
      createdAt: now(),
      updatedAt: now(),
    });

    await emit("app.sidetrail.walk", DAVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailB.uri, cid: trailB.cid },
      visitedStops: [stopTidB],
      createdAt: now(),
      updatedAt: now(),
    });

    await emit("app.sidetrail.walk", EVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailB.uri, cid: trailB.cid },
      visitedStops: [stopTidB],
      createdAt: now(),
      updatedAt: now(),
    });

    const trails = await queries.loadTrails();

    // Trail B with more walkers should rank higher
    expect(trails[0].title).toBe("Trail B - Three Walkers");
    expect(trails[1].title).toBe("Trail A - One Walker");
  });

  it("completions also contribute to hotness", async () => {
    const stopTidA = generateTid();
    const stopTidB = generateTid();

    // Two trails
    const trailA = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Trail A - Only Walk",
      description: "Someone is walking",
      stops: [
        { tid: stopTidA, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#eee",
      createdAt: now(),
    });

    const trailB = await emit("app.sidetrail.trail", BOB.did, {
      $type: "app.sidetrail.trail",
      title: "Trail B - Walk Plus Completion",
      description: "Someone walked and completed",
      stops: [
        { tid: stopTidB, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#222",
      backgroundColor: "#ddd",
      createdAt: now(),
    });

    // Trail A: one walk
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailA.uri, cid: trailA.cid },
      visitedStops: [stopTidA],
      createdAt: now(),
      updatedAt: now(),
    });

    // Trail B: one walk + one completion (more activity)
    await emit("app.sidetrail.walk", DAVE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailB.uri, cid: trailB.cid },
      visitedStops: [stopTidB],
      createdAt: now(),
      updatedAt: now(),
    });

    await emit("app.sidetrail.completion", EVE.did, {
      $type: "app.sidetrail.completion",
      trail: { uri: trailB.uri, cid: trailB.cid },
      createdAt: now(),
    });

    const trails = await queries.loadTrails();

    // Trail B with more activity (walk + completion) ranks higher
    expect(trails[0].title).toBe("Trail B - Walk Plus Completion");
    expect(trails[1].title).toBe("Trail A - Only Walk");
  });

  it("trails with activity rank above trails without activity", async () => {
    const stopTid = generateTid();

    // Create an old trail with recent activity
    const sixtyDaysAgo = new Date(Date.now() - 60 * 24 * 60 * 60 * 1000).toISOString();
    const oldTrailWithActivity = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Old Trail With Activity",
      description: "Old but has activity",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#eee",
      createdAt: sixtyDaysAgo,
    });

    // Create another newer trail with no activity
    const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
    await emit("app.sidetrail.trail", BOB.did, {
      $type: "app.sidetrail.trail",
      title: "New Trail No Activity",
      description: "Newer but no one has walked",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#222",
      backgroundColor: "#ddd",
      createdAt: oneDayAgo,
    });

    // Add activity to the old trail
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: oldTrailWithActivity.uri, cid: oldTrailWithActivity.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

    const trails = await queries.loadTrails();

    // Recent activity outweighs creation recency
    expect(trails).toHaveLength(2);
    expect(trails[0].title).toBe("Old Trail With Activity");
    expect(trails[1].title).toBe("New Trail No Activity");
  });

  it("author's own activity does not contribute to hotness", async () => {
    const stopTidA = generateTid();
    const stopTidB = generateTid();

    // Two trails created at the same time
    const trailA = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Trail A - Author Self-Walk",
      description: "Author walked their own trail",
      stops: [
        { tid: stopTidA, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#111",
      backgroundColor: "#eee",
      createdAt: now(),
    });

    const trailB = await emit("app.sidetrail.trail", BOB.did, {
      $type: "app.sidetrail.trail",
      title: "Trail B - Non-Author Walk",
      description: "Someone else walked",
      stops: [
        { tid: stopTidB, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#222",
      backgroundColor: "#ddd",
      createdAt: now(),
    });

    // Trail A: author walks their own trail (shouldn't count toward hotness)
    await emit("app.sidetrail.walk", ALICE.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailA.uri, cid: trailA.cid },
      visitedStops: [stopTidA],
      createdAt: now(),
      updatedAt: now(),
    });

    // Trail B: non-author walks (should count toward hotness)
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trailB.uri, cid: trailB.cid },
      visitedStops: [stopTidB],
      createdAt: now(),
      updatedAt: now(),
    });

    const trails = await queries.loadTrails();

    // Both trails appear, but Trail B (with non-author activity) ranks first
    // since author activity doesn't count toward the score
    expect(trails).toHaveLength(2);
    expect(trails[0].title).toBe("Trail B - Non-Author Walk");
    expect(trails[1].title).toBe("Trail A - Author Self-Walk");
  });
});

describe("Empty states", () => {
  it("home feed is empty when no trails exist", async () => {
    const trails = await queries.loadTrails();
    expect(trails).toHaveLength(0);
  });

  it("trail detail page works even with no activity", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Lonely Trail",
      description: "Nobody here yet",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#555",
      backgroundColor: "#666",
      createdAt: now(),
    });

    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    const walkers = await detail.walkers;
    const completions = await detail.completions;
    const activeWalkers = await queries.loadTrailActiveWalkers(trail.uri);

    expect(walkers).toHaveLength(0);
    expect(completions).toHaveLength(0);
    expect(activeWalkers).toHaveLength(0);
  });

  it("non-existent trail throws error", async () => {
    await expect(queries.loadTrailDetail(ALICE.handle, "nonexistent")).rejects.toThrow(
      "Trail not found",
    );
  });
});
