/**
 * Font Tests
 *
 * Comprehensive tests for font family and font size functionality including:
 * - Font family changes (Arial, Times New Roman, Georgia, etc.)
 * - Font size adjustments (small, medium, large)
 * - Combined font and formatting operations
 * - Undo/redo for font changes
 */

import { test, expect } from '@playwright/test';
import { EditorPage } from '../helpers/editor-page';
import * as assertions from '../helpers/assertions';

test.describe('Font Family', () => {
  let editor: EditorPage;

  test.beforeEach(async ({ page }) => {
    editor = new EditorPage(page);
    await editor.goto();
    await editor.waitForReady();
    await editor.newDocument();
    await editor.focus();
  });

  test('change font to Arial', async ({ page }) => {
    await editor.typeText('Arial font test');
    await editor.selectAll();
    await editor.setFontFamily('Arial');

    // Verify font was applied
    const fontFamily = await page.evaluate(() => {
      const selection = window.getSelection();
      if (selection && selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);
        const element = range.startContainer.parentElement;
        return window.getComputedStyle(element!).fontFamily;
      }
      return '';
    });
    expect(fontFamily).toContain('Arial');
  });

  test('change font to Times New Roman', async ({ page }) => {
    await editor.typeText('Times font test');
    await editor.selectAll();
    await editor.setFontFamily('Times New Roman');

    // Verify font was applied by checking toolbar reflects the change
    // (computed style depends on system font availability)
    const toolbarFont = await page.locator('[aria-label="Select font family"]').textContent();
    expect(toolbarFont?.toLowerCase()).toContain('times');

    await assertions.assertDocumentContainsText(page, 'Times font test');
  });

  test('change font to Georgia', async ({ page }) => {
    await editor.typeText('Georgia font test');
    await editor.selectAll();
    await editor.setFontFamily('Georgia');

    await assertions.assertDocumentContainsText(page, 'Georgia font test');
  });

  test('change font to Verdana', async ({ page }) => {
    await editor.typeText('Verdana font test');
    await editor.selectAll();
    await editor.setFontFamily('Verdana');

    await assertions.assertDocumentContainsText(page, 'Verdana font test');
  });

  test('change font to Courier New', async ({ page }) => {
    await editor.typeText('Courier font test');
    await editor.selectAll();
    await editor.setFontFamily('Courier New');

    await assertions.assertDocumentContainsText(page, 'Courier font test');
  });

  test('font change on partial selection', async ({ page }) => {
    await editor.typeText('Hello World');
    await editor.selectText('World');
    await editor.setFontFamily('Arial');

    await assertions.assertDocumentContainsText(page, 'Hello World');
  });

  test('multiple font families in document', async ({ page }) => {
    // Type all text first
    await editor.typeText('First line');
    await editor.pressEnter();
    await editor.typeText('Second line');

    // Now apply fonts to each part
    await editor.selectText('First line');
    await editor.setFontFamily('Arial');
    await editor.selectText('Second line');
    await editor.setFontFamily('Georgia'); // Use Georgia instead of Times New Roman which may not be in list

    await assertions.assertDocumentContainsText(page, 'First line');
    await assertions.assertDocumentContainsText(page, 'Second line');
  });

  test('undo font family change', async ({ page }) => {
    await editor.typeText('Undo test');
    await editor.selectAll();
    await editor.setFontFamily('Georgia');
    await editor.undo();

    await assertions.assertDocumentContainsText(page, 'Undo test');
  });
});

