<ng-template
  #serverCard
  let-server
>
  <div
    class="group w-full cursor-pointer rounded-lg border bg-card p-3 text-left transition-colors"
    [class.border-border]="!isServerMarkedBanned(server)"
    [class.hover:border-primary/50]="!isServerMarkedBanned(server)"
    [class.hover:bg-card/80]="!isServerMarkedBanned(server)"
    [class.border-destructive/40]="isServerMarkedBanned(server)"
    [class.bg-destructive/5]="isServerMarkedBanned(server)"
    [class.hover:border-destructive/60]="isServerMarkedBanned(server)"
    [title]="serverCardTitle(server)"
    (dblclick)="openServerCard(server)"
  >
    <div class="flex min-w-0 items-start gap-3">
      <div class="grid h-10 w-10 shrink-0 place-items-center overflow-hidden rounded-lg bg-secondary text-sm font-semibold text-foreground">
        @if (server.icon) {
          <div
            aria-hidden="true"
            class="h-full w-full bg-cover bg-center bg-no-repeat"
            [style.backgroundImage]="'url(' + server.icon + ')'"
          ></div>
        } @else {
          {{ server.name[0] || '?' }}
        }
      </div>

      <div class="min-w-0 flex-1">
        <div class="flex min-w-0 flex-wrap items-center gap-2">
          <h3
            class="truncate text-sm font-semibold transition-colors"
            [class.text-foreground]="!isServerMarkedBanned(server)"
            [class.group-hover:text-primary]="!isServerMarkedBanned(server)"
            [class.text-destructive]="isServerMarkedBanned(server)"
          >
            {{ server.name }}
          </h3>

          @if (isServerMarkedBanned(server)) {
            <span class="inline-flex items-center gap-1 rounded-full bg-destructive/10 px-2 py-0.5 text-[11px] font-medium text-destructive">
              <ng-icon
                name="lucideLock"
                class="h-3 w-3"
              />
              {{ 'servers.browser.card.banned' | translate }}
            </span>
          } @else if (server.isPrivate) {
            <span class="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
              <ng-icon
                name="lucideLock"
                class="h-3 w-3"
              />
              {{ 'servers.browser.card.private' | translate }}
            </span>
          } @else if (server.hasPassword) {
            <span class="inline-flex items-center gap-1 rounded-full bg-secondary px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
              <ng-icon
                name="lucideLock"
                class="h-3 w-3"
              />
              {{ 'servers.browser.card.password' | translate }}
            </span>
          } @else {
            <ng-icon
              name="lucideGlobe"
              class="h-4 w-4 text-muted-foreground"
            />
          }
        </div>

        @if (server.description) {
          <p class="mt-1 line-clamp-1 text-xs text-muted-foreground">{{ server.description }}</p>
        }

        <div class="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground">
          <span class="inline-flex items-center gap-1">
            <ng-icon
              name="lucideUsers"
              class="h-3.5 w-3.5"
            />
            {{ getServerUserCount(server) }}/{{ getServerCapacityLabel(server) }}
          </span>
          @if (server.topic) {
            <span class="truncate">{{ server.topic }}</span>
          }
          <span class="truncate">{{ ownerLabel(server) }}</span>
          <span class="truncate">{{ server.sourceName || server.hostName || ('common.labels.unknown' | translate) }}</span>
        </div>
      </div>

      <div class="relative shrink-0">
        @if (isJoinedServer(server)) {
          <div
            class="flex items-center overflow-hidden rounded-md border border-emerald-500/30 bg-emerald-500/10 text-xs font-semibold text-emerald-500"
          >
            <span class="px-2.5 py-1.5">{{ 'servers.browser.card.joined' | translate }}</span>
            <button
              type="button"
              class="grid h-8 w-8 place-items-center border-l border-emerald-500/20 transition-colors hover:bg-emerald-500/15"
              [attr.aria-label]="serverActionsLabel(server)"
              (click)="toggleJoinedServerMenu($event, server)"
            >
              <ng-icon
                name="lucideChevronDown"
                class="h-4 w-4"
              />
            </button>
          </div>

          @if (joinedServerMenuId() === server.id) {
            <div class="absolute right-0 top-full z-20 mt-1 w-36 rounded-md border border-border bg-card py-1 shadow-lg">
              <button
                type="button"
                class="w-full px-3 py-2 text-left text-xs font-medium text-destructive transition-colors hover:bg-destructive/10"
                (click)="openLeaveDialog($event, server)"
              >
                {{ 'servers.browser.card.leave' | translate }}
              </button>
            </div>
          }
        } @else {
          <button
            type="button"
            class="rounded-md bg-primary px-2.5 py-1.5 text-xs font-semibold text-primary-foreground transition-colors hover:bg-primary/90"
            [attr.aria-label]="joinServerLabel(server)"
            (click)="joinServer(server)"
          >
            <span class="sr-only">{{ server.name }}</span>
            {{ 'servers.browser.card.join' | translate }}
          </button>
        }
      </div>
    </div>
  </div>
