import { devices, expect, test } from '@playwright/test';
import { expectNoA11yViolations } from './support/axe';
import { signIn } from './support/cms';
import {
  addSection,
  addTextSection,
  clearDraftSections,
  closeSectionDialog,
  fillTranslatedField,
  openSectionDialog,
  publishHome,
  saveDraft,
  sectionBySummary,
  sectionSummaries,
  submitByKeyboard,
  submitByPointer,
} from './support/home';

/**
 * História 31 — Composição da home: reordenar, ocultar preservando a posição e publicar
 * (P5.S2.1/S2.2/S6.1 — emenda ADR-016).
 *
 * A lista do editor **é** o documento: a ordem do array é a ordem visual, `hidden` mantém a seção
 * na lista e o caminho de teclado (foco + Enter nos botões de mover/ocultar) é o mesmo da WCAG 2.2
 * SC 2.5.7. O spec compõe a home, reordena, oculta/mostra e confere o **portal publicado**; o
 * viewport móvel repete o fluxo com **ponteiro** (`.click()` só passa quando nada intercepta o
 * alvo — a suspeita registrada na S3.1) e o axe mede as duas telas.
 *
 * Pré-condições: `make up` + `php artisan db:seed` (o perfil demo tem `home.*`) + assets.
 *
 * Publicar a home é **estado global**: o arquivo roda no projeto `cms-serial` (um worker, antes
 * dos demais via `dependencies`), como as histórias 18/23/26. O documento publicado ao final tem
 * um `hero` (único `<h1>`) e as seções de texto — nada de HTML/estilo vindo do dado.
 */

const PORTAL_ORIGIN = (process.env.BASE_URL ?? 'http://localhost:50700').replace(/\/$/, '');

/**
 * Emulação do `Pixel 7` **sem** `defaultBrowserType` (`test.use` dentro de `describe` não pode
 * forçar outro browser — o Playwright exigiria um worker novo); o resto do device é idêntico.
 */
const PIXEL_7 = devices['Pixel 7'];

