/**
 * Walking Journey Tests
 *
 * Scenarios covering the user's journey through trails:
 * discovering, starting, progressing, completing, abandoning, and restarting.
 */

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

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

describe("Starting a walk", () => {
  it("user discovers trail, starts walking, sees it in their walks list", async () => {
    const stop1 = generateTid();
    const stop2 = generateTid();

    // Alice publishes a trail
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Learn TypeScript",
      description: "A beginner's guide to TS",
      stops: [
        { tid: stop1, title: "Types Basics", content: "Let's start with types" },
        { tid: stop2, title: "Interfaces", content: "Now interfaces" },
      ],
      accentColor: "#3178c6",
      backgroundColor: "#f0f4f8",
      createdAt: now(),
    });

    // Bob discovers it and starts walking
    setCurrentUser(BOB);
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1],
      createdAt: now(),
      updatedAt: now(),
    });

    // Bob's walks list shows the trail
    const walks = await queries.loadWalks();
    expect(walks).toHaveLength(1);
    expect(walks[0].title).toBe("Learn TypeScript");
    expect(walks[0].visitedStops).toEqual([stop1]);

    // Trail detail shows Bob's progress
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.yourWalk).not.toBeNull();
    expect(detail.yourWalk?.visitedStops).toEqual([stop1]);

    // Bob appears as a walker
    const walkers = await detail.walkers;
    expect(walkers).toHaveLength(1);
    expect(walkers[0].user.handle).toBe(BOB.handle);
    expect(walkers[0].isYou).toBe(true);

    // Bob appears at stop 1
    const atStop1 = await detail.stops[0].walkersHere;
    expect(atStop1.map((u) => u.handle)).toContain(BOB.handle);
  });

  it("walking badges show max 3 most recent walks", async () => {
    const createTrailAndWalk = async (title: string, createdAt: string) => {
      const trail = await emit("app.sidetrail.trail", ALICE.did, {
        $type: "app.sidetrail.trail",
        title,
        description: "Test",
        stops: [
          { tid: generateTid(), title: "Stop", content: "Content" },
          { tid: generateTid(), title: "Filler stop", content: "Filler content" },
        ],
        accentColor: `#${title.replace(" ", "")}`,
        backgroundColor: "#ffffff",
        createdAt: now(),
      });

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

      return trail;
    };

    setCurrentUser(BOB);

    // Bob walks 4 trails at different times
    await createTrailAndWalk("Oldest", "2024-01-01T10:00:00Z");
    await createTrailAndWalk("Second", "2024-01-02T10:00:00Z");
    await createTrailAndWalk("Third", "2024-01-03T10:00:00Z");
    await createTrailAndWalk("Newest", "2024-01-04T10:00:00Z");

    // Badges show exactly 3
    const badges = await queries.loadWalkingBadges();
    expect(badges).toHaveLength(3);
  });
});