test.describe('Font Size', () => {
  let editor: EditorPage;

  test.beforeEach(async ({ page }) => {
    editor = new EditorPage(page);
    await editor.goto();
    await editor.waitForReady();
    await editor.newDocument();
    await editor.focus();
  });

  test('set font size to 12pt', async ({ page }) => {
    await editor.typeText('Size test');
    await editor.selectAll();
    await editor.setFontSize(12);

    await assertions.assertDocumentContainsText(page, 'Size test');
  });

  test('set font size to 14pt', async ({ page }) => {
    await editor.typeText('Size test');
    await editor.selectAll();
    await editor.setFontSize(14);

    await assertions.assertDocumentContainsText(page, 'Size test');
  });

  test('set font size to 18pt', async ({ page }) => {
    await editor.typeText('Size test');
    await editor.selectAll();
    await editor.setFontSize(18);

    await assertions.assertDocumentContainsText(page, 'Size test');
  });

  test('set font size to 24pt (large)', async ({ page }) => {
    await editor.typeText('Large text');
    await editor.selectAll();
    await editor.setFontSize(24);

    await assertions.assertDocumentContainsText(page, 'Large text');
  });

  test('set font size to 36pt (very large)', async ({ page }) => {
    await editor.typeText('Very large text');
    await editor.selectAll();
    await editor.setFontSize(36);

    await assertions.assertDocumentContainsText(page, 'Very large text');
  });

  test('set minimum font size 8pt', async ({ page }) => {
    await editor.typeText('Tiny text');
    await editor.selectAll();
    await editor.setFontSize(8);

    await assertions.assertDocumentContainsText(page, 'Tiny text');
  });

  test('set large font size 72pt', async ({ page }) => {
    await editor.typeText('Huge');
    await editor.selectAll();
    await editor.setFontSize(72);

    await assertions.assertDocumentContainsText(page, 'Huge');
  });

  test('font size on partial selection', async ({ page }) => {
    await editor.typeText('Hello World');
    await editor.selectText('World');
    await editor.setFontSize(20);

    await assertions.assertDocumentContainsText(page, 'Hello World');
  });

  test('undo font size change', async ({ page }) => {
    await editor.typeText('Undo size test');
    await editor.selectAll();
    await editor.setFontSize(48);
    await editor.undo();

    await assertions.assertDocumentContainsText(page, 'Undo size test');
  });
});

test.describe('Combined Font Operations', () => {
  let editor: EditorPage;

  test.beforeEach(async ({ page }) => {
    editor = new EditorPage(page);
    await editor.goto();
    await editor.waitForReady();
    await editor.newDocument();
    await editor.focus();
  });

  test('combined font family and size', async ({ page }) => {
    await editor.typeText('Combined test');
    await editor.selectAll();
    await editor.setFontFamily('Georgia');
    await editor.setFontSize(16);

    await assertions.assertDocumentContainsText(page, 'Combined test');
  });

  test('font with bold formatting', async ({ page }) => {
    await editor.typeText('Bold font test');
    await editor.selectAll();
    await editor.setFontFamily('Arial');
    await editor.applyBold();

    await assertions.assertTextIsBold(page, 'Bold font test');
  });

  test('font with italic formatting', async ({ page }) => {
    await editor.typeText('Italic font test');
    await editor.selectAll();
    await editor.setFontFamily('Georgia');
    await editor.applyItalic();

    await assertions.assertTextIsItalic(page, 'Italic font test');
  });

  test('font family and size with bold', async ({ page }) => {
    await editor.typeText('Full formatting');
    await editor.selectAll();
    await editor.setFontFamily('Verdana');
    await editor.setFontSize(18);
    await editor.applyBold();

    await assertions.assertTextIsBold(page, 'Full formatting');
  });

  test('font with text color', async ({ page }) => {
    await editor.typeText('Colored font');
    await editor.selectAll();
    await editor.setFontFamily('Arial');
    await editor.setTextColor('#FF0000');

    await assertions.assertDocumentContainsText(page, 'Colored font');
  });

  test('multiple font changes in sequence', async ({ page }) => {
    await editor.typeText('Font changes');
    await editor.selectAll();
    await editor.setFontFamily('Arial');
    await editor.setFontFamily('Georgia');
    await editor.setFontFamily('Times New Roman');

    await assertions.assertDocumentContainsText(page, 'Font changes');
  });

  test('multiple size changes in sequence', async ({ page }) => {
    await editor.typeText('Size changes');
    await editor.selectAll();
    await editor.setFontSize(12);
    await editor.setFontSize(18);
    await editor.setFontSize(24);

    await assertions.assertDocumentContainsText(page, 'Size changes');
  });
});