test.describe('CMS — composição da home', () => {
  test('reordena por teclado, oculta sem perder a posição e publica a ordem no portal', async ({
    page,
  }) => {
    test.setTimeout(90_000);

    const stamp = Date.now();
    const heroTitle = `Home composta ${stamp}`;
    const [alfa, beta, gama] = [`Alfa ${stamp}`, `Beta ${stamp}`, `Gama ${stamp}`];

    await signIn(page);
    await page.goto('/home');
    await expect(page.getByRole('heading', { level: 1, name: 'Editor da home' })).toBeVisible();

    // Estado inicial limpo: o spec não depende do rascunho deixado por outra execução.
    await clearDraftSections(page);

    // 1. Hero (único `<h1>` do documento) + três seções de texto identificáveis pelo título.
    await addSection(page, 'hero');
    await openSectionDialog(page, 'Nova seção (ainda não salva)');
    await fillTranslatedField(page, 'Título', 'Português (Brasil)', heroTitle);
    await closeSectionDialog(page);
    await saveDraft(page);

    await addTextSection(page, alfa, 'Corpo Alfa');
    await addTextSection(page, beta, 'Corpo Beta');
    await addTextSection(page, gama, 'Corpo Gama');

    await expect.poll(() => sectionSummaries(page)).toEqual([heroTitle, alfa, beta, gama]);

    // 2. Reordenar por **teclado**: “Subir” na última seção (Gama) → [Alfa, Gama, Beta].
    await submitByKeyboard(page, 'Subir', gama);
    await expect.poll(() => sectionSummaries(page)).toEqual([heroTitle, alfa, gama, beta]);

    // 3. Ocultar: a seção sai do portal e **mantém a posição** na lista (badge “Oculta”).
    await submitByPointer(page, 'Ocultar', gama);
    await expect.poll(() => sectionSummaries(page)).toEqual([heroTitle, alfa, gama, beta]);
    await expect(sectionBySummary(page, gama)).toContainText('Oculta');
    await expectNoA11yViolations(page);

    // 4. Publicar e conferir o portal: a ordem do documento é a ordem visual e a oculta não sai.
    await publishHome(page);
    await page.goto(`${PORTAL_ORIGIN}/`, { waitUntil: 'load' });

    await expect(page.locator('main#main-content h1')).toHaveText(heroTitle);
    await expect(page.locator('main#main-content h2')).toHaveText([alfa, beta]);
    await expect(page.getByText(gama)).toHaveCount(0);
    expect(await page.locator('h1').count(), 'exatamente um <h1>').toBe(1);
    await expectNoA11yViolations(page);

    // 5. Mostrar de novo: volta **na mesma posição** ([Alfa, Gama, Beta]) e o portal confirma.
    await page.goto('/home');
    await submitByPointer(page, 'Mostrar', gama);
    await expect.poll(() => sectionSummaries(page)).toEqual([heroTitle, alfa, gama, beta]);
    await publishHome(page);

    await page.goto(`${PORTAL_ORIGIN}/?e2e=reordenada`, { waitUntil: 'load' });
    await expect(page.locator('main#main-content h2')).toHaveText([alfa, gama, beta]);
    await expectNoA11yViolations(page);
  });

  test.describe('sem JavaScript', () => {
    test.use({ javaScriptEnabled: false });

    test('adiciona e reordena uma seção pelo caminho nativo (sem JS)', async ({ page }) => {
      const stamp = Date.now();
      const title = `Sem JS ${stamp}`;

      await signIn(page);

      // Sem JS o `<dialog>` não abre: os campos ficam no fluxo e o submit do formulário é o
      // caminho nativo (nenhuma melhoria progressiva é pré-requisito).
      await page.goto('/home?novo=text');
      await page
        .getByRole('region', { name: 'Nova seção (ainda não salva)' })
        .getByRole('group', { name: 'Título' })
        .getByLabel('Português (Brasil)')
        .fill(title);
      await page
        .getByRole('region', { name: 'Nova seção (ainda não salva)' })
        .getByRole('group', { name: 'Conteúdo' })
        .getByLabel('Português (Brasil)')
        .fill(
          JSON.stringify({
            type: 'doc',
            content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Corpo sem JS' }] }],
          }),
        );
      await page.getByRole('button', { name: 'Salvar rascunho' }).first().click();
      await expect(page.getByText('Rascunho salvo.')).toBeVisible();

      const before = await sectionSummaries(page);
      expect(before.at(-1)).toBe(title);

      // Teclado no submit nativo: a seção sobe exatamente uma posição (posição anterior - 1).
      await submitByKeyboard(page, 'Subir', title);

      const after = await sectionSummaries(page);
      expect(after.indexOf(title)).toBe(before.length - 2);
    });
  });

  test.describe('viewport móvel — ponteiro', () => {
    test.use({
      viewport: PIXEL_7.viewport,
      deviceScaleFactor: PIXEL_7.deviceScaleFactor,
      isMobile: PIXEL_7.isMobile,
      hasTouch: PIXEL_7.hasTouch,
      userAgent: PIXEL_7.userAgent,
    });

    test('edita, move e oculta seções com clique real (sem sobreposição/interceptação)', async ({
      page,
    }) => {
      test.setTimeout(90_000);

      const stamp = Date.now();
      const title = `Mobile ${stamp}`;

      await signIn(page);

      // O estado publicado não muda aqui: apenas o rascunho é composto/alterado.
      await page.goto('/home?novo=text');
      await expect(
        page.getByRole('region', { name: 'Nova seção (ainda não salva)' }),
      ).toBeVisible();

      // `.click()` do Playwright só conclui quando o alvo está estável e **não interceptado**:
      // é a prova da suspeita de sobreposição registrada na S3.1 (fluxo longo + diálogo nativo).
      await openSectionDialog(page, 'Nova seção (ainda não salva)');
      await fillTranslatedField(page, 'Título', 'Português (Brasil)', title);
      await fillTranslatedField(
        page,
        'Conteúdo',
        'Português (Brasil)',
        JSON.stringify({
          type: 'doc',
          content: [{ type: 'paragraph', content: [{ type: 'text', text: 'Corpo mobile' }] }],
        }),
      );
      await closeSectionDialog(page);
      await saveDraft(page);

      const section = sectionBySummary(page, title);
      await expect(section).toHaveCount(1);

      // Mover/ocultar continuam acionáveis por ponteiro no viewport de 390 px.
      await submitByPointer(page, 'Subir', title);
      await submitByPointer(page, 'Ocultar', title);

      const hidden = sectionBySummary(page, title);
      await expect(hidden).toContainText('Oculta');
      await expectNoA11yViolations(page);
    });
  });
});