describe("Progressing through a trail", () => {
  it("user visits stops one by one, position updates correctly", async () => {
    const stop1 = generateTid();
    const stop2 = generateTid();
    const stop3 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Three Stop Trail",
      description: "Progress through all stops",
      stops: [
        { tid: stop1, title: "First", content: "Start" },
        { tid: stop2, title: "Second", content: "Middle" },
        { tid: stop3, title: "Third", content: "End" },
      ],
      accentColor: "#ff0000",
      backgroundColor: "#fff0f0",
      createdAt: now(),
    });

    setCurrentUser(BOB);

    // Start at stop 1
    const walk = await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1],
      createdAt: now(),
      updatedAt: now(),
    });

    let detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect((await detail.stops[0].walkersHere).map((u) => u.handle)).toContain(BOB.handle);
    expect(await detail.stops[1].walkersHere).toHaveLength(0);

    // Move to stop 2
    await emit(
      "app.sidetrail.walk",
      BOB.did,
      {
        $type: "app.sidetrail.walk",
        trail: { uri: trail.uri, cid: trail.cid },
        visitedStops: [stop1, stop2],
        createdAt: now(),
        updatedAt: now(),
      },
      walk.rkey,
    );

    detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(await detail.stops[0].walkersHere).toHaveLength(0);
    expect((await detail.stops[1].walkersHere).map((u) => u.handle)).toContain(BOB.handle);

    // Move to stop 3
    await emit(
      "app.sidetrail.walk",
      BOB.did,
      {
        $type: "app.sidetrail.walk",
        trail: { uri: trail.uri, cid: trail.cid },
        visitedStops: [stop1, stop2, stop3],
        createdAt: now(),
        updatedAt: now(),
      },
      walk.rkey,
    );

    detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect((await detail.stops[2].walkersHere).map((u) => u.handle)).toContain(BOB.handle);

    // Walks list shows full progress
    const walks = await queries.loadWalks();
    expect(walks[0].visitedStops).toEqual([stop1, stop2, stop3]);
  });

  it("going back to a previous stop updates walker position", async () => {
    const stop1 = generateTid();
    const stop2 = generateTid();
    const stop3 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Go Back Trail",
      description: "Test going back",
      stops: [
        { tid: stop1, title: "First", content: "Start" },
        { tid: stop2, title: "Second", content: "Middle" },
        { tid: stop3, title: "Third", content: "End" },
      ],
      accentColor: "#ff0000",
      backgroundColor: "#fff0f0",
      createdAt: now(),
    });

    setCurrentUser(BOB);

    // Bob visits all 3 stops
    const walk = await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1, stop2, stop3],
      createdAt: now(),
      updatedAt: now(),
    });

    // Bob is at stop 3
    let detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect((await detail.stops[2].walkersHere).map((u) => u.handle)).toContain(BOB.handle);
    expect(await detail.stops[1].walkersHere).toHaveLength(0);

    // Bob goes back to stop 2 (visitedStops becomes [stop1, stop3, stop2])
    await emit(
      "app.sidetrail.walk",
      BOB.did,
      {
        $type: "app.sidetrail.walk",
        trail: { uri: trail.uri, cid: trail.cid },
        visitedStops: [stop1, stop3, stop2], // stop2 moved to end = current position
        createdAt: now(),
        updatedAt: now(),
      },
      walk.rkey,
    );

    // Bob is now at stop 2, not stop 3
    detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect((await detail.stops[1].walkersHere).map((u) => u.handle)).toContain(BOB.handle);
    expect(await detail.stops[2].walkersHere).toHaveLength(0);

    // Walks list shows updated order
    const walks = await queries.loadWalks();
    expect(walks[0].visitedStops).toEqual([stop1, stop3, stop2]);
  });

  it("walk updates affect activity ordering", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Activity Order Trail",
      description: "Test",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#00ff00",
      backgroundColor: "#f0fff0",
      createdAt: now(),
    });

    // Bob starts first
    const bobWalk = 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",
    });

    // Carol starts later
    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",
    });

    // Carol is first (most recent)
    let detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    let walkers = await detail.walkers;
    expect(walkers[0].user.handle).toBe(CAROL.handle);

    // Bob updates his walk (now most recent)
    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-03T10:00:00Z",
      },
      bobWalk.rkey,
    );

    detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    walkers = await detail.walkers;
    expect(walkers[0].user.handle).toBe(BOB.handle);
  });
});

