/**
 * Settings Mobile Collapse Behavior
 *
 * The Settings page renders ~400 controls across ~57 sections. Before this
 * change the page on mobile rendered taller than 16,384 px — Puppeteer's
 * screenshot ceiling and a real UX disaster for the user, who would scroll
 * for what felt like forever. `initAccordions()` now starts every section
 * collapsed at the `(max-width: 767px)` breakpoint; desktop keeps the old
 * "all expanded" default.
 *
 * Search filtering must override the collapse: when the user is actively
 * filtering, every surviving section needs to be visible.
 *
 * Three cases worth pinning down:
 *
 *   1. Mobile (small viewport): sections start collapsed; page height
 *      drops well below the 16,384 px ceiling.
 *   2. Desktop: sections start expanded — same as the previous behavior,
 *      so power-user workflows aren't disrupted.
 *   3. Mobile + active search: surviving sections are force-expanded so
 *      matches aren't hidden behind a tap.
 *
 * This spec runs on a small mobile (Pixel 5) and a desktop project; the
 * Playwright project routing keeps each assertion paired with its viewport.
 */

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

const SECTION_HEADER = '.ldr-settings-section-header';
const SECTION_BODY = '.ldr-settings-section-body';
const SEARCH_INPUT = '#settings-search';

async function gotoSettings(page) {
  await page.goto('/settings/');
  await page.waitForSelector(SECTION_HEADER, { state: 'attached', timeout: 15000 });
  // initAccordions runs synchronously after innerHTML, but the async settings
  // fetch -> render -> initAccordions chain takes a moment. Poll for the
  // section count to stabilise before reading the collapsed state.
  await expect.poll(
    async () => page.locator(SECTION_HEADER).count(),
    { timeout: 10000, message: 'settings sections should finish rendering' },
  ).toBeGreaterThan(5);
}

async function isMobileViewport(page) {
  // Read the breakpoint from the same constant settings.js uses, so a
  // future change to the threshold doesn't desync the test from production.
  const breakpoint = await page.evaluate(
    () => window.__LDR_MOBILE_BREAKPOINT_PX ?? 767,
  );
  return page.evaluate(
    (bp) => window.matchMedia(`(max-width: ${bp}px)`).matches,
    breakpoint,
  );
}

test.describe('Settings Mobile Collapse', () => {
  test('sections start collapsed on mobile, expanded on desktop', async ({ page }) => {
    await gotoSettings(page);

    const mobile = await isMobileViewport(page);
    const stats = await page.evaluate(([headerSel, bodySel]) => {
      const headers = Array.from(document.querySelectorAll(headerSel));
      const bodies = Array.from(document.querySelectorAll(bodySel));
      return {
        total: headers.length,
        collapsed: headers.filter((h) => h.classList.contains('collapsed')).length,
        visibleBodies: bodies.filter((b) => getComputedStyle(b).display !== 'none').length,
      };
    }, [SECTION_HEADER, SECTION_BODY]);

    expect(stats.total, 'should render multiple sections').toBeGreaterThan(5);

    if (mobile) {
      expect(stats.collapsed, 'every section should start collapsed on mobile').toBe(stats.total);
      expect(stats.visibleBodies, 'no section body visible on mobile').toBe(0);
    } else {
      expect(stats.collapsed, 'no section should start collapsed on desktop').toBe(0);
      expect(stats.visibleBodies, 'every section body visible on desktop').toBe(stats.total);
    }
  });

  test('mobile page height drops below the 16384px screenshot ceiling', async ({ page }) => {
    await gotoSettings(page);
    const mobile = await isMobileViewport(page);
    test.skip(!mobile, 'page-height assertion is only meaningful at the mobile breakpoint');

    const pageHeight = await page.evaluate(() => document.documentElement.scrollHeight);

    // The bug we're fixing rendered the page > 16,384px tall. Pick a
    // comfortable bound below that (10,000px) — a regression that
    // re-expands sections by default would blow past 16,384 again.
    expect(pageHeight, 'collapsed Settings should be well under 16384px tall').toBeLessThan(10000);
  });

  test('active search forces surviving sections to expand', async ({ page }) => {
    await gotoSettings(page);

    await page.evaluate((selector) => {
      const input = document.querySelector(selector);
      input.focus();
      input.value = 'iterations';
      input.dispatchEvent(new Event('input', { bubbles: true }));
    }, SEARCH_INPUT);

    // The search rebuild is synchronous; the DOM swap is fast but the
    // initAccordions call is right after innerHTML assignment, so poll
    // until the collapsed count reaches zero rather than racing it.
    await expect.poll(
      async () => page.evaluate((headerSel) => {
        const headers = Array.from(document.querySelectorAll(headerSel));
        return {
          total: headers.length,
          collapsed: headers.filter((h) => h.classList.contains('collapsed')).length,
        };
      }, SECTION_HEADER),
      { timeout: 3000, message: 'every surviving section should be expanded during search' },
    ).toEqual(expect.objectContaining({ collapsed: 0 }));

    const post = await page.evaluate((headerSel) => {
      const headers = Array.from(document.querySelectorAll(headerSel));
      return { total: headers.length };
    }, SECTION_HEADER);
    expect(post.total, 'search should still return matching sections').toBeGreaterThan(0);
  });

  test('tapping a collapsed header expands it; tapping again collapses', async ({ page }) => {
    await gotoSettings(page);
    const mobile = await isMobileViewport(page);
    test.skip(!mobile, 'desktop sections start expanded; the toggle starting state differs there');

    // First tap: expand
    await page.locator(SECTION_HEADER).first().click();
    await expect.poll(
      async () => page.evaluate((sel) => {
        const h = document.querySelector(sel);
        const body = document.getElementById(h.getAttribute('data-target'));
        return {
          collapsed: h.classList.contains('collapsed'),
          bodyDisplay: body ? getComputedStyle(body).display : null,
        };
      }, SECTION_HEADER),
      { timeout: 2000, message: 'first tap should expand the section' },
    ).toEqual({ collapsed: false, bodyDisplay: 'block' });

    // Second tap: collapse again
    await page.locator(SECTION_HEADER).first().click();
    await expect.poll(
      async () => page.evaluate((sel) => {
        const h = document.querySelector(sel);
        const body = document.getElementById(h.getAttribute('data-target'));
        return {
          collapsed: h.classList.contains('collapsed'),
          bodyDisplay: body ? getComputedStyle(body).display : null,
        };
      }, SECTION_HEADER),
      { timeout: 2000, message: 'second tap should collapse the section again' },
    ).toEqual({ collapsed: true, bodyDisplay: 'none' });
  });
});
