/** * EntityGrid.vue - Unified entity grid layout component * * Extracted from
GiftedDialog.vue to provide a reusable grid layout * for displaying people,
projects, and special entities with selection. * * @author Matthew Raymer */
<template>
  <!-- Quick Search -->
  <div id="QuickSearch" class="mb-4 flex items-center text-sm">
    <input
      v-model="searchTerm"
      type="text"
      placeholder="Search…"
      class="block w-full rounded-l border border-r-0 border-slate-400 px-3 py-1.5 placeholder:italic placeholder:text-slate-400 focus:outline-none"
      @input="handleSearchInput"
      @keydown.enter="performSearch"
    />
    <div
      v-show="isSearching && searchTerm"
      class="border-y border-slate-400 ps-2 py-1.5 text-center text-slate-400"
    >
      <font-awesome
        icon="spinner"
        class="fa-spin-pulse leading-[1.1]"
      ></font-awesome>
    </div>
    <button
      :disabled="!searchTerm"
      class="px-2 py-1.5 rounded-r bg-white border border-l-0 border-slate-400 text-slate-400 disabled:cursor-not-allowed"
      @click="clearSearch"
    >
      <font-awesome
        :icon="searchTerm ? 'times' : 'magnifying-glass'"
        class="fa-fw"
      ></font-awesome>
    </button>
  </div>

  <div
    v-if="searchTerm && !isSearching && filteredEntities.length === 0"
    class="mb-4 text-sm italic text-slate-500 text-center"
  >
    “{{ searchTerm }}” doesn't match any
    {{ entityType === "people" ? "people" : "projects" }}. Try a different
    search.
  </div>

  <ul
    ref="scrollContainer"
    class="border-t border-slate-300 mb-4 max-h-[60vh] overflow-y-auto"
  >
    <!-- Special entities (You, Unnamed) for people grids -->
    <template v-if="entityType === 'people'">
      <!-- "You" entity -->
      <SpecialEntityCard
        v-if="showYouEntity && !searchTerm.trim()"
        entity-type="you"
        label="You"
        icon="hand"
        :selectable="youSelectable"
        :conflicted="youConflicted"
        :entity-data="youEntityData"
        :notify="notify"
        :conflict-context="conflictContext"
        @entity-selected="handleEntitySelected"
      />

      <!-- "Unnamed" entity -->
      <SpecialEntityCard
        v-if="showUnnamedEntity && !searchTerm.trim()"
        entity-type="unnamed"
        :label="unnamedEntityName"
        icon="circle-question"
        :entity-data="unnamedEntityData"
        :notify="notify"
        :conflict-context="conflictContext"
        @entity-selected="handleEntitySelected"
      />
    </template>

    <!-- Empty state message -->
    <li v-if="hasNoEntities" :class="emptyStateClasses">
      {{ emptyStateMessage }}
    </li>

    <!-- Entity cards (people or projects) -->
    <template v-if="entityType === 'people'">
      <!-- When showing contacts without search: split into recent and alphabetical -->
      <template v-if="!searchTerm.trim()">
        <!-- Recently Added Section -->
        <template v-if="recentContacts.length > 0">
          <li
            class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
          >
            Recently Added
          </li>
          <PersonCard
            v-for="person in recentContacts"
            :key="person.did"
            :person="person"
            :conflicted="isPersonConflicted(person.did)"
            :show-time-icon="true"
            :notify="notify"
            :conflict-context="conflictContext"
            @person-selected="handlePersonSelected"
          />
        </template>

        <!-- Alphabetical Section -->
        <template v-if="alphabeticalContacts.length > 0">
          <li
            class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
          >
            Everyone
          </li>
          <PersonCard
            v-for="person in alphabeticalContacts"
            :key="person.did"
            :person="person"
            :conflicted="isPersonConflicted(person.did)"
            :show-time-icon="true"
            :notify="notify"
            :conflict-context="conflictContext"
            @person-selected="handlePersonSelected"
          />
        </template>
      </template>

      <!-- When searching: show filtered results normally -->
      <template v-else>
        <PersonCard
          v-for="person in displayedEntities as Contact[]"
          :key="person.did"
          :person="person"
          :conflicted="isPersonConflicted(person.did)"
          :show-time-icon="true"
          :notify="notify"
          :conflict-context="conflictContext"
          @person-selected="handlePersonSelected"
        />
      </template>
    </template>

    <template v-else-if="entityType === 'projects'">
      <!-- When showing projects without search: split into recently bookmarked and rest -->
      <template v-if="!searchTerm.trim()">
        <!-- Recently Bookmarked Section -->
        <template v-if="recentBookmarkedProjects.length > 0">
          <li
            class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
          >
            Recently Bookmarked
          </li>
          <ProjectCard
            v-for="project in recentBookmarkedProjects"
            :key="project.handleId"
            :project="project"
            :active-did="activeDid"
            :all-my-dids="allMyDids"
            :all-contacts="allContacts"
            :conflicted="isProjectConflicted(project.handleId)"
            :notify="notify"
            :conflict-context="conflictContext"
            @project-selected="handleProjectSelected"
          />
        </template>

        <!-- Rest of Projects Section -->
        <li
          v-if="recentBookmarkedProjects.length > 0"
          class="text-xs font-semibold text-slate-500 uppercase pt-5 pb-1.5 px-2 border-b border-slate-300"
        >
          All Projects
        </li>
        <ProjectCard
          v-for="project in remainingProjects"
          :key="project.handleId"
          :project="project"
          :active-did="activeDid"
          :all-my-dids="allMyDids"
          :all-contacts="allContacts"
          :conflicted="isProjectConflicted(project.handleId)"
          :notify="notify"
          :conflict-context="conflictContext"
          @project-selected="handleProjectSelected"
        />
      </template>

      <!-- When searching: show filtered results normally -->
      <template v-else>
        <ProjectCard
          v-for="project in displayedEntities as PlanData[]"
          :key="project.handleId"
          :project="project"
          :active-did="activeDid"
          :all-my-dids="allMyDids"
          :all-contacts="allContacts"
          :conflicted="isProjectConflicted(project.handleId)"
          :notify="notify"
          :conflict-context="conflictContext"
          @project-selected="handleProjectSelected"
        />
      </template>
    </template>
  </ul>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit, Watch } from "vue-facing-decorator";
