import { chromium } from '@playwright/test';

const MID = 'a1111111-1111-1111-1111-111111111111';
const CID = 'c1111111-1111-1111-1111-111111111111';

const browser = await chromium.launch();
const ctx = await browser.newContext({ viewport: { width: 390, height: 844 }, deviceScaleFactor: 1 });
const page = await ctx.newPage();

page.on('pageerror', e => console.log('  PAGE ERROR:', e.message));

// Generic stubs so every page renders deterministically without a backend
async function mock() {
    await page.unrouteAll().catch(() => {});
    await page.route('**/api/v1/auth/config', r => r.fulfill({ status: 200, contentType: 'application/json', body: '{"self_register_enabled":true,"private_mode":false}' }));
    await page.route('**/api/v1/auth/me', r => r.fulfill({ status: 401, contentType: 'application/json', body: '{"error":{"code":"x","message":"x"}}' }));
    await page.route('**/api/v1/auth/me/preferences', r => r.fulfill({ status: 401, contentType: 'application/json', body: '{}' }));
    await page.route('**/api/v1/me/bookmarks*', r => r.fulfill({ status: 401, contentType: 'application/json', body: '{}' }));
    await page.route('**/api/v1/me/collections*', r => r.fulfill({ status: 401, contentType: 'application/json', body: '{}' }));
    await page.route('**/api/v1/me/read-progress*', r => r.fulfill({ status: 401, contentType: 'application/json', body: '{}' }));
    await page.route('**/api/v1/mangas/**', r => {
        const url = new URL(r.request().url());
        const segs = url.pathname.split('/');
        const last = segs[segs.length - 1];
        if (last === MID) {
            return r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({
                id: MID,
                title: 'A Very Long Manga Title That Should Ellipsize On Narrow Screens',
                status: 'ongoing',
                alt_titles: ['Alt One', 'Alt Two'],
                // Realistic worst case: a normal-length lorem-ipsum prefix
                // followed by a long unbreakable token (e.g. a crawl-source
                // URL or a romanized title with no separators). The token
                // is what pushed the description past the screen edge on
                // user devices.
                description: 'Lorem ipsum dolor sit amet '.repeat(8) + ' AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA',
                cover_image_path: `mangas/${MID}/cover.png`,
                created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z',
                authors: [{ id: 'au1', name: 'Kentaro-Miura-with-an-extremely-long-author-name' }],
                genres: [{ id: 'g1', name: 'Action' }, { id: 'g2', name: 'Dark Fantasy' }, { id: 'g3', name: 'Adventure' }],
                // Mix of normal-length and one extreme tag with no soft-break
                // opportunities — exercises chip wrap behavior under stress.
                tags: [
                    { id: 't1', name: 'psychological', added_by: null },
                    { id: 't2', name: 'school', added_by: null },
                    { id: 't3', name: 'SuperLongUnbrokenTagNameThatDoesNotFitInAnyReasonableChipWidth', added_by: null }
                ]
            }) });
        }
        if (last === CID) {
            return r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ id: CID, manga_id: MID, number: 1, title: 'The Brand', page_count: 3, created_at: '2026-01-01T00:00:00Z' }) });
        }
        if (last === 'pages') {
            return r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ pages: [
                { id: 'p1', chapter_id: CID, page_number: 1, storage_key: 'x.png', content_type: 'image/png' },
                { id: 'p2', chapter_id: CID, page_number: 2, storage_key: 'y.png', content_type: 'image/png' },
                { id: 'p3', chapter_id: CID, page_number: 3, storage_key: 'z.png', content_type: 'image/png' }
            ] }) });
        }
        // Mangas list or chapters list
        if (url.pathname.endsWith('/chapters')) {
            return r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({
                items: [{ id: CID, manga_id: MID, number: 1, title: 'The Brand', page_count: 3, created_at: '2026-01-01T00:00:00Z' }],
                page: { limit: 50, offset: 0, total: 1 }
            }) });
        }
        const items = Array.from({ length: 8 }, (_, i) => ({
            id: `m${i + 1}`, title: `Manga ${i + 1} with a long title that ellipsizes`,
            status: 'ongoing', alt_titles: [], description: null, cover_image_path: null,
            created_at: '2026-01-01T00:00:00Z', updated_at: '2026-01-01T00:00:00Z',
            authors: [{ id: 'a1', name: 'Author' }], genres: [{ id: 'g1', name: 'Action' }], tags: []
        }));
        return r.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ items, page: { limit: 50, offset: 0, total: 8 } }) });
    });
    await page.route('**/api/v1/genres*', r => r.fulfill({ status: 200, contentType: 'application/json', body: '[]' }));
    await page.route('**/api/v1/files/**', r => r.fulfill({ status: 200, contentType: 'image/png', body: Buffer.from('89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c4890000000d49444154789c63000100000005000158a3b62a0000000049454e44ae426082', 'hex') }));
}

