import { Injectable, inject, signal } from '@angular/core';
import { MediaRepository } from '../../../../core/media/media-repository';
import { MediaAsset, MediaAssetKind, MediaSort, MediaUploadOptions } from '../../../../core/media/models/media-asset.model';

const PAGE_SIZE = 24;

export type MediaViewMode = 'grid' | 'list';

@Injectable({ providedIn: 'root' })
export class MediaLibraryFacade {
  private readonly repository = inject(MediaRepository);

  readonly items = signal<MediaAsset[]>([]);
  readonly total = signal(0);
  readonly loading = signal(false);
  readonly uploading = signal(false);
  readonly error = signal<string | null>(null);
  readonly search = signal('');
  readonly page = signal(1);
  readonly folder = signal<string | null>(null);
  readonly folders = signal<string[]>([]);
  readonly kind = signal<MediaAssetKind | null>(null);
  readonly sort = signal<MediaSort>('recent');
  readonly viewMode = signal<MediaViewMode>('grid');
  readonly selectedIds = signal<Set<string>>(new Set());

  /** Kept so a failed upload can be retried without asking the user to re-pick the file. */
  private lastFailedFile: File | null = null;
  private uploadToken = 0;

  async load(): Promise<void> {
    this.loading.set(true);
    this.error.set(null);
    try {
      const result = await this.repository.list({
        page: this.page(),
        pageSize: PAGE_SIZE,
        search: this.search(),
        folder: this.folder() ?? undefined,
        kind: this.kind() ?? undefined,
        sort: this.sort(),
      });
      this.items.set(result.items);
      this.total.set(result.total);
      this.folders.set(await this.repository.listFolders());
    } catch {
      this.error.set('Failed to load media assets.');
    } finally {
      this.loading.set(false);
    }
  }

  setSearch(value: string): void {
    this.search.set(value);
    this.page.set(1);
    void this.load();
  }

  setFolder(value: string | null): void {
    this.folder.set(value);
    this.page.set(1);
    void this.load();
  }

  setKind(value: MediaAssetKind | null): void {
    this.kind.set(value);
    this.page.set(1);
    void this.load();
  }

  setSort(value: MediaSort): void {
    this.sort.set(value);
    void this.load();
  }

  setViewMode(mode: MediaViewMode): void {
    this.viewMode.set(mode);
  }

  setPage(page: number): void {
    this.page.set(page);
    void this.load();
  }

  async upload(file: File, options: MediaUploadOptions = {}): Promise<void> {
    const token = ++this.uploadToken;
    this.uploading.set(true);
    this.error.set(null);
    try {
      await this.repository.upload(file, { folder: this.folder() ?? undefined, ...options });
      // A cancelled upload can't stop the underlying store write mid-flight (best-effort
      // cancel), but it can at least avoid refreshing the list with the cancelled result.
      if (token !== this.uploadToken) {
        return;
      }
      this.lastFailedFile = null;
      this.page.set(1);
      await this.load();
    } catch (error: unknown) {
      if (token !== this.uploadToken) {
        return;
      }
      this.lastFailedFile = file;
      this.error.set(error instanceof Error ? error.message : 'Failed to upload file.');
    } finally {
      if (token === this.uploadToken) {
        this.uploading.set(false);
      }
    }
  }

  cancelUpload(): void {
    this.uploadToken++;
    this.uploading.set(false);
  }

  canRetry(): boolean {
    return !!this.lastFailedFile;
  }

  async retryUpload(): Promise<void> {
    if (this.lastFailedFile) {
      await this.upload(this.lastFailedFile);
    }
  }

  async updateTags(id: string, tags: string[]): Promise<void> {
    this.error.set(null);
    try {
      await this.repository.update(id, { tags });
      await this.load();
    } catch {
      this.error.set('Failed to update tags.');
    }
  }

  async updateDetails(id: string, patch: { altText?: Record<string, string>; caption?: string; description?: string; decorative?: boolean }): Promise<void> {
    this.error.set(null);
    try {
      await this.repository.update(id, patch);
      await this.load();
    } catch {
      this.error.set('Failed to update asset details.');
    }
  }

  async remove(id: string): Promise<void> {
    this.error.set(null);
    try {
      await this.repository.remove(id);
      this.deselect(id);
      await this.load();
    } catch {
      this.error.set('Failed to delete file.');
    }
  }

  isSelected(id: string): boolean {
    return this.selectedIds().has(id);
  }

  toggleSelect(id: string, checked: boolean): void {
    const next = new Set(this.selectedIds());
    checked ? next.add(id) : next.delete(id);
    this.selectedIds.set(next);
  }

  clearSelection(): void {
    this.selectedIds.set(new Set());
  }

  private deselect(id: string): void {
    const next = new Set(this.selectedIds());
    next.delete(id);
    this.selectedIds.set(next);
  }

  async bulkDelete(ids: string[]): Promise<void> {
    this.error.set(null);
    try {
      for (const id of ids) {
        await this.repository.remove(id);
      }
      this.clearSelection();
      await this.load();
    } catch {
      this.error.set('Failed to delete selected files.');
    }
  }

  async bulkMoveToFolder(ids: string[], folder: string | null): Promise<void> {
    this.error.set(null);
    try {
      for (const id of ids) {
        await this.repository.update(id, { folder: folder ?? undefined });
      }
      this.clearSelection();
      await this.load();
    } catch {
      this.error.set('Failed to move selected files.');
    }
  }

  downloadAssets(assets: MediaAsset[]): void {
    for (const asset of assets) {
      const link = document.createElement('a');
      link.href = asset.url;
      link.download = asset.filename;
      link.click();
    }
  }

  exportMetadata(assets: MediaAsset[]): void {
    const payload = assets.map(asset => ({
      id: asset.id,
      filename: asset.filename,
      mimeType: asset.mimeType,
      size: asset.size,
      width: asset.width,
      height: asset.height,
      altText: asset.altText,
      caption: asset.caption,
      description: asset.description,
      tags: asset.tags,
      folder: asset.folder,
      createdAt: asset.createdAt,
    }));
    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: 'application/json' });
    const url = URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.download = 'media-metadata.json';
    link.click();
    URL.revokeObjectURL(url);
  }
}