import { useInfiniteScroll } from "@vueuse/core";
import PersonCard from "./PersonCard.vue";
import ProjectCard from "./ProjectCard.vue";
import SpecialEntityCard from "./SpecialEntityCard.vue";
import { Contact } from "../db/tables/contacts";
import { PlanData } from "../interfaces/records";
import { NotificationIface } from "../constants/app";
import { UNNAMED_ENTITY_NAME } from "@/constants/entities";
import { PlatformServiceMixin } from "@/utils/PlatformServiceMixin";
import { getHeaders } from "../libs/endorserServer";
import { logger } from "../utils/logger";
import { TIMEOUTS } from "@/utils/notify";

/**
 * Constants for infinite scroll configuration
 */
const INITIAL_BATCH_SIZE = 20;
const INCREMENT_SIZE = 20;
const RECENT_CONTACTS_COUNT = 3;
const RECENT_BOOKMARKED_PROJECTS_COUNT = 10;

/**
 * EntityGrid - Unified grid layout for displaying people or projects
 *
 * Features:
 * - Responsive grid layout for people/projects
 * - Special entity integration (You, Unnamed)
 * - Conflict detection integration
 * - Empty state messaging
 * - Event delegation for entity selection
 * - Warning notifications for conflicted entities
 * - Template streamlined with computed CSS properties
 * - Configurable entity display logic via function props
 */
@Component({
  components: {
    PersonCard,
    ProjectCard,
    SpecialEntityCard,
  },
  mixins: [PlatformServiceMixin],
})
export default class EntityGrid extends Vue {
  /** Type of entities to display */
  @Prop({ required: true })
  entityType!: "people" | "projects";

  // Search state
  searchTerm = "";
  isSearching = false;
  searchTimeout: NodeJS.Timeout | null = null;
  filteredEntities: Contact[] | PlanData[] = [];
  searchBeforeId: string | undefined = undefined;
  isLoadingSearchMore = false;

