import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';

// Touch-target contract for the SHARED interactive primitives.
//
// WCAG / platform HIG put the comfortable touch floor at ~44px. jsdom can't
// evaluate @media queries or compute layout, so we can't assert rendered box
// sizes in a unit test. Instead we pin the CSS *contract*: each shared control
// grows to the --tap-min floor inside the mobile breakpoint. This guards
// against a future edit silently dropping the mobile bump (the regression the
// desktop/mobile consistency review surfaced).
//
// Scope is deliberately the genuinely-shared primitives — the global
// input/button rules in tokens.css, SegmentedControl, and Chip. The per-route
// `.icon-btn` is copy-pasted across seven files and is a separate refactor.

// Resolved from the vitest cwd (the frontend root) — jsdom doesn't hand us a
// file:// import.meta.url to anchor on.
const read = (rel: string) => readFileSync(resolve(process.cwd(), rel), 'utf8');

const TOKENS = read('src/lib/styles/tokens.css');
const SEGMENTED = read('src/lib/components/SegmentedControl.svelte');
const CHIP = read('src/lib/components/Chip.svelte');

// Pull the text inside every `@media (max-width: 640px) { ... }` block,
// brace-balanced so nested rules are captured. Returns the concatenation so a
// `.includes` check only matches declarations that live under the mobile query.
function mobileBlocks(css: string): string {
    const marker = '@media (max-width: 640px)';
    let out = '';
    let from = 0;
    for (;;) {
        const start = css.indexOf(marker, from);
        if (start === -1) break;
        const open = css.indexOf('{', start);
        if (open === -1) break;
        let depth = 0;
        let i = open;
        for (; i < css.length; i++) {
            if (css[i] === '{') depth++;
            else if (css[i] === '}') {
                depth--;
                if (depth === 0) break;
            }
        }
        out += css.slice(open + 1, i) + '\n';
        from = i + 1;
    }
    return out;
}

// Collapse whitespace so `min-height:\n   var(--tap-min)` matches regardless of
// the formatter's line breaks.
const squish = (s: string) => s.replace(/\s+/g, ' ');

describe('shared touch-target contract', () => {
    it('defines a --tap-min token of 44px', () => {
        expect(squish(TOKENS)).toMatch(/--tap-min:\s*44px/);
    });

    it('grows global inputs and buttons to the touch floor on mobile', () => {
        const mobile = squish(mobileBlocks(TOKENS));
        // input/select group and button rule both reach --tap-min under 640px.
        expect(mobile).toMatch(/input[^}]*min-height:\s*var\(--tap-min\)/);
        expect(mobile).toMatch(/button[^{]*\{[^}]*min-height:\s*var\(--tap-min\)/);
    });

    it('grows SegmentedControl options to the touch floor on mobile', () => {
        const mobile = squish(mobileBlocks(SEGMENTED));
        expect(mobile).toMatch(/\.seg[^}]*min-height:\s*var\(--tap-min\)/);
    });

    it("expands the chip remove button's hit area to the touch floor on mobile", () => {
        const mobile = squish(mobileBlocks(CHIP));
        // The visible glyph stays small; the tappable area reaches --tap-min.
        expect(mobile).toMatch(/\.chip-remove[^}]*min-(height|block-size):\s*var\(--tap-min\)/);
    });
});