</ng-template>

<div class="flex h-full min-h-0 flex-col">
  <div class="border-b border-border px-3 py-3">
    <div class="relative">
      <ng-icon
        name="lucideSearch"
        class="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground"
      />
      <input
        type="text"
        appAutoFocus
        appSelectOnFocus
        [attr.aria-label]="'servers.browser.search.ariaLabel' | translate"
        class="h-10 w-full rounded-lg border border-border bg-secondary py-2 pl-10 pr-3 text-sm text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
        [placeholder]="resolvedSearchPlaceholder"
        [(ngModel)]="searchQuery"
        (ngModelChange)="onSearchChange($event)"
      />
    </div>

    @if (showMyServers && savedRooms().length > 0) {
      <div class="mt-2 flex items-center gap-2 overflow-x-auto pb-1">
        <span class="shrink-0 text-xs font-medium text-muted-foreground">{{ 'servers.browser.search.myServers' | translate }}</span>
        @for (room of savedRooms(); track room.id) {
          <button
            type="button"
            class="shrink-0 rounded-md border border-border bg-card px-2.5 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-secondary"
            (click)="joinSavedRoom(room)"
          >
            {{ room.name }}
          </button>
        }
      </div>
    }
  </div>

  <div class="min-h-0 flex-1 overflow-y-auto">
    @if (isSearchMode) {
      <div class="sticky top-0 z-10 flex items-center justify-between border-b border-border bg-background/95 px-3 py-2 backdrop-blur">
        <div>
          <h3 class="text-sm font-semibold text-foreground">{{ 'servers.browser.search.resultsTitle' | translate }}</h3>
          <p class="text-xs text-muted-foreground">{{ 'servers.browser.search.resultsCount' | translate: { count: searchResults().length } }}</p>
        </div>
      </div>

      @if (isSearching()) {
        <div class="flex items-center justify-center py-8">
          <div class="h-8 w-8 animate-spin rounded-full border-b-2 border-primary"></div>
        </div>
      } @else if (searchResults().length === 0) {
        <div class="flex flex-col items-center justify-center px-4 py-10 text-muted-foreground">
          <ng-icon
            name="lucideSearch"
            class="mb-3 h-10 w-10 opacity-50"
          />
          <p class="text-sm font-medium">{{ 'servers.browser.search.noResults' | translate }}</p>
        </div>
      } @else {
        <div class="space-y-2 p-3">
          @for (server of searchResults(); track server.id) {
            <ng-container *ngTemplateOutlet="serverCard; context: { $implicit: server }" />
          }
        </div>
      }
    } @else if (showEmptyState) {
      <div class="flex flex-col items-center justify-center px-6 py-16 text-center text-muted-foreground">
        <ng-icon
          name="lucideSearch"
          class="mb-4 h-12 w-12 opacity-40"
        />
        <p class="text-base font-semibold text-foreground">{{ resolvedEmptyStateTitle }}</p>
        <p class="mt-1 max-w-sm text-sm">{{ resolvedEmptyStateMessage }}</p>
      </div>
    } @else {
      <div class="space-y-6 p-3">
        @for (section of visibleSections; track section.id) {
          <section>
            <div class="mb-2 px-1">
              <h3 class="text-sm font-semibold text-foreground">{{ section.title }}</h3>
              @if (section.subtitle) {
                <p class="text-xs text-muted-foreground">{{ section.subtitle }}</p>
              }
            </div>
            <div class="space-y-2">
              @for (server of section.servers; track server.id) {
                <ng-container *ngTemplateOutlet="serverCard; context: { $implicit: server }" />
              }
            </div>
          </section>
        }
      </div>
    }
  </div>

  @if (joinErrorMessage() || error()) {
    <div class="border-t border-destructive bg-destructive/10 p-4">
      <p class="text-sm text-destructive">{{ joinErrorMessage() || error() }}</p>
    </div>
  }