  // API server for project searches
  apiServer = "";

  // Internal project state (when entities prop not provided for projects)
  allProjects: PlanData[] = [];
  loadBeforeId: string | undefined = undefined;
  isLoadingProjects = false;

  // Infinite scroll state
  displayedCount = INITIAL_BATCH_SIZE;
  infiniteScrollReset?: () => void;
  scrollContainer?: HTMLElement;

  // Starred projects state (for showing recently bookmarked projects)
  starredPlanHandleIds: string[] = [];

  /**
   * Array of entities to display
   *
   * For contacts (entityType === 'people'): REQUIRED - Must be a COMPLETE list from local database.
   * Use $contactsByDateAdded() to ensure all contacts are included.
   * Client-side filtering assumes the complete list is available.
   * IMPORTANT: When passing Contact[] arrays, they must be sorted by date added
   * (newest first) for the "Recently Added" section to display correctly.
   *
   * For projects (entityType === 'projects'): OPTIONAL - If not provided, EntityGrid loads
   * projects internally from the API server. If provided, uses the provided list.
   */
  @Prop({ required: false })
  entities?: Contact[] | PlanData[];

  /** Active user's DID */
  @Prop({ required: true })
  activeDid!: string;

  /** All user's DIDs */
  @Prop({ required: true })
  allMyDids!: string[];

  /** All contacts */
  @Prop({ required: true })
  allContacts!: Contact[];

  /** Function to check if a person DID would create a conflict */
  @Prop({ required: true })
  conflictChecker!: (did: string) => boolean;

  /** Whether to show the "You" entity for people grids */
  @Prop({ default: true })
  showYouEntity!: boolean;

  /** Whether to show the "Unnamed" entity for people grids */
  @Prop({ default: true })
  showUnnamedEntity!: boolean;

  /** Whether the "You" entity is selectable */
  @Prop({ default: true })
  youSelectable!: boolean;

  /** Notification function from parent component */
  @Prop()
  notify?: (notification: NotificationIface, timeout?: number) => void;

  /** Context for conflict messages (e.g., "giver", "recipient") */
  @Prop({ default: "other party" })
  conflictContext!: string;

  /**
   * Function to determine which entities to display (allows parent control)
   *
   * This function prop allows parent components to customize which entities
   * are displayed in the grid, enabling advanced filtering and sorting.
   * Note: Infinite scroll is disabled when this prop is provided.
   *
   * @param entities - The full array of entities (Contact[] or PlanData[])
   * @param entityType - The type of entities being displayed ("people" or "projects")
   * @returns Filtered/sorted array of entities to display
   *
   * @example
   * // Custom filtering: only show contacts with profile images
   * :display-entities-function="(entities, type) =>
   *   entities.filter(e => e.profileImageUrl)"
   *
   * @example
   * // Custom sorting: sort projects by name
   * :display-entities-function="(entities, type) =>
   *   entities.sort((a, b) => a.name.localeCompare(b.name))"
   */
  @Prop({ default: null })
  displayEntitiesFunction?: (
    entities: Contact[] | PlanData[],
    entityType: "people" | "projects",
  ) => Contact[] | PlanData[];

  /**
   * CSS classes for the empty state message
   */
  get emptyStateClasses(): string {
    return "text-xs text-slate-500 italic col-span-full";
  }

  /**
   * Check if there are no entities to display
   */
  get hasNoEntities(): boolean {
    if (this.entityType === "projects") {
      // For projects: check internal state if no entities prop, otherwise check prop
      const projectsToCheck = this.entities || this.allProjects;
      return projectsToCheck.length === 0;
    } else {
      // For people: entities prop is required
      return !this.entities || this.entities.length === 0;
    }
  }

  /**
   * Get the entities array to use (prop or internal state)
   */
  get entitiesToUse(): Contact[] | PlanData[] {
    if (this.entityType === "projects") {
      // For projects: use prop if provided, otherwise use internal state
      return this.entities || this.allProjects;
    } else {
      // For people: entities prop is required
      return this.entities || [];
    }
  }

