/** * 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"
      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 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"
        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
        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="entities.length === 0" :class="emptyStateClasses">
      {{ emptyStateMessage }}
    </li>

    <!-- Entity cards (people or projects) -->
    <template v-if="entityType === 'people'">
      <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 v-else-if="entityType === 'projects'">
      <ProjectCard
        v-for="project in displayedEntities as PlanData[]"
        :key="project.handleId"
        :project="project"
        :active-did="activeDid"
        :all-my-dids="allMyDids"
        :all-contacts="allContacts"
        :notify="notify"
        :conflict-context="conflictContext"
        @project-selected="handleProjectSelected"
      />
    </template>
  </ul>
</template>

<script lang="ts">
import { Component, Prop, Vue, Emit } from "vue-facing-decorator";
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";

/**
 * 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,
  },
})
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[] = [];

  /** Array of entities to display */
  @Prop({ required: true })
  entities!: Contact[] | PlanData[];

  /** Maximum number of entities to display */
  @Prop({ default: 10 })
  maxItems!: number;

  /** 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 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, sorting, and
   * display logic beyond the default simple slice behavior.
   *
   * @param entities - The full array of entities (Contact[] or PlanData[])
   * @param entityType - The type of entities being displayed ("people" or "projects")
   * @param maxItems - The maximum number of items to display (from maxItems prop)
   * @returns Filtered/sorted array of entities to display
   *
   * @example
   * // Custom filtering: only show contacts with profile images
   * :display-entities-function="(entities, type, max) =>
   *   entities.filter(e => e.profileImageUrl).slice(0, max)"
   *
   * @example
   * // Custom sorting: sort projects by name
   * :display-entities-function="(entities, type, max) =>
   *   entities.sort((a, b) => a.name.localeCompare(b.name)).slice(0, max)"
   *
   * @example
   * // Advanced logic: different limits for different entity types
   * :display-entities-function="(entities, type, max) =>
   *   type === 'projects' ? entities.slice(0, 5) : entities.slice(0, max)"
   */
  @Prop({ default: null })
  displayEntitiesFunction?: (
    entities: Contact[] | PlanData[],
    entityType: "people" | "projects",
    maxItems: number,
  ) => Contact[] | PlanData[];

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

  /**
   * Computed entities to display - uses function prop if provided, otherwise defaults
   * When searching, returns filtered results instead of original logic
   */
  get displayedEntities(): Contact[] | PlanData[] {
    // If searching, return filtered results
    if (this.searchTerm.trim()) {
      return this.filteredEntities;
    }

    // Original logic when not searching
    if (this.displayEntitiesFunction) {
      return this.displayEntitiesFunction(
        this.entities,
        this.entityType,
        this.maxItems,
      );
    }

    // Default implementation for backward compatibility
    const maxDisplay = this.entityType === "projects" ? 10 : this.maxItems;
    return this.entities.slice(0, maxDisplay);
  }

  /**
   * 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);
  }

  /**
   * 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
   */
  async performSearch(): Promise<void> {
    if (!this.searchTerm.trim()) {
      this.filteredEntities = [];
      return;
    }

    this.isSearching = true;

    try {
      // Simulate async search (in case we need to add API calls later)
      await new Promise((resolve) => setTimeout(resolve, 100));

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

      if (this.entityType === "people") {
        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);
          },
        );
      } else {
        this.filteredEntities = (this.entities as PlanData[]).filter(
          (project: PlanData) => {
            const name = project.name?.toLowerCase() || "";
            const handleId = project.handleId.toLowerCase();
            return name.includes(searchLower) || handleId.includes(searchLower);
          },
        );
      }
    } finally {
      this.isSearching = false;
    }
  }

  /**
   * Clear the search
   */
  clearSearch(): void {
    this.searchTerm = "";
    this.filteredEntities = [];
    this.isSearching = false;

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

  // Emit methods using @Emit decorator

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

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

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