describe("Completing a trail", () => {
  it("completion appears on trail and in user's profile", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Completable Trail",
      description: "Finish this one",
      stops: [
        { tid: stopTid, title: "Only Stop", content: "Done!" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ffd700",
      backgroundColor: "#fffde7",
      createdAt: now(),
    });

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

    // Completion shows on trail
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    const completions = await detail.completions;
    expect(completions).toHaveLength(1);
    expect(completions[0].user.handle).toBe(BOB.handle);

    // Completer also appears in walkers list
    const walkers = await detail.walkers;
    expect(walkers.map((w) => w.user.handle)).toContain(BOB.handle);

    // Completion shows on Bob's profile
    const completedTrails = await queries.loadUserCompletedTrails(BOB.handle);
    expect(completedTrails).toHaveLength(1);
    expect(completedTrails[0].title).toBe("Completable Trail");
  });

  it("completing same trail twice shows most recent in profile (deduped)", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Re-completable",
      description: "Can complete multiple times",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ff00ff",
      backgroundColor: "#fff0ff",
      createdAt: now(),
    });

    // Bob completes twice
    await emit("app.sidetrail.completion", BOB.did, {
      $type: "app.sidetrail.completion",
      trail: { uri: trail.uri, cid: trail.cid },
      createdAt: "2024-01-01T10:00:00Z",
    });

    await emit("app.sidetrail.completion", BOB.did, {
      $type: "app.sidetrail.completion",
      trail: { uri: trail.uri, cid: trail.cid },
      createdAt: "2024-06-01T10:00:00Z",
    });

    // Profile shows only one (most recent)
    const completedTrails = await queries.loadUserCompletedTrails(BOB.handle);
    expect(completedTrails).toHaveLength(1);
    expect(completedTrails[0].completedAt.toISOString()).toBe("2024-06-01T10:00:00.000Z");

    // But trail detail shows both completions
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    const completions = await detail.completions;
    expect(completions).toHaveLength(2);
  });

  it("user with both active walk AND completion: most recent wins in walkers", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Walk And Complete",
      description: "Testing precedence",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#0000ff",
      backgroundColor: "#f0f0ff",
      createdAt: now(),
    });

    // Bob walks first (older)
    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",
    });

    // Bob completes later (newer)
    await emit("app.sidetrail.completion", BOB.did, {
      $type: "app.sidetrail.completion",
      trail: { uri: trail.uri, cid: trail.cid },
      createdAt: "2024-06-01T10:00:00Z",
    });

    // Bob appears only once in walkers (dedupe by user)
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    const walkers = await detail.walkers;
    const bobEntries = walkers.filter((w) => w.user.handle === BOB.handle);
    expect(bobEntries).toHaveLength(1);
  });
});

describe("Abandoning a walk", () => {
  it("abandoned walk disappears from user's walks and trail's walkers", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Abandonable Trail",
      description: "User might give up",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ff6b6b",
      backgroundColor: "#fff5f5",
      createdAt: now(),
    });

    const walk = 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);

    // Bob is walking
    let walks = await queries.loadWalks();
    expect(walks).toHaveLength(1);

    let detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.yourWalk).not.toBeNull();
    expect(await detail.walkers).toHaveLength(1);

    // Bob abandons
    await emitDelete("app.sidetrail.walk", walk.uri);

    // Walk is gone
    walks = await queries.loadWalks();
    expect(walks).toHaveLength(0);

    detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.yourWalk).toBeNull();
    expect(await detail.walkers).toHaveLength(0);
  });
});

describe("Restarting after abandonment", () => {
  it("user can abandon and start fresh on the same trail", async () => {
    const stop1 = generateTid();
    const stop2 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Restart Trail",
      description: "Try again!",
      stops: [
        { tid: stop1, title: "First", content: "Begin" },
        { tid: stop2, title: "Second", content: "Continue" },
      ],
      accentColor: "#4caf50",
      backgroundColor: "#e8f5e9",
      createdAt: now(),
    });

    setCurrentUser(BOB);

    // First attempt: Bob progresses to stop 2 then abandons
    const firstWalk = await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1, stop2],
      createdAt: now(),
      updatedAt: now(),
    });

    await emitDelete("app.sidetrail.walk", firstWalk.uri);

    // Verify abandoned
    let detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.yourWalk).toBeNull();

    // Second attempt: Bob starts fresh at stop 1
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1],
      createdAt: now(),
      updatedAt: now(),
    });

    detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.yourWalk).not.toBeNull();
    expect(detail.yourWalk?.visitedStops).toEqual([stop1]);

    const walks = await queries.loadWalks();
    expect(walks).toHaveLength(1);
  });
});

describe("Forgetting a trail completely", () => {
  it("user forgets trail: both walk and completion are removed", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Forgettable Trail",
      description: "Remove all traces",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#9c27b0",
      backgroundColor: "#f3e5f5",
      createdAt: now(),
    });

    // Bob walks AND completes (unusual but possible)
    const walk = await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stopTid],
      createdAt: now(),
      updatedAt: now(),
    });

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

    setCurrentUser(BOB);

    // Verify both exist
    let walks = await queries.loadWalks();
    let completedTrails = await queries.loadUserCompletedTrails(BOB.handle);
    expect(walks).toHaveLength(1);
    expect(completedTrails).toHaveLength(1);

    // Bob forgets (deletes both)
    await emitDelete("app.sidetrail.walk", walk.uri);
    await emitDelete("app.sidetrail.completion", completion.uri);

    // Both gone
    walks = await queries.loadWalks();
    completedTrails = await queries.loadUserCompletedTrails(BOB.handle);
    expect(walks).toHaveLength(0);
    expect(completedTrails).toHaveLength(0);

    // Trail shows no Bob activity
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(await detail.walkers).toHaveLength(0);
    expect(await detail.completions).toHaveLength(0);
  });
});

