import { beforeEach, describe, expect, it } from 'vitest';
import { handleActuateAPI } from '../../api/index.js';
import { initDB } from '../../db.js';
function createMockDB(docs) {
    const findMany = async (args) => {
        const where = args?.where ?? {};
        return docs.filter((d) => {
            if (where.status && d.status !== where.status)
                return false;
            if (where.deletedAt === null && d.deletedAt !== null)
                return false;
            return true;
        });
    };
    return {
        document: { findMany, findFirst: async () => null, count: async () => 0 },
        user: {},
        session: {},
        media: {},
    };
}
const PUBLIC_PAGE = {
    title: 'Public About Page',
    slug: 'about',
    collection: 'pages',
    status: 'PUBLISHED',
    deletedAt: null,
    updatedAt: new Date('2026-03-01T00:00:00Z'),
    data: { metaDescription: 'About us' },
};
const PATH_PAGE = {
    title: 'Custom Path Page',
    slug: 'ignored-slug',
    collection: 'pages',
    status: 'PUBLISHED',
    deletedAt: null,
    updatedAt: new Date('2026-03-05T00:00:00Z'),
    data: { path: '/services/consulting', metaDescription: 'Consulting' },
};
const SOFT_DELETED = {
    title: 'Deleted Secret Page',
    slug: 'deleted-secret',
    collection: 'pages',
    status: 'PUBLISHED',
    deletedAt: new Date('2026-02-01T00:00:00Z'),
    updatedAt: new Date('2026-02-15T00:00:00Z'),
    data: {},
};
const INTERNAL_CONFIG = {
    title: 'SEO Config',
    slug: 'seo-config',
    collection: '__seo_config__',
    status: 'PUBLISHED',
    deletedAt: null,
    updatedAt: new Date('2026-03-10T00:00:00Z'),
    data: {},
};
describe('GET /llms.txt', () => {
    beforeEach(() => {
        delete globalThis.__actuateConfig;
    });
    it('lists public content but excludes soft-deleted and internal docs', async () => {
        const docs = [PUBLIC_PAGE, SOFT_DELETED, INTERNAL_CONFIG];
        initDB(createMockDB(docs));
        const handler = handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                seo: { siteUrl: 'https://example.com' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        expect(res.status).toBe(200);
        const text = await res.text();
        expect(text).toContain('Public About Page');
        // Soft-deleted content must never surface publicly.
        expect(text).not.toContain('Deleted Secret Page');
        expect(text).not.toContain('deleted-secret');
        // Internal/system collections must never surface publicly.
        expect(text).not.toContain('__seo_config__');
        expect(text).not.toContain('SEO Config');
    });
    it('emits absolute canonical URLs that honor data.path', async () => {
        const docs = [PUBLIC_PAGE, PATH_PAGE];
        initDB(createMockDB(docs));
        const handler = handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                seo: { siteUrl: 'https://example.com', siteName: 'Acme' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        // Absolute URL (previously a hardcoded empty siteUrl produced "/about").
        expect(text).toContain('https://example.com/about');
        // data.path wins over slug — must not fall back to /pages/ignored-slug.
        expect(text).toContain('https://example.com/services/consulting');
        expect(text).not.toContain('ignored-slug');
        // Site name comes from config, not the hardcoded "Actuate CMS".
        expect(text).toContain('# Acme');
    });
    it('prefers the GEO key takeaway over the meta description', async () => {
        const doc = {
            title: 'Hiking Boots',
            slug: 'boots',
            collection: 'pages',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-01T00:00:00Z'),
            data: {
                keyTakeaway: 'Pick boots by terrain and fit.',
                metaDescription: 'A long marketing meta description.',
            },
        };
        initDB(createMockDB([doc]));
        const handler = handleActuateAPI({
            prismaClient: createMockDB([doc]),
            config: {
                seo: { siteUrl: 'https://example.com' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        expect(text).toContain('Pick boots by terrain and fit.');
        expect(text).not.toContain('A long marketing meta description.');
    });
    it('marks the file itself noindex via X-Robots-Tag', async () => {
        initDB(createMockDB([PUBLIC_PAGE]));
        const handler = handleActuateAPI({
            prismaClient: createMockDB([PUBLIC_PAGE]),
            config: {
                seo: { siteUrl: 'https://example.com' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        expect(res.headers.get('x-robots-tag')).toBe('noindex');
    });
    it('returns 404 when llms is disabled in config', async () => {
        initDB(createMockDB([PUBLIC_PAGE]));
        const handler = handleActuateAPI({
            prismaClient: createMockDB([PUBLIC_PAGE]),
            config: {
                seo: { siteUrl: 'https://example.com', llms: { disabled: true } },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        expect(res.status).toBe(404);
    });
    it('restricts AUTO selection to eligibleCollections when configured', async () => {
        const blog = {
            title: 'A Blog Post',
            slug: 'a-blog-post',
            collection: 'posts',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-02T00:00:00Z'),
            data: {},
        };
        const docs = [PUBLIC_PAGE, blog];
        initDB(createMockDB(docs));
        const handler = handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                seo: { siteUrl: 'https://example.com', llms: { eligibleCollections: ['pages'] } },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        expect(text).toContain('Public About Page');
        expect(text).not.toContain('A Blog Post');
    });
    it('caps entries per section', async () => {
        const docs = Array.from({ length: 5 }, (_, i) => ({
            title: `Page ${i}`,
            slug: `page-${i}`,
            collection: 'pages',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date(`2026-03-0${i + 1}T00:00:00Z`),
            data: {},
        }));
        initDB(createMockDB(docs));
        const handler = handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                seo: { siteUrl: 'https://example.com', llms: { maxPerSection: 2 } },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        const itemCount = (text.match(/^- \[Page /gm) ?? []).length;
        expect(itemCount).toBe(2);
    });
    it('sanitizes descriptions into safe single-line text', async () => {
        const doc = {
            title: 'Tricky [Title]',
            slug: 'tricky',
            collection: 'pages',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-01T00:00:00Z'),
            data: { keyTakeaway: 'Line one\nLine two (with parens) [and brackets]' },
        };
        initDB(createMockDB([doc]));
        const handler = handleActuateAPI({
            prismaClient: createMockDB([doc]),
            config: {
                seo: { siteUrl: 'https://example.com' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        // No raw newlines inside the list item, and link-breaking chars removed.
        expect(text).toContain('Line one Line two with parens and brackets');
        expect(text).toContain('Tricky Title');
        expect(text).not.toMatch(/\[Title\]/);
    });
    it('excludes noindexed documents', async () => {
        const hidden = {
            title: 'Hidden Page',
            slug: 'hidden',
            collection: 'pages',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-01T00:00:00Z'),
            data: { noindex: true },
        };
        const docs = [PUBLIC_PAGE, hidden];
        initDB(createMockDB(docs));
        const handler = handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                seo: { siteUrl: 'https://example.com' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        expect(text).toContain('Public About Page');
        expect(text).not.toContain('Hidden Page');
    });
    it('honors per-doc llmsInclude: ALWAYS overrides eligibility, NEVER excludes', async () => {
        const pinnedBlog = {
            title: 'Pinned Post',
            slug: 'pinned',
            collection: 'posts',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-02T00:00:00Z'),
            data: { llmsInclude: 'ALWAYS' },
        };
        const excludedPage = {
            title: 'Excluded Page',
            slug: 'excluded',
            collection: 'pages',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-03T00:00:00Z'),
            data: { llmsInclude: 'NEVER' },
        };
        const docs = [PUBLIC_PAGE, pinnedBlog, excludedPage];
        initDB(createMockDB(docs));
        const handler = handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                // Only `pages` are AUTO-eligible — the pinned post is in `posts`.
                seo: { siteUrl: 'https://example.com', llms: { eligibleCollections: ['pages'] } },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                    posts: { slug: 'posts', labels: { singular: 'Post', plural: 'Posts' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        expect(text).toContain('Pinned Post'); // ALWAYS beats the eligibility filter
        expect(text).toContain('Public About Page'); // AUTO + eligible
        expect(text).not.toContain('Excluded Page'); // NEVER
    });
    it('uses curated llmsDescription, section, and optional grouping', async () => {
        const doc = {
            title: 'Curated Page',
            slug: 'curated',
            collection: 'pages',
            status: 'PUBLISHED',
            deletedAt: null,
            updatedAt: new Date('2026-03-01T00:00:00Z'),
            data: {
                keyTakeaway: 'A takeaway that should lose.',
                llmsDescription: 'The curated agent description.',
                llmsSection: 'Highlights',
                llmsOptional: true,
            },
        };
        initDB(createMockDB([doc]));
        const handler = handleActuateAPI({
            prismaClient: createMockDB([doc]),
            config: {
                seo: { siteUrl: 'https://example.com' },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        expect(text).toContain('The curated agent description.');
        expect(text).not.toContain('A takeaway that should lose.');
        // Optional entries land under the reserved ## Optional section, not under
        // their own ## Highlights section.
        expect(text).toContain('## Optional');
    });
    it('uses the configured llms summary and intro', async () => {
        initDB(createMockDB([PUBLIC_PAGE]));
        const handler = handleActuateAPI({
            prismaClient: createMockDB([PUBLIC_PAGE]),
            config: {
                seo: {
                    siteUrl: 'https://example.com',
                    defaultMetaDescription: 'Generic fallback.',
                    llms: { summary: 'The reviewed site summary.', intro: 'A bit more context.' },
                },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
        const res = await handler(new Request('https://example.com/api/cms/llms.txt'));
        const text = await res.text();
        expect(text).toContain('> The reviewed site summary.');
        expect(text).toContain('A bit more context.');
        expect(text).not.toContain('Generic fallback.');
    });
});
describe('GET /llms-full.txt', () => {
    beforeEach(() => {
        delete globalThis.__actuateConfig;
    });
    const ARTICLE = {
        title: 'Deep Dive',
        slug: 'deep-dive',
        collection: 'pages',
        status: 'PUBLISHED',
        deletedAt: null,
        updatedAt: new Date('2026-03-01T00:00:00Z'),
        data: { body: '<p>The full body content.</p>' },
        plainText: 'The full body content.',
    };
    function makeHandler(docs, llms = {}) {
        initDB(createMockDB(docs));
        return handleActuateAPI({
            prismaClient: createMockDB(docs),
            config: {
                seo: { siteUrl: 'https://example.com', siteName: 'Acme', llms },
                collections: {
                    pages: { slug: 'pages', labels: { singular: 'Page', plural: 'Pages' }, fields: {} },
                },
            },
        });
    }
    it('404s when fullEnabled is not set', async () => {
        const handler = makeHandler([ARTICLE]);
        const res = await handler(new Request('https://example.com/api/cms/llms-full.txt'));
        expect(res.status).toBe(404);
    });
    it('inlines page content as markdown when enabled', async () => {
        const handler = makeHandler([ARTICLE], { fullEnabled: true });
        const res = await handler(new Request('https://example.com/api/cms/llms-full.txt'));
        expect(res.status).toBe(200);
        expect(res.headers.get('x-robots-tag')).toBe('noindex');
        const text = await res.text();
        expect(text).toContain('# Acme');
        expect(text).toContain('URL: https://example.com/deep-dive');
        expect(text).toContain('# Deep Dive');
        expect(text).toContain('The full body content.');
    });
    it('excludes noindexed docs and truncates at the byte budget', async () => {
        const big = {
            ...ARTICLE,
            title: 'Big',
            slug: 'big',
            data: { body: `<p>${'x'.repeat(5000)}</p>` },
        };
        const hidden = {
            ...ARTICLE,
            title: 'Hidden',
            slug: 'hidden',
            data: { ...ARTICLE.data, noindex: true },
        };
        const handler = makeHandler([ARTICLE, big, hidden], { fullEnabled: true, maxFullBytes: 600 });
        const res = await handler(new Request('https://example.com/api/cms/llms-full.txt'));
        const text = await res.text();
        expect(text).not.toContain('Hidden');
        expect(text).toContain('truncated');
    });
});
//# sourceMappingURL=llms-txt.test.js.map