test.describe('Font Edge Cases', () => {
  let editor: EditorPage;

  test.beforeEach(async ({ page }) => {
    editor = new EditorPage(page);
    await editor.goto();
    await editor.waitForReady();
    await editor.newDocument();
    await editor.focus();
  });

  test('font change with no selection', async ({ page }) => {
    await editor.typeText('No selection');
    // Don't select - should apply to next typed text or do nothing
    await editor.setFontFamily('Arial');
    // Small delay to ensure stored marks are set
    await page.waitForTimeout(100);
    await editor.typeText(' more text');

    await assertions.assertDocumentContainsText(page, 'No selection more text');
  });

  test('font change on empty document', async ({ page }) => {
    // Set font before typing
    await editor.setFontFamily('Georgia');
    // Small delay to ensure stored marks are set
    await page.waitForTimeout(100);
    await editor.typeText('Text after font set');

    await assertions.assertDocumentContainsText(page, 'Text after font set');
  });

  test('font size on empty document', async ({ page }) => {
    // Set size before typing
    await editor.setFontSize(24);
    // Small delay to ensure stored marks are set
    await page.waitForTimeout(100);
    await editor.typeText('Large from start');

    await assertions.assertDocumentContainsText(page, 'Large from start');
  });

  test('rapid font family changes', async ({ page }) => {
    await editor.typeText('Rapid changes');
    await editor.selectAll();

    // Rapid font changes
    await editor.setFontFamily('Arial');
    await editor.setFontFamily('Georgia');
    await editor.setFontFamily('Verdana');
    await editor.setFontFamily('Times New Roman');

    await assertions.assertDocumentContainsText(page, 'Rapid changes');
  });

  test('rapid font size changes', async ({ page }) => {
    await editor.typeText('Rapid size');
    await editor.selectAll();

    // Rapid size changes
    await editor.setFontSize(10);
    await editor.setFontSize(14);
    await editor.setFontSize(18);
    await editor.setFontSize(24);

    await assertions.assertDocumentContainsText(page, 'Rapid size');
  });

  test('font with special characters', async ({ page }) => {
    await editor.typeText('Special: @#$%^&*()');
    await editor.selectAll();
    await editor.setFontFamily('Courier New');

    await assertions.assertDocumentContainsText(page, 'Special: @#$%^&*()');
  });

  test('font with unicode characters', async ({ page }) => {
    await editor.typeText('Unicode: 日本語 中文');
    await editor.selectAll();
    await editor.setFontFamily('Arial');

    await assertions.assertDocumentContainsText(page, 'Unicode:');
  });
});