</div>

@if (leaveDialogRoom()) {
  <app-leave-server-dialog
    [room]="leaveDialogRoom()!"
    [currentUser]="currentUser() ?? null"
    (confirmed)="confirmLeaveServer($event)"
    (cancelled)="closeLeaveDialog()"
  />
}

@if (showBannedDialog()) {
  <app-confirm-dialog
    [title]="'servers.browser.bannedDialog.title' | translate"
    [confirmLabel]="'common.actions.ok' | translate"
    [cancelLabel]="'common.actions.close' | translate"
    variant="danger"
    [widthClass]="'w-96 max-w-[90vw]'"
    (confirmed)="closeBannedDialog()"
    (cancelled)="closeBannedDialog()"
  >
    <p>{{ bannedDialogMessage() }}</p>
  </app-confirm-dialog>
}

@if (showPasswordDialog() && passwordPromptServer()) {
  <app-confirm-dialog
    [title]="'servers.browser.passwordDialog.title' | translate"
    [confirmLabel]="'servers.browser.passwordDialog.confirm' | translate"
    [cancelLabel]="'common.actions.cancel' | translate"
    [widthClass]="'w-[420px] max-w-[92vw]'"
    (confirmed)="confirmPasswordJoin()"
    (cancelled)="closePasswordDialog()"
  >
    <div class="space-y-3">
      <p>{{ passwordDialogMessage(passwordPromptServer()!) }}</p>

      <div>
        <label
          for="join-server-password"
          class="mb-1 block text-xs font-medium uppercase tracking-wide text-muted-foreground"
        >
          {{ 'servers.browser.passwordDialog.label' | translate }}
        </label>
        <input
          id="join-server-password"
          type="password"
          [(ngModel)]="joinPassword"
          [placeholder]="'servers.browser.passwordDialog.placeholder' | translate"
          class="w-full rounded-lg border border-border bg-secondary px-3 py-2 text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary"
        />
      </div>

      @if (joinPasswordError()) {
        <p class="text-sm text-destructive">{{ joinPasswordError() }}</p>
      }
    </div>
  </app-confirm-dialog>
}