  /**
   * Computed entities to display - uses function prop if provided, otherwise uses infinite scroll
   * When searching, returns filtered results with infinite scroll applied
   */
  get displayedEntities(): Contact[] | PlanData[] {
    // If searching, return filtered results with infinite scroll
    if (this.searchTerm.trim()) {
      return this.filteredEntities.slice(0, this.displayedCount);
    }

    // If custom function provided, use it (disables infinite scroll)
    if (this.displayEntitiesFunction) {
      return this.displayEntitiesFunction(this.entitiesToUse, this.entityType);
    }

    // Default: projects use infinite scroll
    if (this.entityType === "projects") {
      return (this.entitiesToUse as PlanData[]).slice(0, this.displayedCount);
    }

    // People: handled by recentContacts + alphabeticalContacts (both use displayedCount)
    return [];
  }

  /**
   * Get the most recently added contacts (when showing contacts and not searching)
   *
   * NOTE: This assumes entities are already sorted by date added (newest first).
   * See the entities prop documentation for details on using $contactsByDateAdded().
   */
  get recentContacts(): Contact[] {
    if (
      this.entityType !== "people" ||
      this.searchTerm.trim() ||
      !this.entities
    ) {
      return [];
    }
    // Entities are already sorted by date added (newest first)
    return (this.entities as Contact[]).slice(0, RECENT_CONTACTS_COUNT);
  }

  /**
   * Get all contacts sorted alphabetically (when showing contacts and not searching)
   * Includes contacts shown in "Recently Added" section as well
   * Uses infinite scroll to control how many are displayed
   */
  get alphabeticalContacts(): Contact[] {
    if (
      this.entityType !== "people" ||
      this.searchTerm.trim() ||
      !this.entities
    ) {
      return [];
    }
    // Sort all contacts alphabetically (including recent ones)
    // Create a copy to avoid mutating the original array
    const sorted = [...(this.entities as Contact[])].sort(
      (a: Contact, b: Contact) => {
        // Sort alphabetically by name, falling back to DID if name is missing
        const nameA = (a.name || a.did).toLowerCase();
        const nameB = (b.name || b.did).toLowerCase();
        return nameA.localeCompare(nameB);
      },
    );
    // Apply infinite scroll: show based on displayedCount
    return sorted.slice(0, this.displayedCount);
  }

  /**
   * Get the 3 most recently bookmarked projects (when showing projects and not searching)
   * The starredPlanHandleIds array order represents bookmark order (newest at the end)
   */
  get recentBookmarkedProjects(): PlanData[] {
    if (
      this.entityType !== "projects" ||
      this.searchTerm.trim() ||
      this.starredPlanHandleIds.length === 0
    ) {
      return [];
    }

    const projects = this.entitiesToUse as PlanData[];
    if (projects.length === 0) {
      return [];
    }

    // Get the last 3 starred IDs (most recently bookmarked)
    const recentStarredIds = this.starredPlanHandleIds.slice(
      -RECENT_BOOKMARKED_PROJECTS_COUNT,
    );

    // Find projects matching those IDs, sorting with newest first
    const recentProjects = recentStarredIds
      .map((id) => projects.find((p) => p.handleId === id))
      .filter((p): p is PlanData => p !== undefined)
      .reverse();

    return recentProjects;
  }

  /**
   * Get all projects (when showing projects and not searching)
   * Includes projects shown in "Recently Bookmarked" section as well
   * Uses infinite scroll to control how many are displayed
   */
  get remainingProjects(): PlanData[] {
    if (this.entityType !== "projects" || this.searchTerm.trim()) {
      return [];
    }

    const projects = this.entitiesToUse as PlanData[];
    if (projects.length === 0) {
      return [];
    }

    // Apply infinite scroll: show based on displayedCount
    return projects.slice(0, this.displayedCount);
  }

  /**
   * Computed empty state message based on entity type
   */
  get emptyStateMessage(): string {
    if (this.entityType === "projects") {
      return "(No projects found.)";
    } else {
      return "(Add friends to see more people worthy of recognition.)";
    }
  }

  /**
   * Whether the "You" entity is conflicted
   */
  get youConflicted(): boolean {
    return this.conflictChecker(this.activeDid);
  }

