import {
  ICardQueryRepository,
  CardQueryOptions,
  UrlCardQueryResultDTO,
  CollectionCardQueryResultDTO,
  UrlCardViewDTO,
  UrlCardView,
  PaginatedQueryResult,
  CardSortField,
  SortOrder,
  LibraryForUrlDTO,
  NoteCardForUrlRawDTO,
  UrlLibraryInfo,
  SearchUrlsOptions,
  UrlSearchResultDTO,
} from '../../domain/ICardQueryRepository';
import { CardTypeEnum } from '../../domain/value-objects/CardType';
import { InMemoryCardRepository } from './InMemoryCardRepository';
import { InMemoryCollectionRepository } from './InMemoryCollectionRepository';
import { IConnectionRepository } from '../../domain/IConnectionRepository';
import { Card } from '../../domain/Card';
import { CollectionId } from '../../domain/value-objects/CollectionId';
import { CuratorId } from '../../domain/value-objects/CuratorId';
import { UrlOrCardId } from '../../domain/value-objects/UrlOrCardId';
import { URL } from '../../domain/value-objects/URL';

export class InMemoryCardQueryRepository implements ICardQueryRepository {
  constructor(
    private cardRepository: InMemoryCardRepository,
    private collectionRepository: InMemoryCollectionRepository,
    private connectionRepository?: IConnectionRepository,
  ) {}