@if (pluginConsentDialog(); as dialog) {
  <app-modal-backdrop
    [zIndex]="50"
    [dismissable]="false"
  />
  <section
    class="fixed left-1/2 top-1/2 z-[51] flex max-h-[min(42rem,calc(100vh-2rem))] w-[min(34rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border border-border bg-card text-foreground shadow-2xl"
    role="dialog"
    aria-modal="true"
    aria-labelledby="join-plugin-consent-title"
  >
    <header class="border-b border-border p-4">
      <p class="text-sm text-muted-foreground">{{ 'servers.plugins.eyebrow' | translate }}</p>
      <h2
        id="join-plugin-consent-title"
        class="mt-1 text-lg font-semibold"
      >
        {{ pluginUsesPluginsLabel(dialog.server.name) }}
      </h2>
    </header>

    <div class="grid min-h-0 gap-4 overflow-auto p-4">
      @if (dialog.required.length > 0) {
        <section class="grid gap-2">
          <h3 class="text-sm font-semibold">{{ 'servers.plugins.requiredTitle' | translate }}</h3>
          @for (requirement of dialog.required; track requirement.pluginId) {
            <div class="grid gap-3 rounded-lg border border-border bg-background/50 px-3 py-2">
              <div class="flex items-start justify-between gap-3">
                <div class="min-w-0">
                  <p class="truncate text-sm font-semibold">{{ requirement.manifest?.title || requirement.pluginId }}</p>
                  @if (requirement.reason) {
                    <p class="mt-1 text-xs text-muted-foreground">{{ requirement.reason }}</p>
                  }
                </div>
                <span class="shrink-0 rounded-full bg-primary/10 px-2 py-0.5 text-xs font-semibold text-primary">{{
                  'servers.plugins.requiredBadge' | translate
                }}</span>
              </div>

              @if (requirement.manifest?.capabilities; as capabilities) {
                <details class="rounded-md border border-border bg-secondary/40 px-2 py-1.5 text-xs text-muted-foreground">
                  <summary class="cursor-pointer font-semibold text-foreground">{{ 'servers.plugins.capabilities' | translate }}</summary>
                  <div class="mt-2 flex flex-wrap gap-1.5">
                    @for (capability of capabilities; track capability) {
                      <span class="rounded-full bg-background px-2 py-0.5 font-mono text-[11px] text-muted-foreground">{{ capability }}</span>
                    }
                  </div>
                </details>
              }

              <div class="flex flex-wrap items-center gap-2">
                @if (getPluginSourceUrl(requirement)) {
                  <button
                    type="button"
                    (click)="openPluginSource(requirement)"
                    class="inline-flex min-h-8 items-center justify-center gap-2 rounded-lg border border-border bg-card px-3 py-1.5 text-xs font-semibold transition-colors hover:bg-secondary"
                  >
                    <ng-icon
                      name="lucideExternalLink"
                      class="h-3.5 w-3.5"
                    />
                    {{ 'servers.plugins.source' | translate }}
                  </button>
                }

                @if (hasPluginReadme(requirement)) {
                  <button
                    type="button"
                    (click)="openPluginConsentReadme(requirement)"
                    [disabled]="pluginConsentReadmeLoadingId() === requirement.pluginId"
                    class="inline-flex min-h-8 items-center justify-center gap-2 rounded-lg border border-border bg-card px-3 py-1.5 text-xs font-semibold transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-55"
                  >
                    <ng-icon
                      name="lucideFileText"
                      class="h-3.5 w-3.5"
                    />
                    {{ pluginReadmeButtonLabel(requirement.pluginId) }}
                  </button>
                }
              </div>
            </div>
          }
        </section>
      }

      @if (dialog.optional.length > 0) {
        <section class="grid gap-2">
          <h3 class="text-sm font-semibold">{{ 'servers.plugins.optionalTitle' | translate }}</h3>
          @for (requirement of dialog.optional; track requirement.pluginId) {
            <div class="grid gap-3 rounded-lg border border-border bg-background/50 px-3 py-2">
              <label class="flex items-start gap-3">
                <input
                  type="checkbox"
                  class="mt-1 h-4 w-4 rounded border-border bg-secondary"
                  [checked]="selectedOptionalPluginIds().has(requirement.pluginId)"
                  [disabled]="pluginConsentBusy()"
                  (change)="toggleOptionalPluginInstall(requirement.pluginId, $any($event.target).checked)"
                />
                <span class="min-w-0 flex-1">
                  <span class="block truncate text-sm font-semibold">{{ requirement.manifest?.title || requirement.pluginId }}</span>
                  @if (requirement.reason) {
                    <span class="mt-1 block text-xs text-muted-foreground">{{ requirement.reason }}</span>
                  }
                </span>
              </label>

              @if (requirement.manifest?.capabilities; as capabilities) {
                <details class="rounded-md border border-border bg-secondary/40 px-2 py-1.5 text-xs text-muted-foreground">
                  <summary class="cursor-pointer font-semibold text-foreground">{{ 'servers.plugins.capabilities' | translate }}</summary>
                  <div class="mt-2 flex flex-wrap gap-1.5">
                    @for (capability of capabilities; track capability) {
                      <span class="rounded-full bg-background px-2 py-0.5 font-mono text-[11px] text-muted-foreground">{{ capability }}</span>
                    }
                  </div>
                </details>
              }

              <div class="flex flex-wrap items-center gap-2">
                @if (getPluginSourceUrl(requirement)) {
                  <button
                    type="button"
                    (click)="openPluginSource(requirement)"
                    class="inline-flex min-h-8 items-center justify-center gap-2 rounded-lg border border-border bg-card px-3 py-1.5 text-xs font-semibold transition-colors hover:bg-secondary"
                  >
                    <ng-icon
                      name="lucideExternalLink"
                      class="h-3.5 w-3.5"
                    />
                    {{ 'servers.plugins.source' | translate }}
                  </button>
                }

                @if (hasPluginReadme(requirement)) {
                  <button
                    type="button"
                    (click)="openPluginConsentReadme(requirement)"
                    [disabled]="pluginConsentReadmeLoadingId() === requirement.pluginId"
                    class="inline-flex min-h-8 items-center justify-center gap-2 rounded-lg border border-border bg-card px-3 py-1.5 text-xs font-semibold transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-55"
                  >
                    <ng-icon
                      name="lucideFileText"
                      class="h-3.5 w-3.5"
                    />
                    {{ pluginReadmeButtonLabel(requirement.pluginId) }}
                  </button>
                }
              </div>
            </div>
          }
        </section>
      }

      @if (pluginConsentReadmeError()) {
        <p class="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">{{ pluginConsentReadmeError() }}</p>
      }

      @if (pluginConsentError()) {
        <p class="rounded-lg border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">{{ pluginConsentError() }}</p>
      }
    </div>

    <footer class="flex justify-end gap-2 border-t border-border p-4">
      <button
        type="button"
        (click)="closePluginConsentDialog()"
        [disabled]="pluginConsentBusy()"
        class="inline-flex min-h-8 items-center justify-center rounded-lg border border-border bg-card px-3 py-1.5 text-sm font-semibold transition-colors hover:bg-secondary disabled:cursor-not-allowed disabled:opacity-55"
      >
        {{ 'servers.plugins.cancelJoin' | translate }}
      </button>
      <button
        type="button"
        (click)="confirmPluginConsent()"
        [disabled]="pluginConsentBusy()"
        class="inline-flex min-h-8 items-center justify-center rounded-lg border border-primary bg-primary px-3 py-1.5 text-sm font-semibold text-primary-foreground transition-colors hover:bg-primary/90 disabled:cursor-not-allowed disabled:opacity-55"
      >
        {{ pluginConsentConfirmLabel(dialog.required.length) }}
      </button>
    </footer>
  </section>

  @if (pluginConsentReadme(); as readme) {
    <app-modal-backdrop
      [zIndex]="52"
      [ariaLabel]="'servers.plugins.closeReadme' | translate"
      (dismissed)="closePluginConsentReadme()"
    />
    <section
      class="fixed left-1/2 top-1/2 z-[53] flex max-h-[min(44rem,calc(100vh-2rem))] w-[min(44rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 flex-col overflow-hidden rounded-lg border border-border bg-card text-foreground shadow-2xl"
      role="dialog"
      aria-modal="true"
      aria-labelledby="join-plugin-readme-title"
    >
      <header class="flex items-start justify-between gap-3 border-b border-border p-4">
        <div class="min-w-0">
          <p class="text-sm text-muted-foreground">{{ 'servers.plugins.readmeEyebrow' | translate }}</p>
          <h2
            id="join-plugin-readme-title"
            class="mt-1 truncate text-lg font-semibold"
          >
            {{ readme.title }}
          </h2>
        </div>
        <button
          type="button"
          (click)="closePluginConsentReadme()"
          class="grid h-8 w-8 shrink-0 place-items-center rounded-lg border border-border text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground"
          [title]="'common.actions.close' | translate"
        >
          X
        </button>
      </header>

      <div
        class="min-h-0 overflow-auto p-4 text-sm leading-6 [&_a]:text-primary [&_blockquote]:border-l-2 [&_blockquote]:border-border [&_blockquote]:pl-3 [&_code]:rounded [&_code]:bg-background [&_code]:px-1 [&_h1]:mb-2 [&_h1]:text-xl [&_h1]:font-semibold [&_h2]:mb-2 [&_h2]:mt-4 [&_h2]:text-lg [&_h2]:font-semibold [&_h3]:mb-1 [&_h3]:mt-3 [&_h3]:font-semibold [&_li]:ml-5 [&_ol]:list-decimal [&_p]:mb-3 [&_pre]:mb-3 [&_pre]:overflow-auto [&_pre]:rounded-lg [&_pre]:bg-background [&_pre]:p-3 [&_ul]:list-disc"
      >
        <app-chat-message-markdown [content]="readme.markdown" />
      </div>
    </section>
  }
}