  /**
   * Entity data for the "You" special entity
   */
  get youEntityData(): { did: string; name: string } {
    return {
      did: this.activeDid,
      name: "You",
    };
  }

  /**
   * Entity data for the "Unnamed" special entity
   */
  get unnamedEntityData(): { did: string; name: string } {
    return {
      did: "",
      name: UNNAMED_ENTITY_NAME,
    };
  }

  /**
   * Get the unnamed entity name constant
   */
  get unnamedEntityName(): string {
    return UNNAMED_ENTITY_NAME;
  }

  /**
   * Check if a person DID is conflicted
   */
  isPersonConflicted(did: string): boolean {
    return this.conflictChecker(did);
  }

  /**
   * Check if a project handleId is conflicted
   */
  isProjectConflicted(handleId: string): boolean {
    return this.conflictChecker(handleId);
  }

  /**
   * Handle person selection from PersonCard
   */
  handlePersonSelected(person: Contact): void {
    this.emitEntitySelected({
      type: "person",
      data: person,
    });
  }

  /**
   * Handle project selection from ProjectCard
   */
  handleProjectSelected(project: PlanData): void {
    this.emitEntitySelected({
      type: "project",
      data: project,
    });
  }

  /**
   * Handle special entity selection from SpecialEntityCard
   * Treat "You" and "Unnamed" as person entities
   */
  handleEntitySelected(event: { data: { did?: string; name: string } }): void {
    // Convert special entities to person entities since they represent people
    this.emitEntitySelected({
      type: "person",
      data: event.data as Contact,
    });
  }

  /**
   * Handle search input with debouncing
   */
  handleSearchInput(): void {
    // Show spinner immediately when user types
    this.isSearching = true;

    // Clear existing timeout
    if (this.searchTimeout) {
      clearTimeout(this.searchTimeout);
    }

    // Set new timeout for 500ms delay
    this.searchTimeout = setTimeout(() => {
      this.performSearch();
    }, 500);
  }

  /**
   * Perform the actual search
   * Routes to server-side search for projects or client-side filtering for contacts
   */
  async performSearch(): Promise<void> {
    if (!this.searchTerm.trim()) {
      this.filteredEntities = [];
      this.displayedCount = INITIAL_BATCH_SIZE;
      this.searchBeforeId = undefined;
      this.infiniteScrollReset?.();
      return;
    }

    this.isSearching = true;
    this.searchBeforeId = undefined; // Reset pagination for new search

    try {
      if (this.entityType === "projects") {
        // Server-side search for projects (initial load, no beforeId)
        const searchLower = this.searchTerm.toLowerCase().trim();
        await this.fetchProjects(undefined, searchLower);
      } else {
        // Client-side filtering for contacts (complete list)
        await this.performContactSearch();
      }

      // Reset displayed count when search completes
      this.displayedCount = INITIAL_BATCH_SIZE;
      this.infiniteScrollReset?.();
    } finally {
      this.isSearching = false;
    }
  }