  async getUrlCardsOfUser(
    userId: string,
    options: CardQueryOptions,
    callingUserId?: string,
  ): Promise<PaginatedQueryResult<UrlCardQueryResultDTO>> {
    try {
      // Get all cards and filter by user's library membership
      const allCards = this.cardRepository.getAllCards();
      let userCards = allCards.filter(
        (card) =>
          card.isUrlCard && card.isInLibrary(CuratorId.create(userId).unwrap()),
      );

      // Filter by urlType if specified
      if (options.urlType) {
        userCards = userCards.filter(
          (card) => card.content.urlContent?.metadata?.type === options.urlType,
        );
      }

      // Filter out cards in collections if uncollected flag is set
      if (options.uncollected) {
        const allCollections = this.collectionRepository.getAllCollections();
        const collectedCardIds = new Set<string>();

        // Find all cards that the user added to any collection
        for (const collection of allCollections) {
          for (const cardLink of collection.cardLinks) {
            if (cardLink.addedBy.value === userId) {
              collectedCardIds.add(cardLink.cardId.getStringValue());
            }
          }
        }

        // Filter out cards that are in collections
        userCards = userCards.filter(
          (card) => !collectedCardIds.has(card.cardId.getStringValue()),
        );
      }

      const userCardResults = await Promise.all(
        userCards.map((card) =>
          this.cardToUrlCardQueryResult(card, callingUserId),
        ),
      );

      // Sort cards
      const sortedCards = this.sortCards(
        userCardResults,
        options.sortBy,
        options.sortOrder,
      );

      // Apply pagination
      const startIndex = (options.page - 1) * options.limit;
      const endIndex = startIndex + options.limit;
      const paginatedCards = sortedCards.slice(startIndex, endIndex);

      return {
        items: paginatedCards,
        totalCount: userCardResults.length,
        hasMore: endIndex < userCardResults.length,
      };
    } catch (error) {
      throw new Error(
        `Failed to query URL cards: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  private sortCards(
    cards: UrlCardQueryResultDTO[],
    sortBy: CardSortField,
    sortOrder: SortOrder,
  ): UrlCardQueryResultDTO[] {
    const sorted = [...cards].sort((a, b) => {
      let comparison = 0;

      switch (sortBy) {
        case CardSortField.CREATED_AT:
          comparison = a.createdAt.getTime() - b.createdAt.getTime();
          break;
        case CardSortField.UPDATED_AT:
          comparison = a.updatedAt.getTime() - b.updatedAt.getTime();
          break;
        case CardSortField.LIBRARY_COUNT:
          comparison = a.libraryCount - b.libraryCount;
          break;
        default:
          comparison = 0;
      }

      return sortOrder === SortOrder.DESC ? -comparison : comparison;
    });

    return sorted;
  }

  private async cardToUrlCardQueryResult(
    card: Card,
    callingUserId?: string,
  ): Promise<UrlCardQueryResultDTO> {
    if (!card.isUrlCard || !card.content.urlContent) {
      throw new Error('Card is not a URL card');
    }

    // Find collections this card belongs to by querying the collection repository
    const allCollections = this.collectionRepository.getAllCollections();
    const collections: {
      id: string;
      name: string;
      authorId: string;
      accessType: string;
    }[] = [];

    for (const collection of allCollections) {
      if (
        collection.cardIds.some(
          (cardId) => cardId.getStringValue() === card.cardId.getStringValue(),
        )
      ) {
        collections.push({
          id: collection.collectionId.getStringValue(),
          name: collection.name.value,
          authorId: collection.authorId.value,
          accessType: collection.accessType,
        });
      }
    }

    // Find note card by the same author with matching parent card ID
    const allCards = this.cardRepository.getAllCards();
    const noteCard = allCards.find(
      (c) =>
        c.type.value === 'NOTE' &&
        c.parentCardId?.equals(card.cardId) &&
        c.curatorId.value === card.curatorId.value, // Only notes by the same author
    );

    const note = noteCard
      ? {
          id: noteCard.cardId.getStringValue(),
          text: noteCard.content.noteContent?.text || '',
        }
      : undefined;

    const url = card.content.urlContent.url.value;

    // Compute urlInLibrary if callingUserId is provided
    const urlInLibrary = callingUserId
      ? this.isUrlInUserLibrary(url, callingUserId)
      : undefined;

    // Get connection count and connection status
    const urlConnectionCount = await this.getUrlConnectionCount(url);
    const urlIsConnected = callingUserId
      ? await this.isUrlConnectedByUser(url, callingUserId)
      : undefined;

    return {
      id: card.cardId.getStringValue(),
      type: CardTypeEnum.URL,
      uri: card.publishedRecordId?.uri,
      url,
      cardContent: {
        url,
        title: card.content.urlContent.metadata?.title,
        description: card.content.urlContent.metadata?.description,
        author: card.content.urlContent.metadata?.author,
        imageUrl: card.content.urlContent.metadata?.imageUrl,
      },
      libraryCount: this.getLibraryCountForCard(card.cardId.getStringValue()),
      urlLibraryCount: this.getUrlLibraryCount(url),
      urlInLibrary,
      urlConnectionCount,
      urlIsConnected,
      createdAt: card.createdAt,
      updatedAt: card.updatedAt,
      authorId: card.curatorId.value,
      collections,
      note,
    };
  }

  private getLibraryCountForCard(cardId: string): number {
    const card = this.cardRepository.getStoredCard({
      getStringValue: () => cardId,
    } as any);
    return card ? card.libraryMembershipCount : 0;
  }

  private getUrlLibraryCount(url: string): number {
    // Get all URL cards with this URL and count unique library memberships
    const allCards = this.cardRepository.getAllCards();
    const urlCards = allCards.filter(
      (card) => card.isUrlCard && card.url?.value === url,
    );

    // Get all unique user IDs who have any card with this URL
    const uniqueUserIds = new Set<string>();
    for (const card of urlCards) {
      for (const membership of card.libraryMemberships) {
        uniqueUserIds.add(membership.curatorId.value);
      }
    }

    return uniqueUserIds.size;
  }

  private isUrlInUserLibrary(url: string, userId: string): boolean {
    // Check if the user has any URL card with this URL (by checking authorId)
    const allCards = this.cardRepository.getAllCards();
    return allCards.some(
      (card) =>
        card.isUrlCard &&
        card.url?.value === url &&
        card.curatorId.value === userId,
    );
  }

  private async getUrlConnectionCount(url: string): Promise<number> {
    if (!this.connectionRepository) {
      return 0;
    }

    try {
      const urlResult = URL.create(url);
      if (urlResult.isErr()) {
        return 0;
      }

      const urlOrCardIdResult = UrlOrCardId.createFromUrl(urlResult.value);
      if (urlOrCardIdResult.isErr()) {
        return 0;
      }

      const urlOrCardId = urlOrCardIdResult.value;

      // Get connections where this URL is the source
      const sourceConnectionsResult =
        await this.connectionRepository.findBySource(urlOrCardId);
      const sourceConnections = sourceConnectionsResult.isOk()
        ? sourceConnectionsResult.value
        : [];

      // Get connections where this URL is the target
      const targetConnectionsResult =
        await this.connectionRepository.findByTarget(urlOrCardId);
      const targetConnections = targetConnectionsResult.isOk()
        ? targetConnectionsResult.value
        : [];

      return sourceConnections.length + targetConnections.length;
    } catch (error) {
      return 0;
    }
  }

  private async isUrlConnectedByUser(
    url: string,
    userId: string,
  ): Promise<boolean> {
    if (!this.connectionRepository) {
      return false;
    }

    try {
      const curatorId = CuratorId.create(userId);
      if (curatorId.isErr()) {
        return false;
      }

      // Get all connections by this curator
      const connectionsResult = await this.connectionRepository.findByCuratorId(
        curatorId.value,
      );
      if (connectionsResult.isErr()) {
        return false;
      }

      const connections = connectionsResult.value;

      // Check if any connection involves this URL (as source or target)
      return connections.some(
        (conn) =>
          (conn.source.isUrl && conn.source.url?.value === url) ||
          (conn.target.isUrl && conn.target.url?.value === url),
      );
    } catch (error) {
      return false;
    }
  }

  async getCardsInCollection(
    collectionId: string,
    options: CardQueryOptions,
    callingUserId?: string,
  ): Promise<PaginatedQueryResult<CollectionCardQueryResultDTO>> {
    try {
      // Get the collection from the repository
      const collectionIdObj = CollectionId.createFromString(collectionId);
      if (collectionIdObj.isErr()) {
        throw new Error(`Invalid collection ID: ${collectionId}`);
      }

      const collectionResult = await this.collectionRepository.findById(
        collectionIdObj.value,
      );
      if (collectionResult.isErr()) {
        throw collectionResult.error;
      }

      const collection = collectionResult.value;
      if (!collection) {
        return {
          items: [],
          totalCount: 0,
          hasMore: false,
        };
      }

      // Get cards that are in this collection
      const allCards = this.cardRepository.getAllCards();
      const collectionCardIds = new Set(
        collection.cardIds.map((id) => id.getStringValue()),
      );
      let collectionCards = allCards.filter(
        (card) =>
          collectionCardIds.has(card.cardId.getStringValue()) && card.isUrlCard,
      );

      // Filter by urlType if specified
      if (options.urlType) {
        collectionCards = collectionCards.filter(
          (card) => card.content.urlContent?.metadata?.type === options.urlType,
        );
      }

      const collectionCardResults = await Promise.all(
        collectionCards.map(async (card) =>
          this.toCollectionCardQueryResult(
            await this.cardToUrlCardQueryResult(card, callingUserId),
          ),
        ),
      );

      // Sort cards
      const sortedCards = this.sortCollectionCards(
        collectionCardResults,
        options.sortBy,
        options.sortOrder,
      );

      // Apply pagination
      const startIndex = (options.page - 1) * options.limit;
      const endIndex = startIndex + options.limit;
      const paginatedCards = sortedCards.slice(startIndex, endIndex);

      return {
        items: paginatedCards,
        totalCount: collectionCardResults.length,
        hasMore: endIndex < collectionCardResults.length,
      };
    } catch (error) {
      throw new Error(
        `Failed to query collection cards: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  private sortCollectionCards(
    cards: CollectionCardQueryResultDTO[],
    sortBy: CardSortField,
    sortOrder: SortOrder,
  ): CollectionCardQueryResultDTO[] {
    const sorted = [...cards].sort((a, b) => {
      let comparison = 0;

      switch (sortBy) {
        case CardSortField.CREATED_AT:
          comparison = a.createdAt.getTime() - b.createdAt.getTime();
          break;
        case CardSortField.UPDATED_AT:
          comparison = a.updatedAt.getTime() - b.updatedAt.getTime();
          break;
        case CardSortField.LIBRARY_COUNT:
          // Sort by URL library count instead of card library count
          comparison = a.urlLibraryCount - b.urlLibraryCount;
          break;
        default:
          comparison = 0;
      }

      return sortOrder === SortOrder.DESC ? -comparison : comparison;
    });

    return sorted;
  }

  private toCollectionCardQueryResult(
    card: UrlCardQueryResultDTO,
  ): CollectionCardQueryResultDTO {
    return {
      id: card.id,
      type: CardTypeEnum.URL,
      url: card.url,
      cardContent: card.cardContent,
      libraryCount: card.libraryCount,
      urlLibraryCount: card.urlLibraryCount,
      urlInLibrary: card.urlInLibrary,
      createdAt: card.createdAt,
      updatedAt: card.updatedAt,
      authorId: card.authorId,
      note: card.note,
    };
  }

  async getUrlCardView(
    cardId: string,
    callingUserId?: string,
  ): Promise<UrlCardViewDTO | null> {
    const allCards = this.cardRepository.getAllCards();
    const card = allCards.find((c) => c.cardId.getStringValue() === cardId);
    if (!card || !card.isUrlCard) {
      return null;
    }

    const urlCardResult = await this.cardToUrlCardQueryResult(
      card,
      callingUserId,
    );

    // Get library memberships from the card itself
    const libraries = card.libraryMemberships.map((membership) => ({
      userId: membership.curatorId.value,
    }));

    // Find note card by the same author with matching parent card ID
    const noteCard = allCards.find(
      (c) =>
        c.type.value === 'NOTE' &&
        c.parentCardId?.equals(card.cardId) &&
        c.curatorId.value === card.curatorId.value, // Only notes by the same author
    );

    const note = noteCard
      ? {
          id: noteCard.cardId.getStringValue(),
          text: noteCard.content.noteContent?.text || '',
        }
      : undefined;

    return {
      ...urlCardResult,
      libraries,
      note,
    };
  }

  async getUrlCardBasic(
    cardId: string,
    callingUserId?: string,
  ): Promise<UrlCardView | null> {
    const allCards = this.cardRepository.getAllCards();
    const card = allCards.find((c) => c.cardId.getStringValue() === cardId);
    if (!card || !card.isUrlCard) {
      return null;
    }

    // Find note card by the same author with matching parent card ID
    const noteCard = allCards.find(
      (c) =>
        c.type.value === 'NOTE' &&
        c.parentCardId?.equals(card.cardId) &&
        c.curatorId.value === card.curatorId.value, // Only notes by the same author
    );

    const note = noteCard
      ? {
          id: noteCard.cardId.getStringValue(),
          text: noteCard.content.noteContent?.text || '',
        }
      : undefined;

    const url = card.content.urlContent!.url.value;

    // Compute urlInLibrary if callingUserId is provided
    const urlInLibrary = callingUserId
      ? this.isUrlInUserLibrary(url, callingUserId)
      : undefined;

    // Get connection count and connection status
    const urlConnectionCount = await this.getUrlConnectionCount(url);
    const urlIsConnected = callingUserId
      ? await this.isUrlConnectedByUser(url, callingUserId)
      : undefined;

    return {
      id: card.cardId.getStringValue(),
      type: CardTypeEnum.URL,
      url,
      cardContent: {
        url,
        title: card.content.urlContent!.metadata?.title,
        description: card.content.urlContent!.metadata?.description,
        author: card.content.urlContent!.metadata?.author,
        imageUrl: card.content.urlContent!.metadata?.imageUrl,
      },
      libraryCount: this.getLibraryCountForCard(card.cardId.getStringValue()),
      urlLibraryCount: this.getUrlLibraryCount(url),
      urlInLibrary,
      urlConnectionCount,
      urlIsConnected,
      createdAt: card.createdAt,
      updatedAt: card.updatedAt,
      authorId: card.curatorId.value,
      note,
    };
  }

  async getLibrariesForCard(cardId: string): Promise<string[]> {
    const allCards = this.cardRepository.getAllCards();
    const card = allCards.find((c) => c.cardId.getStringValue() === cardId);

    if (!card) {
      return [];
    }

    return card.libraryMemberships.map(
      (membership) => membership.curatorId.value,
    );
  }

  async getLibrariesForUrl(
    url: string,
    options: CardQueryOptions,
  ): Promise<PaginatedQueryResult<LibraryForUrlDTO>> {
    try {
      // Get all cards and filter by URL
      const allCards = this.cardRepository.getAllCards();
      const urlCards = allCards.filter(
        (card) => card.isUrlCard && card.url?.value === url,
      );

      // Create library entries for each card
      const libraries: LibraryForUrlDTO[] = [];
      for (const card of urlCards) {
        // Skip cards without urlContent (should not happen since we filtered for URL cards)
        if (!card.content.urlContent) {
          continue;
        }

        for (const membership of card.libraryMemberships) {
          const noteCard = allCards.find(
            (c) => c.isNoteCard && c.parentCardId?.equals(card.cardId),
          );

          const urlLibraryCount = this.getUrlLibraryCount(url);

          libraries.push({
            userId: membership.curatorId.value,
            card: {
              id: card.cardId.getStringValue(),
              url: card.content.urlContent.url.value,
              cardContent: {
                url: card.content.urlContent.url.value,
                title: card.content.urlContent.metadata?.title,
                description: card.content.urlContent.metadata?.description,
                author: card.content.urlContent.metadata?.author,
                imageUrl: card.content.urlContent.metadata?.imageUrl,
              },
              libraryCount: this.getLibraryCountForCard(
                card.cardId.getStringValue(),
              ),
              urlLibraryCount,
              urlInLibrary: true,
              createdAt: card.createdAt,
              updatedAt: card.updatedAt,
              note: noteCard
                ? {
                    id: noteCard.cardId.getStringValue(),
                    text: noteCard.content.noteContent?.text || '',
                  }
                : undefined,
            },
          });
        }
      }

      // Sort libraries (by userId for consistency)
      const sortedLibraries = this.sortLibraries(
        libraries,
        options.sortBy,
        options.sortOrder,
      );

      // Apply pagination
      const startIndex = (options.page - 1) * options.limit;
      const endIndex = startIndex + options.limit;
      const paginatedLibraries = sortedLibraries.slice(startIndex, endIndex);

      return {
        items: paginatedLibraries,
        totalCount: libraries.length,
        hasMore: endIndex < libraries.length,
      };
    } catch (error) {
      throw new Error(
        `Failed to query libraries for URL: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  private sortLibraries(
    libraries: LibraryForUrlDTO[],
    sortBy: CardSortField,
    sortOrder: SortOrder,
  ): LibraryForUrlDTO[] {
    const sorted = [...libraries].sort((a, b) => {
      let comparison = 0;

      switch (sortBy) {
        case CardSortField.CREATED_AT:
        case CardSortField.UPDATED_AT:
        case CardSortField.LIBRARY_COUNT:
          // For libraries, we'll sort by userId as a fallback
          comparison = a.userId.localeCompare(b.userId);
          break;
        default:
          comparison = a.userId.localeCompare(b.userId);
      }

      return sortOrder === SortOrder.DESC ? -comparison : comparison;
    });

    return sorted;
  }

  async getNoteCardsForUrl(
    url: string,
    options: CardQueryOptions,
  ): Promise<PaginatedQueryResult<NoteCardForUrlRawDTO>> {
    try {
      // Get all note cards with the specified URL
      const allCards = this.cardRepository.getAllCards();
      const noteCards = allCards
        .filter((card) => card.isNoteCard && card.url?.value === url)
        .map((card) => ({
          id: card.cardId.getStringValue(),
          note: card.content.noteContent?.text || '',
          authorId: card.curatorId.value,
          createdAt: card.createdAt,
          updatedAt: card.updatedAt,
        }));

      // Sort note cards
      const sortedNotes = this.sortNoteCards(
        noteCards,
        options.sortBy,
        options.sortOrder,
      );

      // Apply pagination
      const startIndex = (options.page - 1) * options.limit;
      const endIndex = startIndex + options.limit;
      const paginatedNotes = sortedNotes.slice(startIndex, endIndex);

      return {
        items: paginatedNotes,
        totalCount: noteCards.length,
        hasMore: endIndex < noteCards.length,
      };
    } catch (error) {
      throw new Error(
        `Failed to query note cards for URL: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  private sortNoteCards(
    notes: NoteCardForUrlRawDTO[],
    sortBy: CardSortField,
    sortOrder: SortOrder,
  ): NoteCardForUrlRawDTO[] {
    const sorted = [...notes].sort((a, b) => {
      let comparison = 0;

      switch (sortBy) {
        case CardSortField.CREATED_AT:
          comparison = a.createdAt.getTime() - b.createdAt.getTime();
          break;
        case CardSortField.UPDATED_AT:
          comparison = a.updatedAt.getTime() - b.updatedAt.getTime();
          break;
        case CardSortField.LIBRARY_COUNT:
          // For note cards, sort by authorId as fallback
          comparison = a.authorId.localeCompare(b.authorId);
          break;
        default:
          comparison = 0;
      }

      return sortOrder === SortOrder.DESC ? -comparison : comparison;
    });

    return sorted;
  }

  async getBatchUrlCardViews(
    cardIds: string[],
    callingUserId?: string,
  ): Promise<Map<string, UrlCardView>> {
    const resultMap = new Map<string, UrlCardView>();

    for (const cardId of cardIds) {
      const cardView = await this.getUrlCardBasic(cardId, callingUserId);
      if (cardView) {
        resultMap.set(cardId, cardView);
      }
    }

    return resultMap;
  }

  async getBatchUrlLibraryInfo(
    urls: string[],
    callingUserId?: string,
  ): Promise<Map<string, UrlLibraryInfo>> {
    const resultMap = new Map<string, UrlLibraryInfo>();

    for (const url of urls) {
      // Get URL library count using existing helper
      const urlLibraryCount = this.getUrlLibraryCount(url);

      // Check if URL is in calling user's library
      const urlInLibrary = callingUserId
        ? this.isUrlInUserLibrary(url, callingUserId)
        : undefined;

      // Get connection count and connection status
      const urlConnectionCount = await this.getUrlConnectionCount(url);
      const urlIsConnected = callingUserId
        ? await this.isUrlConnectedByUser(url, callingUserId)
        : undefined;

      // Get metadata from any card with this URL
      const allCards = this.cardRepository.getAllCards();
      const urlCard = allCards.find(
        (card) => card.isUrlCard && card.url?.value === url,
      );

      const metadata = urlCard?.content.urlContent
        ? {
            url: urlCard.content.urlContent.url.value,
            title: urlCard.content.urlContent.metadata?.title,
            description: urlCard.content.urlContent.metadata?.description,
            author: urlCard.content.urlContent.metadata?.author,
            publishedDate: urlCard.content.urlContent.metadata?.publishedDate,
            siteName: urlCard.content.urlContent.metadata?.siteName,
            imageUrl: urlCard.content.urlContent.metadata?.imageUrl,
            type: urlCard.content.urlContent.metadata?.type,
            retrievedAt: urlCard.content.urlContent.metadata?.retrievedAt,
            doi: urlCard.content.urlContent.metadata?.doi,
            isbn: urlCard.content.urlContent.metadata?.isbn,
          }
        : {
            url,
          };

      resultMap.set(url, {
        urlLibraryCount,
        urlInLibrary,
        urlConnectionCount,
        urlIsConnected,
        metadata,
      });
    }

    return resultMap;
  }

  async searchUrls(
    options: SearchUrlsOptions,
  ): Promise<PaginatedQueryResult<UrlSearchResultDTO>> {
    try {
      const { searchQuery, page, limit, sortBy, sortOrder, urlType } = options;

      // Get all URL cards
      const allCards = this.cardRepository.getAllCards();
      let urlCards = allCards.filter((card) => card.isUrlCard);

      // Filter by urlType if specified
      if (urlType) {
        urlCards = urlCards.filter(
          (card) => card.content.urlContent?.metadata?.type === urlType,
        );
      }

      // Tokenize search query into words
      const searchWords = searchQuery.trim().toLowerCase().split(/\s+/);

      // Filter cards by tokenized substring search
      const matchingCards = urlCards.filter((card) => {
        const title =
          card.content.urlContent?.metadata?.title?.toLowerCase() || '';
        const description =
          card.content.urlContent?.metadata?.description?.toLowerCase() || '';
        const url = card.content.urlContent?.url.value.toLowerCase() || '';

        // All words must match somewhere in title, description, or url
        return searchWords.every((word) => {
          return (
            title.includes(word) ||
            description.includes(word) ||
            url.includes(word)
          );
        });
      });

      // Deduplicate by URL - keep most recent card per URL
      const urlMap = new Map<
        string,
        { card: Card; contentData: any; updatedAt: Date }
      >();

      matchingCards.forEach((card) => {
        const url = card.content.urlContent?.url.value;
        if (!url) return;

        const existing = urlMap.get(url);
        if (
          !existing ||
          card.updatedAt.getTime() > existing.updatedAt.getTime()
        ) {
          // Build contentData from card
          const contentData = {
            url: card.content.urlContent?.url.value,
            metadata: card.content.urlContent?.metadata
              ? {
                  url: card.content.urlContent.metadata.url,
                  title: card.content.urlContent.metadata.title,
                  description: card.content.urlContent.metadata.description,
                  author: card.content.urlContent.metadata.author,
                  publishedDate: card.content.urlContent.metadata.publishedDate,
                  siteName: card.content.urlContent.metadata.siteName,
                  imageUrl: card.content.urlContent.metadata.imageUrl,
                  type: card.content.urlContent.metadata.type,
                  retrievedAt: card.content.urlContent.metadata.retrievedAt,
                  doi: card.content.urlContent.metadata.doi,
                  isbn: card.content.urlContent.metadata.isbn,
                }
              : undefined,
          };

          urlMap.set(url, {
            card,
            contentData,
            updatedAt: card.updatedAt,
          });
        }
      });

      // Convert to array
      const uniqueUrls = Array.from(urlMap.entries()).map(([url, data]) => ({
        url,
        contentData: data.contentData,
        updatedAt: data.updatedAt,
        card: data.card,
      }));

      // For LIBRARY_COUNT sorting, calculate library counts
      if (sortBy === CardSortField.LIBRARY_COUNT) {
        const itemsWithCounts = uniqueUrls.map((item) => {
          // Use the existing helper method to get library count
          const libraryCount = this.getUrlLibraryCount(item.url);

          return {
            ...item,
            libraryCount,
          };
        });

        // Sort by library count
        itemsWithCounts.sort((a, b) => {
          const comparison = a.libraryCount - b.libraryCount;
          return sortOrder === SortOrder.ASC ? comparison : -comparison;
        });

        // Paginate
        const startIndex = (page - 1) * limit;
        const endIndex = startIndex + limit;
        const paginatedItems = itemsWithCounts.slice(startIndex, endIndex);

        return {
          items: paginatedItems.map((item) => ({
            url: item.url,
            contentData: item.contentData,
            updatedAt: item.updatedAt,
          })),
          totalCount: itemsWithCounts.length,
          hasMore: endIndex < itemsWithCounts.length,
        };
      }

      // For other sort fields (CREATED_AT, UPDATED_AT)
      uniqueUrls.sort((a, b) => {
        let comparison = 0;

        switch (sortBy) {
          case CardSortField.CREATED_AT:
            comparison =
              a.card.createdAt.getTime() - b.card.createdAt.getTime();
            break;
          case CardSortField.UPDATED_AT:
          default:
            comparison = a.updatedAt.getTime() - b.updatedAt.getTime();
            break;
        }

        return sortOrder === SortOrder.ASC ? comparison : -comparison;
      });

      // Paginate
      const startIndex = (page - 1) * limit;
      const endIndex = startIndex + limit;
      const paginatedUrls = uniqueUrls.slice(startIndex, endIndex);

      return {
        items: paginatedUrls.map((item) => ({
          url: item.url,
          contentData: item.contentData,
          updatedAt: item.updatedAt,
        })),
        totalCount: uniqueUrls.length,
        hasMore: endIndex < uniqueUrls.length,
      };
    } catch (error) {
      throw new Error(
        `Failed to search URLs: ${error instanceof Error ? error.message : String(error)}`,
      );
    }
  }

  async getUrlAggregateStats(url: string): Promise<{
    libraryCount: number;
    noteCount: number;
  }> {
    const allCards = this.cardRepository.getAllCards();

    // Get library count - count distinct users who have URL cards with this URL
    const urlCards = allCards.filter(
      (c) => c.type.value === 'URL' && c.url?.value === url,
    );
    const uniqueUsers = new Set<string>();
    urlCards.forEach((card) => {
      card.libraryMemberships.forEach((membership) => {
        uniqueUsers.add(membership.curatorId.value);
      });
    });
    const libraryCount = uniqueUsers.size;

    // Get note count - count NOTE cards that reference URL cards with this URL
    const urlCardIds = new Set(urlCards.map((c) => c.cardId.getStringValue()));
    const noteCount = allCards.filter(
      (c) =>
        c.type.value === 'NOTE' &&
        c.parentCardId &&
        urlCardIds.has(c.parentCardId.getStringValue()),
    ).length;

    return {
      libraryCount,
      noteCount,
    };
  }

  async getProfileCardStats(authorId: string): Promise<{
    urlCardCount: number;
  }> {
    const allCards = this.cardRepository.getAllCards();

    // Count URL cards authored by this user
    const urlCardCount = allCards.filter(
      (c) => c.type.value === 'URL' && c.curatorId.value === authorId,
    ).length;

    return {
      urlCardCount,
    };
  }

  clear(): void {
    // No separate state to clear
  }
}