describe("Current user context", () => {
  it("loadWalks returns empty when not logged in", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Some Trail",
      description: "Test",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#123",
      backgroundColor: "#456",
      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(),
    });

    // Not logged in
    const walks = await queries.loadWalks();
    expect(walks).toHaveLength(0);
  });

  it("loadWalkingBadges returns empty when not logged in", async () => {
    const badges = await queries.loadWalkingBadges();
    expect(badges).toHaveLength(0);
  });

  it("yourWalk is null when not logged in", async () => {
    const stopTid = generateTid();
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Trail",
      description: "Test",
      stops: [
        { tid: stopTid, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#abc",
      backgroundColor: "#def",
      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(),
    });

    // Not logged in - but Bob's walk still shows in walkers
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);
    expect(detail.yourWalk).toBeNull();
    expect(await detail.walkers).toHaveLength(1);
  });

  it("loadCurrentUser returns user when logged in", async () => {
    setCurrentUser(BOB);
    const user = await queries.loadCurrentUser();
    expect(user).not.toBeNull();
    expect(user?.did).toBe(BOB.did);
    expect(user?.handle).toBe(BOB.handle);
  });

  it("loadCurrentUser returns null when not logged in", async () => {
    const user = await queries.loadCurrentUser();
    expect(user).toBeNull();
  });
});

describe("Walks ordering", () => {
  it("user's walks ordered by createdAt descending", async () => {
    const createTrailAndWalk = async (title: string, createdAt: string) => {
      const trail = await emit("app.sidetrail.trail", ALICE.did, {
        $type: "app.sidetrail.trail",
        title,
        description: "Test",
        stops: [
          { tid: generateTid(), title: "Stop", content: "Content" },
          { tid: generateTid(), title: "Filler stop", content: "Filler content" },
        ],
        accentColor: "#111",
        backgroundColor: "#eee",
        createdAt: now(),
      });

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

    await createTrailAndWalk("Old Walk", "2024-01-01T10:00:00Z");
    await createTrailAndWalk("New Walk", "2024-06-01T10:00:00Z");
    await createTrailAndWalk("Middle Walk", "2024-03-01T10:00:00Z");

    setCurrentUser(BOB);
    const walks = await queries.loadWalks();

    expect(walks[0].title).toBe("New Walk");
    expect(walks[1].title).toBe("Middle Walk");
    expect(walks[2].title).toBe("Old Walk");
  });

  it("walk without updatedAt uses createdAt as fallback", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Fallback Test",
      description: "Test",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#000",
      backgroundColor: "#fff",
      createdAt: now(),
    });

    // Create walk WITHOUT updatedAt field
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [],
      createdAt: "2024-05-15T12:00:00Z",
      // Note: no updatedAt field
    });

    setCurrentUser(BOB);
    const walks = await queries.loadWalks();

    expect(walks).toHaveLength(1);
    // Should fall back to createdAt from the DB record (not the record field)
    expect(walks[0].updatedAt).toBeInstanceOf(Date);
  });
});

/**
 * Duplicate Walk Handling (Regression Tests)
 *
 * ATProto is the source of truth, but users may end up with duplicate walk records
 * for the same trail (from different clients, race conditions, etc). Our interpretation
 * is "one walk per trail per user" - we always work with the most recently updated walk.
 */
