import {
  Component,
  OnInit,
  ChangeDetectionStrategy,
  ChangeDetectorRef,
} from '@angular/core';
import { CommonModule } from '@angular/common';
import { Router, RouterLink, ActivatedRoute } from '@angular/router';
import { Subject, debounceTime, distinctUntilChanged, share, skip, timer } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ImageRecord, ImageService } from '../services/image.service';
import { TagService } from '../services/tag.service';

const PLACEHOLDER_SVG = `data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="200" height="160" viewBox="0 0 200 160"><rect width="200" height="160" fill="%23252525"/><text x="50%" y="50%" dominant-baseline="middle" text-anchor="middle" font-size="32" fill="%23555">&#x1F5BC;</text></svg>`;

@Component({
  selector: 'app-library',
  standalone: true,
  imports: [CommonModule, RouterLink],
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="library">
      <header>
        <h1><a class="home-link" routerLink="/" [queryParams]="{}">Reactbin</a></h1>
        <div class="header-actions">
          <a routerLink="/tags" class="tags-link">Browse tags</a>
          <button class="upload-btn" (click)="router.navigate(['/upload'])">Upload</button>
        </div>
      </header>

      <div class="filter-bar">
        <input
          placeholder="Filter by tag…"
          (input)="onTagInput($event)"
          [value]="tagSearch"
        />
        <div class="chips">
          <span *ngFor="let tag of activeFilters" class="chip">
            {{ tag }} <button (click)="removeFilter(tag)">×</button>
          </span>
        </div>
        <ul class="suggestions" *ngIf="suggestions.length">
          <li *ngFor="let s of suggestions" (click)="addFilter(s.name)" (keydown.enter)="addFilter(s.name)" tabindex="0" role="option" [attr.aria-selected]="false">{{ s.name }} ({{ s.image_count }})</li>
        </ul>
      </div>

      <!-- Skeleton loading grid -->
      <div *ngIf="showSpinner" class="grid">
        <div *ngFor="let _ of skeletonItems" class="image-card skeleton card-skeleton"></div>
      </div>

      <!-- Error state -->
      <div *ngIf="error && !showSpinner" class="error-card">
        <p>Failed to load images. Please check your connection.</p>
        <button class="retry-btn" (click)="load()">Retry</button>
      </div>

      <!-- Empty state -->
      <div *ngIf="images.length === 0 && !showSpinner && !error" class="empty-state">
        <span class="empty-icon">✦</span>
        <p *ngIf="activeFilters.length">No images match these filters.</p>
        <p *ngIf="!activeFilters.length">No images yet.</p>
        <a *ngIf="!activeFilters.length" routerLink="/upload" class="upload-link">Upload your first image</a>
      </div>

      <!-- Image grid -->
      <div *ngIf="!showSpinner && !error" class="grid">
        <div
          *ngFor="let img of images"
          class="image-card"
          role="button"
          tabindex="0"
          (click)="router.navigate(['/i', img.short_id])"
          (keydown.enter)="router.navigate(['/i', img.short_id])"
        >
          <img
            [src]="img.thumbnail_url ?? img.file_url"
            [alt]="img.filename"
            loading="lazy"
            (error)="onImgError($event)"
          />
          <div class="tag-row">
            <span *ngFor="let tag of img.tags" class="chip small">{{ tag }}</span>
          </div>
        </div>
      </div>

      <!-- Total count — always visible when images exist -->
      <p *ngIf="total > 0 && !showSpinner && !error" class="total-count">{{ total }} images</p>

      <!-- Pagination controls — only when more than one page -->
      <div *ngIf="totalPages > 1 && !showSpinner && !error" class="pagination-bar">
        <button class="pag-btn first-btn" [disabled]="currentPage === 1" (click)="firstPage()" aria-label="First page">«</button>
        <button class="pag-btn prev-btn" [disabled]="currentPage === 1" (click)="prevPage()" aria-label="Previous page">‹</button>
        <button
          *ngFor="let p of pageWindow"
          class="pag-btn page-btn"
          [class.active]="p === currentPage"
          (click)="goToPage(p)"
          [attr.aria-current]="p === currentPage ? 'page' : null"
        >{{ p }}</button>
        <button class="pag-btn next-btn" [disabled]="currentPage === totalPages" (click)="nextPage()" aria-label="Next page">›</button>
        <button class="pag-btn last-btn" [disabled]="currentPage === totalPages" (click)="lastPage()" aria-label="Last page">»</button>
      </div>
    </div>
  `,
  styles: [`
    .library { max-width: 1200px; margin: 0 auto; padding: 24px 16px; }
    header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
    .header-actions { display: flex; align-items: center; gap: 12px; }
    .tags-link { color: var(--text-muted); text-decoration: none; font-size: 0.9rem; transition: color var(--transition); }
    .tags-link:hover { color: var(--text); }
    .upload-btn { padding: 8px 20px; background: var(--accent); color: var(--accent-text); border: none; border-radius: var(--radius); cursor: pointer; font-weight: 600; }
    .filter-bar { position: relative; margin-bottom: 24px; }
    .filter-bar input { width: 100%; padding: 10px; background: var(--surface); border: 1px solid var(--border); color: var(--text); border-radius: var(--radius); }
    .filter-bar input:focus { outline: none; border-color: var(--border-focus); }
    .chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
    .chip { background: var(--surface-raised); padding: 3px 10px; border-radius: var(--radius-chip); font-size: 0.85rem; display: flex; align-items: center; gap: 4px; }
    .chip.small { font-size: 0.75rem; padding: 2px 8px; }
    .chip button { background: none; border: none; color: var(--text-muted); cursor: pointer; padding: 0; font-size: 1rem; }
    .suggestions { position: absolute; z-index: 10; background: var(--surface); border: 1px solid var(--border); list-style: none; width: 100%; max-height: 200px; overflow-y: auto; border-radius: 0 0 var(--radius) var(--radius); }
    .suggestions li { padding: 8px 12px; cursor: pointer; }
    .suggestions li:hover { background: var(--surface-raised); }
    .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }
    .image-card { cursor: pointer; background: var(--surface); border-radius: var(--radius); overflow: hidden; transition: transform var(--transition), box-shadow var(--transition); }
    .image-card:hover { transform: translateY(-2px); box-shadow: 0 4px 16px rgba(0,0,0,0.4); }
    .image-card img { width: 100%; height: 160px; object-fit: cover; display: block; }
    .card-skeleton { height: 200px; }
    .tag-row { padding: 6px; display: flex; flex-wrap: nowrap; gap: 4px; overflow: hidden; position: relative; }
    .tag-row::after { content: ''; position: absolute; right: 0; top: 0; bottom: 0; width: 2rem; background: linear-gradient(to right, transparent, var(--surface)); pointer-events: none; }
    .home-link { color: inherit; text-decoration: none; cursor: pointer; }
    .empty-state { text-align: center; padding: 60px 0; color: var(--text-muted); }
    .empty-icon { display: block; font-size: 2rem; margin-bottom: 12px; }
    .upload-link { display: inline-block; margin-top: 16px; color: var(--accent); text-decoration: none; font-weight: 600; }
    .upload-link:hover { text-decoration: underline; }
    .error-card { text-align: center; padding: 40px; background: var(--surface); border-radius: var(--radius); border: 1px solid var(--border); }
    .error-card p { color: var(--text-muted); margin-bottom: 16px; }
    .retry-btn { padding: 8px 24px; background: var(--surface-raised); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; transition: border-color var(--transition); }
    .retry-btn:hover { border-color: var(--border-focus); }
    .total-count { text-align: center; color: var(--text-muted); font-size: 0.85rem; margin: 16px 0 8px; }
    .pagination-bar { display: flex; justify-content: center; align-items: center; gap: 6px; margin: 16px 0 24px; flex-wrap: wrap; }
    .pag-btn { min-width: 36px; height: 36px; padding: 0 10px; background: var(--surface-raised); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius); cursor: pointer; font-size: 0.95rem; transition: border-color var(--transition), background var(--transition); }
    .pag-btn:hover:not(:disabled) { border-color: var(--border-focus); }
    .pag-btn:disabled { opacity: 0.35; cursor: not-allowed; }
    .page-btn.active { background: var(--accent); color: var(--accent-text); border-color: var(--accent); }
  `],
})
export class LibraryComponent implements OnInit {
  images: ImageRecord[] = [];
  activeFilters: string[] = [];
  tagSearch = '';
  suggestions: { name: string; image_count: number }[] = [];
  showSpinner = false;
  error = false;
  currentPage = 1;
  totalPages = 1;
  total = 0;
  readonly skeletonItems = Array(8).fill(null);
  private readonly limit = 24;
  private readonly filterChange$ = new Subject<string>();

  constructor(
    public imageService: ImageService,
    private tagService: TagService,
    public router: Router,
    private cdr: ChangeDetectorRef,
    private route: ActivatedRoute,
  ) {}

  ngOnInit(): void {
    const tagsParam = this.route.snapshot.queryParamMap.get('tags');
    if (tagsParam) {
      this.activeFilters = tagsParam.split(',').map((t) => t.trim()).filter((t) => t.length > 0);
    }
    const pageParam = this.route.snapshot.queryParamMap.get('page');
    if (pageParam) {
      this.currentPage = Math.max(1, parseInt(pageParam, 10) || 1);
    }
    this.load();
    this.route.queryParamMap.pipe(skip(1)).subscribe((params) => {
      const newPage = params.get('page') ? Math.max(1, parseInt(params.get('page')!, 10) || 1) : 1;
      const newTagsParam = params.get('tags');
      const newTags = newTagsParam
        ? newTagsParam.split(',').map((t) => t.trim()).filter((t) => t.length > 0)
        : [];
      const pageChanged = newPage !== this.currentPage;
      const tagsChanged = JSON.stringify(newTags) !== JSON.stringify(this.activeFilters);
      if (pageChanged || tagsChanged) {
        this.currentPage = newPage;
        this.activeFilters = newTags;
        this.images = [];
        this.load();
      }
    });
    this.filterChange$.pipe(debounceTime(300), distinctUntilChanged()).subscribe((q) => {
      if (q) {
        this.tagService.list(q, 10).subscribe((r) => {
          this.suggestions = r.items;
          this.cdr.markForCheck();
        });
      } else {
        this.suggestions = [];
        this.cdr.markForCheck();
      }
    });
  }

  load(): void {
    this.error = false;
    const offset = (this.currentPage - 1) * this.limit;
    const req$ = this.imageService.list(this.activeFilters, this.limit, offset).pipe(share());
    timer(150).pipe(takeUntil(req$)).subscribe(() => {
      this.showSpinner = true;
      this.cdr.markForCheck();
    });
    req$.subscribe({
      next: (res) => {
        this.images = res.items;
        this.total = res.total;
        this.totalPages = Math.ceil(res.total / this.limit) || 1;
        const clamped = Math.max(1, Math.min(this.currentPage, this.totalPages));
        if (clamped !== this.currentPage) {
          this.currentPage = clamped;
          this.router.navigate([], { queryParams: { page: this.currentPage }, queryParamsHandling: 'merge' });
        }
        this.showSpinner = false;
        this.cdr.markForCheck();
      },
      error: () => {
        this.showSpinner = false;
        this.error = true;
        this.cdr.markForCheck();
      },
    });
  }

  get pageWindow(): number[] {
    let start = Math.max(1, this.currentPage - 1);
    const end = Math.min(this.totalPages, start + 3);
    start = Math.max(1, end - 3);
    return Array.from({ length: end - start + 1 }, (_, i) => start + i);
  }

  goToPage(page: number): void {
    if (page >= 1 && page <= this.totalPages && page !== this.currentPage) {
      this.currentPage = page;
      this.router.navigate([], { queryParams: { page: this.currentPage }, queryParamsHandling: 'merge' });
      this.load();
    }
  }

  firstPage(): void {
    if (this.currentPage !== 1) {
      this.currentPage = 1;
      this.router.navigate([], { queryParams: { page: 1 }, queryParamsHandling: 'merge' });
      this.load();
    }
  }

  lastPage(): void {
    if (this.currentPage !== this.totalPages) {
      this.currentPage = this.totalPages;
      this.router.navigate([], { queryParams: { page: this.totalPages }, queryParamsHandling: 'merge' });
      this.load();
    }
  }

  nextPage(): void {
    if (this.currentPage < this.totalPages) {
      this.currentPage++;
      this.router.navigate([], { queryParams: { page: this.currentPage }, queryParamsHandling: 'merge' });
      this.load();
    }
  }

  prevPage(): void {
    if (this.currentPage > 1) {
      this.currentPage--;
      this.router.navigate([], { queryParams: { page: this.currentPage }, queryParamsHandling: 'merge' });
      this.load();
    }
  }

  onTagInput(event: Event): void {
    const val = (event.target as HTMLInputElement).value;
    this.tagSearch = val;
    this.filterChange$.next(val);
  }

  addFilter(tag: string): void {
    if (!this.activeFilters.includes(tag)) {
      this.activeFilters = [...this.activeFilters, tag];
    }
    this.tagSearch = '';
    this.suggestions = [];
    this.applyFilter(this.activeFilters);
  }

  removeFilter(tag: string): void {
    this.activeFilters = this.activeFilters.filter((t) => t !== tag);
    this.applyFilter(this.activeFilters);
  }

  applyFilter(tags: string[]): void {
    this.activeFilters = tags;
    this.currentPage = 1;
    this.images = [];
    this.router.navigate([], {
      queryParams: { page: 1, tags: tags.length ? tags.join(',') : null },
      queryParamsHandling: 'merge',
    });
    this.load();
  }

  onImgError(event: Event): void {
    const img = event.target as HTMLImageElement;
    if (!img.src.startsWith('data:')) {
      img.src = PLACEHOLDER_SVG;
    }
  }
}