test.describe('Custom font registration via fonts prop', () => {
  test('does not register the custom face when fonts prop is omitted', async ({ page }) => {
    const editor = new EditorPage(page);
    await editor.goto();
    await editor.waitForReady();

    const found = await page.evaluate(() => {
      return Array.from(document.fonts).some(
        (f) => f.family.replace(/^["']|["']$/g, '') === 'E2E Custom Font'
      );
    });
    expect(found).toBe(false);
  });

  test('injects @font-face with each declared weight when fonts prop is supplied', async ({
    page,
  }) => {
    await page.goto('/?e2e=1&empty=1&customFonts=1');
    const editor = new EditorPage(page);
    await editor.waitForReady();

    const styleText = await page.evaluate(() => {
      const styles = Array.from(document.head.querySelectorAll('style'));
      return styles.map((s) => s.textContent || '').join('\n');
    });
    expect(styleText).toContain('font-family: "E2E Custom Font"');
    expect(styleText).toContain('font-weight: normal');
    expect(styleText).toContain('font-weight: 700');
  });

  test('registers the custom face on document.fonts', async ({ page }) => {
    await page.goto('/?e2e=1&empty=1&customFonts=1');
    const editor = new EditorPage(page);
    await editor.waitForReady();

    const families = await page.evaluate(() => {
      return Array.from(document.fonts).map((f) => f.family.replace(/^["']|["']$/g, ''));
    });
    expect(families).toContain('E2E Custom Font');
  });

  test('custom font actually loads and renders distinct glyphs', async ({ page }) => {
    await page.goto('/?e2e=1&empty=1&customFonts=1');
    const editor = new EditorPage(page);
    await editor.waitForReady();

    // Wait for the registered face to finish loading.
    const status = await page.evaluate(async () => {
      const face = Array.from(document.fonts).find(
        (f) => f.family.replace(/^["']|["']$/g, '') === 'E2E Custom Font'
      );
      if (!face) return 'missing';
      await face.load();
      return face.status;
    });
    expect(status).toBe('loaded');

    // Compare canvas measurement against a generic fallback. Different glyphs
    // produce different widths — proves the browser used the loaded face.
    const widths = await page.evaluate(() => {
      const sample = 'Custom font sample 1234567890';
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d')!;
      ctx.font = '72px monospace';
      const monospace = ctx.measureText(sample).width;
      ctx.font = '72px "E2E Custom Font", monospace';
      const custom = ctx.measureText(sample).width;
      return { monospace, custom };
    });
    expect(widths.custom).not.toBe(widths.monospace);
  });
});

test.describe('Google Fonts via loadFont()', () => {
  test('loadFont(name) registers a Google Fonts face on document.fonts', async ({ page }) => {
    // The `fonts` prop is for self-hosted faces. For Google Fonts, consumers
    // call `loadFont(name)` directly — the demo does that when ?googleFont=
    // is set, mirroring the recommended consumer pattern.
    await page.goto('/?e2e=1&empty=1&googleFont=Pacifico');
    const editor = new EditorPage(page);
    await editor.waitForReady();

    const loaded = await page.evaluate(async () => {
      await document.fonts.ready;
      return document.fonts.check('16px "Pacifico"');
    });
    expect(loaded).toBe(true);

    // Glyph check: Pacifico is a script face, visibly wider than monospace.
    const widths = await page.evaluate(() => {
      const sample = 'Pacifico script sample';
      const canvas = document.createElement('canvas');
      const ctx = canvas.getContext('2d')!;
      ctx.font = '72px monospace';
      const fallback = ctx.measureText(sample).width;
      ctx.font = '72px "Pacifico", monospace';
      const pacifico = ctx.measureText(sample).width;
      return { fallback, pacifico };
    });
    expect(widths.pacifico).not.toBe(widths.fallback);
  });
});

test.describe('Font size picker dropdown (issue #808)', () => {
  let editor: EditorPage;

  test.beforeEach(async ({ page }) => {
    editor = new EditorPage(page);
    await editor.goto();
    await editor.waitForReady();
    await editor.newDocument();
    await editor.focus();
  });

  test('scrolling inside the size list keeps the dropdown open', async ({ page }) => {
    await page.locator('[data-testid="font-size-display"]').first().click();

    const listbox = page.getByRole('listbox');
    await expect(listbox).toBeVisible();

    // Scrolling the dropdown's own list fires a scroll event that previously
    // bubbled to the window-capture close handler and dismissed the dropdown.
    await listbox.evaluate((el) => {
      el.scrollTop = el.scrollHeight;
      el.dispatchEvent(new Event('scroll', { bubbles: true }));
    });

    // Dropdown must stay open so the user can reach and click a preset.
    await expect(listbox).toBeVisible();
    await page.getByRole('option', { name: '72', exact: true }).click();
    await expect(listbox).toBeHidden();
  });
});
