import {
  ElementRef,
  Injector,
  computed,
  runInInjectionContext,
  ɵChangeDetectionScheduler as ChangeDetectionScheduler,
  ɵEffectScheduler as EffectScheduler
} from '@angular/core';
import { CustomEmoji } from '../../../../shared-kernel';
import { CustomEmojiService } from '../../application/custom-emoji.service';
import { initializeAppI18nForTests, provideAppI18nForTests } from '../../../../core/i18n/app-i18n.testing';
import { CustomEmojiPickerComponent } from './custom-emoji-picker.component';

const savedEmojis: CustomEmoji[] = [
  {
    id: 'party-id',
    name: 'party-parrot',
    creatorUserId: 'user-1',
    dataUrl: 'data:image/webp;base64,party',
    hash: 'party-hash',
    mime: 'image/webp',
    size: 128,
    createdAt: 100,
    updatedAt: 100,
    savedByUser: true
  },
  {
    id: 'wave-id',
    name: 'wave',
    creatorUserId: 'user-1',
    dataUrl: 'data:image/webp;base64,wave',
    hash: 'wave-hash',
    mime: 'image/webp',
    size: 128,
    createdAt: 100,
    updatedAt: 100,
    savedByUser: true
  }
];

function createEffectSchedulerMock() {
  const scheduledEffects = new Set<{ dirty: boolean; run: () => void }>();

  return {
    add: vi.fn((scheduledEffect: { dirty: boolean; run: () => void }) => {
      scheduledEffects.add(scheduledEffect);
    }),
    flush: vi.fn(() => {
      for (const scheduledEffect of scheduledEffects) {
        if (scheduledEffect.dirty) {
          scheduledEffect.run();
        }
      }
    }),
    remove: vi.fn((scheduledEffect: { dirty: boolean; run: () => void }) => {
      scheduledEffects.delete(scheduledEffect);
    }),
    schedule: vi.fn((scheduledEffect: { dirty: boolean; run: () => void }) => {
      scheduledEffects.add(scheduledEffect);
    })
  };
}

describe('CustomEmojiPickerComponent', () => {
  function createComponent(
    hostContainsTarget: boolean,
    emojis: CustomEmoji[] = []
  ): CustomEmojiPickerComponent {
    const injector = Injector.create({
      providers: [
        ...provideAppI18nForTests(),
        CustomEmojiPickerComponent,
        {
          provide: ChangeDetectionScheduler,
          useValue: { notify: vi.fn() }
        },
        {
          provide: EffectScheduler,
          useValue: createEffectSchedulerMock()
        },
        {
          provide: ElementRef,
          useValue: new ElementRef({
            contains: () => hostContainsTarget
          })
        },
        {
          provide: CustomEmojiService,
          useValue: {
            loadForUser: vi.fn(async () => undefined),
            shortcutEntries: computed(() => []),
            emojis: computed(() => emojis)
          }
        }
      ]
    });

    initializeAppI18nForTests(injector);

    return runInInjectionContext(injector, () => injector.get(CustomEmojiPickerComponent));
  }

  it('emits dismissed when clicking outside the picker', () => {
    const component = createComponent(false);
    const dismissed = vi.fn();

    component.dismissed.subscribe(() => dismissed());
    component.onDocumentClick({ target: {} } as MouseEvent);

    expect(dismissed).toHaveBeenCalledTimes(1);
  });

  it('does not emit dismissed when clicking inside the picker', () => {
    const component = createComponent(true);
    const dismissed = vi.fn();

    component.dismissed.subscribe(() => dismissed());
    component.onDocumentClick({ target: {} } as MouseEvent);

    expect(dismissed).not.toHaveBeenCalled();
  });

  it('filters unicode and custom emoji when the picker search query changes', () => {
    const component = createComponent(true, savedEmojis);

    component.setSearchQuery('heart');

    expect(component.filteredUnicodeEntries().map((entry) => entry.emoji)).toEqual(['❤️']);
    expect(component.filteredCustomEmojis()).toEqual([]);
    expect(component.showEmptySearchState()).toBe(false);

    component.setSearchQuery('par');

    expect(component.filteredUnicodeEntries().map((entry) => entry.emoji)).toEqual(['🎉']);
    expect(component.filteredCustomEmojis().map((emoji) => emoji.id)).toEqual(['party-id']);
  });

  it('shows an empty search state when no emoji matches the query', () => {
    const component = createComponent(true, savedEmojis);

    component.setSearchQuery('missing-emoji');

    expect(component.showEmptySearchState()).toBe(true);
  });

  it('clears the search query when the picker is dismissed', () => {
    const component = createComponent(true, savedEmojis);

    component.setSearchQuery('party');
    component.dismiss();

    expect(component.searchQuery()).toBe('');
  });

  it('defaults inline mode to false for floating popover embedding', () => {
    const component = createComponent(true);

    expect(component.inline()).toBe(false);
  });

});