  /**
   * Fetch projects from API server
   * Unified method for both loading all projects and searching projects.
   * If claimContents is provided, performs search and updates filteredEntities.
   * If claimContents is not provided, loads all projects and updates allProjects.
   *
   * @param beforeId - Optional rowId for pagination (loads projects before this ID)
   * @param claimContents - Optional search term (if provided, performs search; if not, loads all)
   */
  async fetchProjects(
    beforeId?: string,
    claimContents?: string,
  ): Promise<void> {
    if (!this.apiServer) {
      if (claimContents) {
        this.filteredEntities = [];
      } else {
        this.allProjects = [];
      }
      if (this.notify) {
        this.notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "API server not configured",
          },
          TIMEOUTS.SHORT,
        );
      }
      return;
    }

    const isSearch = !!claimContents;
    let url = `${this.apiServer}/api/v2/report/plans`;

    // Build query parameters
    const params: string[] = [];
    if (claimContents) {
      params.push(
        `claimContents=${encodeURIComponent(claimContents.toLowerCase().trim())}`,
      );
    }
    if (beforeId) {
      params.push(`beforeId=${encodeURIComponent(beforeId)}`);
    }
    if (params.length > 0) {
      url += `?${params.join("&")}`;
    }

    try {
      const response = await fetch(url, {
        method: "GET",
        headers: await getHeaders(this.activeDid),
      });

      if (response.status !== 200) {
        throw new Error(
          isSearch ? "Failed to search projects" : "Failed to load projects",
        );
      }

      const results = await response.json();
      if (results.data) {
        const newProjects = results.data.map(
          (plan: PlanData & { rowId?: string }) => ({
            ...plan,
            rowId: plan.rowId,
          }),
        );

        if (isSearch) {
          // Search mode: update filteredEntities
          if (beforeId) {
            // Pagination: append new projects to existing search results
            this.filteredEntities.push(...newProjects);
          } else {
            // Initial search: replace array
            this.filteredEntities = newProjects;
          }

          // Update searchBeforeId for next pagination
          if (newProjects.length > 0) {
            const lastProject = newProjects[newProjects.length - 1];
            this.searchBeforeId = lastProject.rowId || undefined;
          } else {
            this.searchBeforeId = undefined; // No more results
          }
        } else {
          // Load mode: update allProjects
          if (beforeId) {
            // Pagination: append new projects
            this.allProjects.push(...newProjects);
          } else {
            // Initial load: replace array
            this.allProjects = newProjects;
          }

          // Update loadBeforeId for next pagination
          if (newProjects.length > 0) {
            const lastProject = newProjects[newProjects.length - 1];
            this.loadBeforeId = lastProject.rowId || undefined;
          } else {
            this.loadBeforeId = undefined; // No more results
          }
        }
      } else {
        // No data in response
        if (isSearch) {
          if (!beforeId) {
            // Only clear on initial search, not pagination
            this.filteredEntities = [];
          }
          this.searchBeforeId = undefined;
        } else {
          if (!beforeId) {
            // Only clear on initial load, not pagination
            this.allProjects = [];
          }
          this.loadBeforeId = undefined;
        }
      }
    } catch (error) {
      logger.error(
        `Error ${isSearch ? "searching" : "loading"} projects:`,
        error,
      );
      if (isSearch) {
        if (!beforeId) {
          // Only clear on initial search error, not pagination error
          this.filteredEntities = [];
        }
        this.searchBeforeId = undefined;
      } else {
        if (!beforeId) {
          // Only clear on initial load error, not pagination error
          this.allProjects = [];
        }
        this.loadBeforeId = undefined;
      }
      if (this.notify) {
        this.notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: isSearch
              ? "Failed to search projects. Please try again."
              : "Failed to load projects. Please try again.",
          },
          TIMEOUTS.STANDARD,
        );
      }
    }
  }

  /**
   * Client-side contact search
   * Assumes entities prop contains complete contact list from local database
   */
  async performContactSearch(): Promise<void> {
    if (!this.entities) {
      this.filteredEntities = [];
      return;
    }

    // Simulate async (for consistency with project search)
    await new Promise((resolve) => setTimeout(resolve, 100));

    const searchLower = this.searchTerm.toLowerCase().trim();

    this.filteredEntities = (this.entities as Contact[])
      .filter((contact: Contact) => {
        const name = contact.name?.toLowerCase() || "";
        const did = contact.did.toLowerCase();
        return name.includes(searchLower) || did.includes(searchLower);
      })
      .sort((a: Contact, b: Contact) => {
        // Sort alphabetically by name, falling back to DID if name is missing
        const nameA = (a.name || a.did).toLowerCase();
        const nameB = (b.name || b.did).toLowerCase();
        return nameA.localeCompare(nameB);
      });

    // Contacts don't need pagination (complete list)
    this.searchBeforeId = undefined;
  }

  /**
   * Clear the search
   */
  clearSearch(): void {
    this.searchTerm = "";
    this.filteredEntities = [];
    this.isSearching = false;
    this.displayedCount = INITIAL_BATCH_SIZE;
    this.searchBeforeId = undefined;
    this.infiniteScrollReset?.();

    // Clear any pending timeout
    if (this.searchTimeout) {
      clearTimeout(this.searchTimeout);
      this.searchTimeout = null;
    }
  }

  /**
   * Determine if more entities can be loaded
   */
  canLoadMore(): boolean {
    if (this.displayEntitiesFunction) {
      // Custom function disables infinite scroll
      return false;
    }

    if (this.searchTerm.trim()) {
      // Search mode: check if more results available
      if (this.entityType === "projects") {
        // Projects: can load more if:
        // 1. We have more already-loaded results to show, OR
        // 2. We've shown all loaded results AND there's a searchBeforeId to load more
        const hasMoreLoaded =
          this.displayedCount < this.filteredEntities.length;
        const canLoadMoreFromServer =
          this.displayedCount >= this.filteredEntities.length &&
          !!this.searchBeforeId &&
          !this.isLoadingSearchMore;
        return hasMoreLoaded || canLoadMoreFromServer;
      } else {
        // Contacts: client-side filtering returns all results at once
        return this.displayedCount < this.filteredEntities.length;
      }
    }

    // Non-search mode
    if (this.entityType === "projects") {
      // Projects: check internal state or prop
      const projectsToCheck = this.entities || this.allProjects;
      const beforeId = this.entities ? undefined : this.loadBeforeId;

      // Can load more if:
      // 1. We have more already-loaded results to show, OR
      // 2. We've shown all loaded results AND there's a beforeId to load more (and not using entities prop)
      const hasMoreLoaded = this.displayedCount < projectsToCheck.length;
      const canLoadMoreFromServer =
        !this.entities &&
        this.displayedCount >= projectsToCheck.length &&
        !!beforeId &&
        !this.isLoadingProjects;

      return hasMoreLoaded || canLoadMoreFromServer;
    }

    // People: check if more alphabetical contacts available
    // All contacts are shown alphabetically (recent ones appear in both sections)
    if (!this.entities) {
      return false;
    }
    return this.displayedCount < this.entities.length;
  }

  /**
   * Initialize infinite scroll on mount
   */
  async mounted(): Promise<void> {
    // Load apiServer for project searches/loads
    if (this.entityType === "projects") {
      const settings = await this.$accountSettings();
      this.apiServer = settings.apiServer || "";

      // Load starred project IDs for showing recently bookmarked projects
      this.starredPlanHandleIds = settings.starredPlanHandleIds || [];

      // Load projects on mount if entities prop not provided
      if (!this.entities && this.apiServer) {
        this.isLoadingProjects = true;
        try {
          await this.fetchProjects();
        } catch (error) {
          logger.error("Error loading projects on mount:", error);
        } finally {
          this.isLoadingProjects = false;
        }
      }
    }

    // Validate entities prop for people
    if (this.entityType === "people" && !this.entities) {
      logger.error(
        "EntityGrid: entities prop is required when entityType is 'people'",
      );
      if (this.notify) {
        this.notify(
          {
            group: "alert",
            type: "danger",
            title: "Error",
            text: "Contacts data is required but not provided.",
          },
          TIMEOUTS.SHORT,
        );
      }
    }

    this.$nextTick(() => {
      const container = this.$refs.scrollContainer as HTMLElement;

      if (container) {
        const { reset } = useInfiniteScroll(
          container,
          async () => {
            // Search mode: handle search pagination
            if (this.searchTerm.trim()) {
              if (this.entityType === "projects") {
                // Projects: load more search results if available
                if (
                  this.displayedCount >= this.filteredEntities.length &&
                  this.searchBeforeId &&
                  !this.isLoadingSearchMore
                ) {
                  this.isLoadingSearchMore = true;
                  try {
                    const searchLower = this.searchTerm.toLowerCase().trim();
                    await this.fetchProjects(this.searchBeforeId, searchLower);
                    // After loading more, reset scroll state to allow further loading
                    this.infiniteScrollReset?.();
                  } catch (error) {
                    logger.error("Error loading more search results:", error);
                    // Error already handled in fetchProjects
                  } finally {
                    this.isLoadingSearchMore = false;
                  }
                } else {
                  // Show more from already-loaded search results
                  this.displayedCount += INCREMENT_SIZE;
                }
              } else {
                // Contacts: show more from already-filtered results
                this.displayedCount += INCREMENT_SIZE;
              }
            } else {
              // Non-search mode
              if (this.entityType === "projects") {
                const projectsToCheck = this.entities || this.allProjects;
                const beforeId = this.entities ? undefined : this.loadBeforeId;

                // If using internal state and need to load more from server
                if (
                  !this.entities &&
                  this.displayedCount >= projectsToCheck.length &&
                  beforeId &&
                  !this.isLoadingProjects
                ) {
                  this.isLoadingProjects = true;
                  try {
                    await this.fetchProjects(beforeId);
                    // After loading more, reset scroll state to allow further loading
                    this.infiniteScrollReset?.();
                  } catch (error) {
                    logger.error("Error loading more projects:", error);
                    // Error already handled in fetchProjects
                  } finally {
                    this.isLoadingProjects = false;
                  }
                } else {
                  // Normal case: increment displayedCount to show more from memory
                  this.displayedCount += INCREMENT_SIZE;
                }
              } else {
                // People: increment displayedCount to show more from memory
                this.displayedCount += INCREMENT_SIZE;
              }
            }
          },
          {
            distance: 50, // pixels from bottom
            canLoadMore: () => this.canLoadMore(),
          },
        );
        this.infiniteScrollReset = reset;
      }
    });
  }

  // Emit methods using @Emit decorator

  @Emit("entity-selected")
  emitEntitySelected(data: {
    type: "person" | "project";
    data: Contact | PlanData;
  }): {
    type: "person" | "project";
    data: Contact | PlanData;
  } {
    return data;
  }

  /**
   * Watch for changes in entityType to load projects when switching to projects
   */
  @Watch("entityType")
  async onEntityTypeChange(newType: "people" | "projects"): Promise<void> {
    // Reset displayed count and clear search when switching types
    this.displayedCount = INITIAL_BATCH_SIZE;
    this.searchTerm = "";
    this.filteredEntities = [];
    this.searchBeforeId = undefined;
    this.infiniteScrollReset?.();

    // When switching to projects, load them if not provided via entities prop
    if (newType === "projects" && !this.entities) {
      // Ensure apiServer is loaded
      if (!this.apiServer) {
        const settings = await this.$accountSettings();
        this.apiServer = settings.apiServer || "";
        this.starredPlanHandleIds = settings.starredPlanHandleIds || [];
      }

      // Load projects if we have an API server
      if (this.apiServer && this.allProjects.length === 0) {
        this.isLoadingProjects = true;
        try {
          await this.fetchProjects();
        } catch (error) {
          logger.error(
            "Error loading projects when switching to projects:",
            error,
          );
        } finally {
          this.isLoadingProjects = false;
        }
      }
    }

    // Clear project state when switching away from projects
    if (newType === "people") {
      this.allProjects = [];
      this.loadBeforeId = undefined;
    }
  }

  /**
   * Watch for changes in search term to reset displayed count and pagination
   */
  @Watch("searchTerm")
  onSearchTermChange(): void {
    // Reset displayed count and pagination when search term changes
    this.displayedCount = INITIAL_BATCH_SIZE;
    this.searchBeforeId = undefined;
    this.infiniteScrollReset?.();
  }

  /**
   * Watch for changes in entities prop to clear search and reset displayed count
   */
  @Watch("entities")
  onEntitiesChange(): void {
    // Clear search when entities change (fresh dialog open)
    if (this.searchTerm) {
      this.searchTerm = "";
      this.filteredEntities = [];
      this.searchBeforeId = undefined;
    }
    this.displayedCount = INITIAL_BATCH_SIZE;
    this.infiniteScrollReset?.();

    // For projects: clear internal state if entities prop is provided
    if (this.entityType === "projects" && this.entities) {
      this.allProjects = [];
      this.loadBeforeId = undefined;
    }
  }

  /**
   * Cleanup timeouts when component is destroyed
   */
  beforeUnmount(): void {
    if (this.searchTimeout) {
      clearTimeout(this.searchTimeout);
    }
  }
}
</script>

<style scoped>
/* Grid-specific styles if needed */
</style>