const routes = [
    ['/',              'catalog'],
    ['/login',         'login'],
    ['/register',      'register'],
    ['/upload',        'upload'],
    [`/manga/${MID}`,  'detail'],
    [`/manga/${MID}/chapter/${CID}`, 'reader'],
    ['/library',       'library'],
    ['/profile',       'profile-overview'],
    ['/profile/account','account'],
    ['/profile/preferences','preferences'],
    ['/bookmarks',     'bookmarks-top'],
    ['/collections',   'collections-top']
];

// Identify elements that "kiss" the viewport edges — their right
// edge is essentially flush with the screen edge. For interactive
// controls (buttons, anchors, inputs, .chip*) this is almost always
// a design bug rather than intentional full-bleed chrome.
function bleeders(thresholdPx) {
    const vw = window.innerWidth;
    const bad = [];
    const candidates = document.querySelectorAll('button, a, input, select, textarea, [class*="chip"], [class*="card"], h1, h2, h3, p');
    for (const el of candidates) {
        const r = el.getBoundingClientRect();
        if (r.width === 0) continue;
        // Skip elements whose ancestor is itself position:fixed at the
        // edges — that's intentional chrome (bottom nav, CTA bar).
        let p = el;
        let inFixed = false;
        while (p) {
            const cs = getComputedStyle(p);
            if (cs.position === 'fixed') { inFixed = true; break; }
            p = p.parentElement;
        }
        if (inFixed) continue;
        // Touching right edge?
        if (vw - r.right < thresholdPx) {
            const tag = el.tagName.toLowerCase();
            const cls = (typeof el.className === 'string' ? el.className : '').split(' ').filter(c => !!c && !c.startsWith('s-')).slice(0, 2).join('.');
            const id = el.dataset.testid ?? cls;
            bad.push(`right-bleed ${tag}.${id} right=${Math.round(r.right)}`);
        }
        // Touching left edge?
        if (r.left < thresholdPx) {
            const tag = el.tagName.toLowerCase();
            const cls = (typeof el.className === 'string' ? el.className : '').split(' ').filter(c => !!c && !c.startsWith('s-')).slice(0, 2).join('.');
            const id = el.dataset.testid ?? cls;
            bad.push(`left-bleed  ${tag}.${id} left=${Math.round(r.left)}`);
        }
    }
    // de-dup
    return [...new Set(bad)];
}