describe("Duplicate walk handling", () => {
  it("loadWalks deduplicates by trail, returning only most recent walk", async () => {
    const stop1 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Trail With Duplicates",
      description: "Testing deduplication",
      stops: [
        { tid: stop1, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ff0000",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

    // Bob has TWO walks for the same trail (simulating ATProto duplicates)
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [], // Older walk - no progress
      createdAt: "2024-01-01T10:00:00Z",
      updatedAt: "2024-01-01T10:00:00Z",
    });

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

    setCurrentUser(BOB);
    const walks = await queries.loadWalks();

    // Should only show ONE walk (the most recent)
    expect(walks).toHaveLength(1);
    expect(walks[0].visitedStops).toEqual([stop1]); // The newer walk's progress
  });

  it("loadUserWalk returns most recently updated walk when duplicates exist", async () => {
    const stop1 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Duplicate Walk Test",
      description: "Testing yourWalk dedup",
      stops: [
        { tid: stop1, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#00ff00",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

    // Bob has two walks - older one has more progress but is stale
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1], // Has progress
      createdAt: "2024-01-01T10:00:00Z",
      updatedAt: "2024-01-01T10:00:00Z",
    });

    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [], // No progress but more recent
      createdAt: "2024-06-01T10:00:00Z",
      updatedAt: "2024-06-01T10:00:00Z",
    });

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

    // Should return the MORE RECENT walk (even though it has less progress)
    expect(detail.yourWalk).not.toBeNull();
    expect(detail.yourWalk?.visitedStops).toEqual([]); // Newer walk's state
  });

  it("loadTrailActiveWalkers shows each user only once even with duplicate walks", async () => {
    const stop1 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Active Walkers Dedup",
      description: "Testing walker dedup",
      stops: [
        { tid: stop1, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#0000ff",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

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

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

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

    // Carol has a normal single walk
    await emit("app.sidetrail.walk", CAROL.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1],
      createdAt: "2024-05-01T10:00:00Z",
      updatedAt: "2024-05-01T10:00:00Z",
    });

    const activeWalkers = await queries.loadTrailActiveWalkers(trail.uri);

    // Bob should appear only ONCE (not 3 times)
    expect(activeWalkers).toHaveLength(2);
    const handles = activeWalkers.map((u) => u.handle);
    expect(handles).toContain(BOB.handle);
    expect(handles).toContain(CAROL.handle);
  });

  it("walkers list shows each user only once with most recent activity", async () => {
    const stop1 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Walkers List Dedup",
      description: "Testing walkers dedup",
      stops: [
        { tid: stop1, title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#ff00ff",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

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

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

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

    // Bob should appear only once
    expect(walkers).toHaveLength(1);
    expect(walkers[0].user.handle).toBe(BOB.handle);
  });

  it("walkersAtStop shows user at only their most recent walk's position", async () => {
    const stop1 = generateTid();
    const stop2 = generateTid();

    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Stop Position Dedup",
      description: "Testing stop position",
      stops: [
        { tid: stop1, title: "First", content: "1" },
        { tid: stop2, title: "Second", content: "2" },
      ],
      accentColor: "#123456",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

    // Bob's older walk is at stop2
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1, stop2],
      createdAt: "2024-01-01T10:00:00Z",
      updatedAt: "2024-01-01T10:00:00Z",
    });

    // Bob's newer walk is at stop1
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [stop1],
      createdAt: "2024-06-01T10:00:00Z",
      updatedAt: "2024-06-01T10:00:00Z",
    });

    setCurrentUser(CAROL); // Not Bob, so we can see Bob as walker
    const detail = await queries.loadTrailDetail(ALICE.handle, trail.rkey);

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

    // Bob should ONLY be at stop1 (his most recent walk's position)
    expect(atStop1.map((u) => u.handle)).toContain(BOB.handle);
    expect(atStop2.map((u) => u.handle)).not.toContain(BOB.handle);
  });

  it("loadWalkingBadges deduplicates by trail", async () => {
    const trail = await emit("app.sidetrail.trail", ALICE.did, {
      $type: "app.sidetrail.trail",
      title: "Badge Dedup Trail",
      description: "Testing badge dedup",
      stops: [
        { tid: generateTid(), title: "Stop", content: "Content" },
        { tid: generateTid(), title: "Filler stop", content: "Filler content" },
      ],
      accentColor: "#badge1",
      backgroundColor: "#ffffff",
      createdAt: now(),
    });

    // Bob has duplicate walks for the same trail
    await emit("app.sidetrail.walk", BOB.did, {
      $type: "app.sidetrail.walk",
      trail: { uri: trail.uri, cid: trail.cid },
      visitedStops: [],
      createdAt: "2024-01-01T10:00:00Z",
      updatedAt: "2024-01-01T10:00:00Z",
    });

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

    setCurrentUser(BOB);
    const badges = await queries.loadWalkingBadges();

    // Should show only ONE badge for this trail
    expect(badges).toHaveLength(1);
  });
});
