import {
  expect,
  type Locator,
  type Page
} from '@playwright/test';

export interface ChatDropFilePayload {
  name: string;
  mimeType: string;
  base64: string;
}

export class ChatMessagesPage {
  readonly composer: Locator;
  readonly composerInput: Locator;
  readonly sendButton: Locator;
  readonly typingIndicator: Locator;
  readonly gifButton: Locator;
  readonly gifPicker: Locator;
  readonly messageItems: Locator;

  constructor(private page: Page) {
    this.composer = page.locator('app-chat-message-composer');
    this.composerInput = page.getByPlaceholder('Type a message...');
    this.sendButton = page.getByRole('button', { name: 'Send message' });
    this.typingIndicator = page.locator('app-typing-indicator');
    this.gifButton = page.getByRole('button', { name: 'Search KLIPY GIFs' });
    this.gifPicker = page.getByRole('dialog', { name: 'KLIPY GIF picker' });
    this.messageItems = page.locator('[data-message-id]');
  }

  async waitForReady(): Promise<void> {
    await expect(this.composerInput).toBeVisible({ timeout: 30_000 });
  }

  async sendMessage(content: string): Promise<void> {
    await this.waitForReady();
    await this.composerInput.fill(content);
    await this.sendButton.click();
  }

  async typeDraft(content: string): Promise<void> {
    await this.waitForReady();
    await this.composerInput.fill(content);
  }

  async clearDraft(): Promise<void> {
    await this.waitForReady();
    await this.composerInput.fill('');
  }

  async attachFiles(files: ChatDropFilePayload[]): Promise<void> {
    await this.waitForReady();

    await this.composerInput.evaluate((element, payloads: ChatDropFilePayload[]) => {
      const dataTransfer = new DataTransfer();

      for (const payload of payloads) {
        const binary = atob(payload.base64);
        const bytes = new Uint8Array(binary.length);

        for (let index = 0; index < binary.length; index++) {
          bytes[index] = binary.charCodeAt(index);
        }

        dataTransfer.items.add(new File([bytes], payload.name, { type: payload.mimeType }));
      }

      element.dispatchEvent(new DragEvent('drop', {
        bubbles: true,
        cancelable: true,
        dataTransfer
      }));
    }, files);
  }

  async openGifPicker(): Promise<void> {
    await this.waitForReady();
    await this.gifButton.click();
    await expect(this.gifPicker).toBeVisible({ timeout: 10_000 });
  }

  async selectFirstGif(): Promise<void> {
    const gifCard = this.gifPicker.getByRole('button', { name: /click to select/i }).first();

    await expect(gifCard).toBeVisible({ timeout: 10_000 });
    await gifCard.click();
  }

  getMessageItemByText(text: string): Locator {
    return this.messageItems.filter({
      has: this.page.getByText(text, { exact: false })
    }).last();
  }

  getMessageImageByAlt(altText: string): Locator {
    return this.page.locator(`[data-message-id] img[alt="${altText}"]`).last();
  }

  async expectMessageImageLoaded(altText: string): Promise<void> {
    const image = this.getMessageImageByAlt(altText);

    await expect(image).toBeVisible({ timeout: 20_000 });
    await expect.poll(async () =>
      image.evaluate((element) => {
        const img = element as HTMLImageElement;

        return img.complete && img.naturalWidth > 0 && img.naturalHeight > 0;
      }), {
      timeout: 20_000,
      message: `Image ${altText} should fully load in chat`
    }).toBe(true);
  }

  getEmbedCardByTitle(title: string): Locator {
    return this.page.locator('app-chat-link-embed').filter({
      has: this.page.getByText(title, { exact: true })
    })
      .last();
  }

  async editOwnMessage(originalText: string, updatedText: string): Promise<void> {
    const messageItem = this.getMessageItemByText(originalText);
    const editButton = messageItem.locator('button:has(ng-icon[name="lucideEdit"])').first();
    const editTextarea = this.page.locator('textarea.edit-textarea').first();
    const saveButton = this.page.locator('button:has(ng-icon[name="lucideCheck"])').first();

    await expect(messageItem).toBeVisible({ timeout: 15_000 });
    await messageItem.hover();
    await editButton.click();
    await expect(editTextarea).toBeVisible({ timeout: 10_000 });
    await editTextarea.fill(updatedText);
    await saveButton.click();
  }

  async deleteOwnMessage(text: string): Promise<void> {
    const messageItem = this.getMessageItemByText(text);
    const deleteButton = messageItem.locator('button:has(ng-icon[name="lucideTrash2"])').first();

    await expect(messageItem).toBeVisible({ timeout: 15_000 });
    await messageItem.hover();
    await deleteButton.click();
  }
}