for (const [path, name] of routes) {
    await mock();
    try {
        await page.goto('http://localhost:5173' + path, { waitUntil: 'networkidle', timeout: 8000 });
    } catch (e) {
        console.log(`${name.padEnd(20)} NAV ERROR: ${e.message.slice(0, 60)}`);
        continue;
    }
    await page.waitForTimeout(400);
    const m = await page.evaluate(() => {
        const all = Array.from(document.querySelectorAll('*'));
        const vw = window.innerWidth;
        const offenders = [];
        for (const el of all) {
            const r = el.getBoundingClientRect();
            if (r.right > vw + 0.5 || r.width > vw + 0.5) {
                const tag = el.tagName.toLowerCase();
                const className = typeof el.className === 'string'
                    ? el.className.split(' ').filter(c => !!c).slice(0, 2).join('.')
                    : '';
                const id = el.dataset.testid ?? className;
                offenders.push(`${tag}.${id} right=${Math.round(r.right)} w=${Math.round(r.width)}`);
            }
        }
        // Detect horizontal overflow from scroll measurement, but skip
        // elements that intentionally clip via overflow:hidden — that's
        // ellipsization, not a layout bug.
        const scrollers = [];
        for (const el of all) {
            if (el.scrollWidth > el.clientWidth + 0.5) {
                const cs = getComputedStyle(el);
                if (cs.overflowX === 'hidden' || cs.overflowX === 'scroll' || cs.overflowX === 'auto') continue;
                const tag = el.tagName.toLowerCase();
                const className = typeof el.className === 'string'
                    ? el.className.split(' ').filter(c => !!c).slice(0, 2).join('.')
                    : '';
                const id = el.dataset.testid ?? className;
                // Walk up to find the offending child too
                let child = '';
                for (const c of el.children) {
                    const r = c.getBoundingClientRect();
                    const er = el.getBoundingClientRect();
                    if (r.right > er.right + 0.5) {
                        child = ` child:${c.tagName.toLowerCase()}.${typeof c.className === 'string' ? c.className.split(' ').filter(x => !!x).slice(0, 2).join('.') : ''}(right=${Math.round(r.right)})`;
                        break;
                    }
                }
                scrollers.push(`${tag}.${id} scrollW=${el.scrollWidth} clientW=${el.clientWidth}${child}`);
            }
        }
        return {
            innerWidth: vw,
            docW: document.documentElement.scrollWidth,
            bodyW: document.body.scrollWidth,
            offenders: offenders.slice(0, 8),
            scrollers: scrollers.slice(0, 8)
        };
    });
    // Also flag elements that touch the viewport edges (no inset gutter)
    const bleed = await page.evaluate((b) => {
        // re-execute the bleeders function here because page context is fresh
        const vw = window.innerWidth;
        const threshold = 4;
        const bad = [];
        const candidates = document.querySelectorAll('button, a, input, select, textarea, [class*="chip"], [class*="card"], h1, h2, h3, p');
        for (const el of candidates) {
            const r = el.getBoundingClientRect();
            if (r.width === 0) continue;
            let p = el;
            let inFixed = false;
            while (p) {
                const cs = getComputedStyle(p);
                if (cs.position === 'fixed') { inFixed = true; break; }
                p = p.parentElement;
            }
            if (inFixed) continue;
            const tag = el.tagName.toLowerCase();
            const cls = (typeof el.className === 'string' ? el.className : '').split(' ').filter(c => !!c && !c.startsWith('s-')).slice(0, 2).join('.');
            const id = el.dataset.testid ?? cls;
            if (vw - r.right < threshold) bad.push(`right-bleed ${tag}.${id} right=${Math.round(r.right)}`);
            if (r.left < threshold) bad.push(`left-bleed  ${tag}.${id} left=${Math.round(r.left)}`);
        }
        return [...new Set(bad)];
    });

    const safe = m.docW <= m.innerWidth && m.bodyW <= m.innerWidth && m.offenders.length === 0 && m.scrollers.length === 0 && bleed.length === 0;
    console.log(`${name.padEnd(20)} ${safe ? '✅' : '⚠️ '} doc=${m.docW} body=${m.bodyW} inner=${m.innerWidth}`);
    if (m.offenders.length) {
        console.log('  --- elements past viewport ---');
        for (const o of m.offenders) console.log('     ', o);
    }
    if (m.scrollers.length) {
        console.log('  --- horizontal scrollers ---');
        for (const s of m.scrollers) console.log('     ', s);
    }
    if (bleed.length) {
        console.log('  --- edge bleeders (touch viewport) ---');
        for (const b of bleed.slice(0, 12)) console.log('     ', b);
    }
    await page.screenshot({ path: `/tmp/audit-${name}.png`, fullPage: false });
}

await browser.close();
