import { describe, expect, it } from 'vitest';
import { classifyLogoShape, logoShapeWarning, contrastRatio, isParseableColor, validateAccentColor, generateAdminThemeTokens, accentTokensToCssVars, ACCENT_PRESETS, siteInitials, resolveAdminBrand, resolvePublicBrand, getFaviconMetadata, parseAppearance, parseBrand, brandAssetIds, resolveBrand, DEFAULT_APPEARANCE, } from '../../appearance/index.js';
describe('classifyLogoShape', () => {
    it('classifies by aspect ratio', () => {
        expect(classifyLogoShape(100, 100)).toBe('square');
        expect(classifyLogoShape(120, 100)).toBe('square'); // ratio 1.2
        expect(classifyLogoShape(400, 100)).toBe('horizontal'); // ratio 4
        expect(classifyLogoShape(100, 400)).toBe('vertical'); // ratio 0.25
        expect(classifyLogoShape(160, 100)).toBe('neutral'); // ratio 1.6
    });
    it('returns unknown for missing/invalid dimensions', () => {
        expect(classifyLogoShape(undefined, undefined)).toBe('unknown');
        expect(classifyLogoShape(0, 100)).toBe('unknown');
        expect(classifyLogoShape(100, null)).toBe('unknown');
    });
});
describe('logoShapeWarning', () => {
    it('warns when a square is used as a primary logo', () => {
        expect(logoShapeWarning('publicPrimaryLogo', 'square')).toMatch(/square mark/i);
    });
    it('warns when a horizontal is used as a logo mark', () => {
        expect(logoShapeWarning('adminLogoMark', 'horizontal')).toMatch(/square mark works better/i);
    });
    it('warns when a non-square favicon source is uploaded', () => {
        expect(logoShapeWarning('faviconSource', 'horizontal')).toMatch(/not square/i);
    });
    it('warns about vertical logos in wide slots', () => {
        expect(logoShapeWarning('publicPrimaryLogo', 'vertical')).toMatch(/vertical/i);
    });
    it('is silent for good fits and unknown shapes', () => {
        expect(logoShapeWarning('publicPrimaryLogo', 'horizontal')).toBeNull();
        expect(logoShapeWarning('publicLogoMark', 'square')).toBeNull();
        expect(logoShapeWarning('publicPrimaryLogo', 'unknown')).toBeNull();
    });
});
describe('color + contrast', () => {
    it('computes the canonical black/white contrast as 21:1', () => {
        expect(contrastRatio('#000000', '#ffffff')).toBeCloseTo(21, 0);
    });
    it('returns 1 for identical colors', () => {
        expect(contrastRatio('#ffffff', '#ffffff')).toBeCloseTo(1, 5);
    });
    it('parses oklch and hex', () => {
        expect(isParseableColor('oklch(0.54 0.23 293)')).toBe(true);
        expect(isParseableColor('#abc')).toBe(true);
        expect(isParseableColor('rebeccapurple')).toBe(false);
        expect(isParseableColor('')).toBe(false);
    });
    it('ranks a dark oklch accent as high contrast against white', () => {
        // The purple preset on white should clear the 3:1 UI bar comfortably.
        expect(contrastRatio('oklch(0.54 0.23 293)', '#ffffff')).toBeGreaterThan(3);
    });
});
describe('validateAccentColor', () => {
    it('accepts every preset', () => {
        for (const name of Object.keys(ACCENT_PRESETS)) {
            expect(validateAccentColor(name).valid).toBe(true);
        }
    });
    it('rejects an unparseable color', () => {
        const r = validateAccentColor('not-a-color');
        expect(r.valid).toBe(false);
        expect(r.reason).toMatch(/valid hex or oklch/i);
    });
    it('rejects a custom color that fails contrast against white', () => {
        const r = validateAccentColor('#fff8dc'); // near-white, low contrast
        expect(r.valid).toBe(false);
        expect(r.reason).toMatch(/below the 3:1/i);
    });
    it('accepts a sufficiently dark custom color', () => {
        expect(validateAccentColor('#0a3d62').valid).toBe(true);
    });
});
describe('generateAdminThemeTokens', () => {
    it('returns preset tokens for light and dark', () => {
        expect(generateAdminThemeTokens({ adminAccentColor: 'blue' }, 'light')).toEqual(ACCENT_PRESETS.blue.light);
        expect(generateAdminThemeTokens({ adminAccentColor: 'blue' }, 'dark')).toEqual(ACCENT_PRESETS.blue.dark);
    });
    it('drives tokens from a custom color', () => {
        const tokens = generateAdminThemeTokens({ adminAccentColor: '#0a3d62' }, 'light');
        expect(tokens.brand).toBe('#0a3d62');
        expect(tokens.primary).toBe('#0a3d62');
    });
    it('falls back to purple for an unknown name', () => {
        expect(generateAdminThemeTokens({ adminAccentColor: 'chartreuse' }, 'light')).toEqual(ACCENT_PRESETS.purple.light);
    });
    it('maps tokens to the expected CSS variables', () => {
        const vars = accentTokensToCssVars(ACCENT_PRESETS.purple.light);
        expect(Object.keys(vars).sort()).toEqual([
            '--brand',
            '--brand-foreground',
            '--primary',
            '--primary-foreground',
            '--ring',
            '--sidebar-primary',
            '--sidebar-primary-foreground',
        ].sort());
    });
});
describe('siteInitials', () => {
    it('derives up to two initials', () => {
        expect(siteInitials('Actuate Media')).toBe('AM');
        expect(siteInitials('Actuate')).toBe('AC');
        expect(siteInitials('')).toBe('A');
        expect(siteInitials(undefined)).toBe('A');
    });
});
function assetMap(...assets) {
    return new Map(assets.map((a) => [a.id, a]));
}
const HORIZONTAL = {
    id: 'primary-1',
    publicUrl: 'https://cdn.example.com/logo.svg',
    mimeType: 'image/svg+xml',
    width: 400,
    height: 100,
};
const MARK = {
    id: 'mark-1',
    publicUrl: 'https://cdn.example.com/mark.svg',
    mimeType: 'image/svg+xml',
    width: 64,
    height: 64,
};
const FAVICON = {
    id: 'fav-1',
    publicUrl: 'https://cdn.example.com/favicon.png',
    mimeType: 'image/png',
    width: 32,
    height: 32,
};
describe('resolveAdminBrand', () => {
    it('inherits the public brand by default', () => {
        const brand = {
            mode: 'inheritPublicBrand',
            publicPrimaryLogoAssetId: 'primary-1',
            publicLogoMarkAssetId: 'mark-1',
        };
        const r = resolveAdminBrand(brand, assetMap(HORIZONTAL, MARK), 'Actuate Media');
        expect(r.inherited).toBe(true);
        expect(r.primary?.url).toBe(HORIZONTAL.publicUrl);
        expect(r.mark?.url).toBe(MARK.publicUrl);
        expect(r.primary?.shape).toBe('horizontal');
        expect(r.initials).toBe('AM');
    });
    it('prefers admin assets in customAdminBrand mode, falling back to public', () => {
        const adminPrimary = { ...HORIZONTAL, id: 'admin-primary' };
        const brand = {
            mode: 'customAdminBrand',
            adminPrimaryLogoAssetId: 'admin-primary',
            publicLogoMarkAssetId: 'mark-1',
        };
        const r = resolveAdminBrand(brand, assetMap(adminPrimary, MARK), 'Actuate Media');
        expect(r.inherited).toBe(false);
        expect(r.primary?.url).toBe(adminPrimary.publicUrl);
        // No admin mark → falls back to the public mark.
        expect(r.mark?.url).toBe(MARK.publicUrl);
    });
    it('falls back the collapsed mark to the favicon source', () => {
        const brand = {
            mode: 'inheritPublicBrand',
            publicPrimaryLogoAssetId: 'primary-1',
            faviconSourceAssetId: 'fav-1',
        };
        const r = resolveAdminBrand(brand, assetMap(HORIZONTAL, FAVICON), 'Actuate Media');
        expect(r.mark?.url).toBe(FAVICON.publicUrl);
    });
    it('returns null logos and initials when nothing is configured', () => {
        const r = resolveAdminBrand({ mode: 'inheritPublicBrand' }, assetMap(), 'Globex Corp');
        expect(r.primary).toBeNull();
        expect(r.mark).toBeNull();
        expect(r.initials).toBe('GC');
    });
});
describe('resolvePublicBrand', () => {
    it('resolves public assets and mark fallback', () => {
        const brand = {
            mode: 'customAdminBrand', // irrelevant to public resolution
            publicPrimaryLogoAssetId: 'primary-1',
            faviconSourceAssetId: 'fav-1',
        };
        const r = resolvePublicBrand(brand, assetMap(HORIZONTAL, FAVICON), 'Actuate Media');
        expect(r.primary?.url).toBe(HORIZONTAL.publicUrl);
        expect(r.mark?.url).toBe(FAVICON.publicUrl);
    });
});
describe('getFaviconMetadata', () => {
    it('returns null icons when no favicon source is set', () => {
        const r = getFaviconMetadata({ mode: 'inheritPublicBrand' }, assetMap());
        expect(r.icon).toBeNull();
        expect(r.apple).toBeNull();
    });
    it('cache-busts the icon URL from updatedAt', () => {
        const brand = {
            mode: 'inheritPublicBrand',
            faviconSourceAssetId: 'fav-1',
            updatedAt: '2026-02-03T00:00:00.000Z',
        };
        const r = getFaviconMetadata(brand, assetMap(FAVICON));
        expect(r.icon).toBe(`${FAVICON.publicUrl}?v=${Date.parse(brand.updatedAt)}`);
        expect(r.apple).toBe(r.icon);
        expect(r.type).toBe('image/png');
    });
});
describe('parse + ids', () => {
    it('parses appearance with defaults', () => {
        expect(parseAppearance({})).toEqual(DEFAULT_APPEARANCE);
        expect(parseAppearance({ appearance: { adminAccentColor: 'teal' } }).adminAccentColor).toBe('teal');
    });
    it('parses brand with defaults', () => {
        expect(parseBrand({}).mode).toBe('inheritPublicBrand');
    });
    it('collects unique asset ids', () => {
        const brand = {
            mode: 'customAdminBrand',
            publicPrimaryLogoAssetId: 'a',
            adminPrimaryLogoAssetId: 'a', // duplicate
            faviconSourceAssetId: 'b',
            generatedFaviconAssetIds: ['c', 'b'],
        };
        expect(brandAssetIds(brand).sort()).toEqual(['a', 'b', 'c']);
    });
});
describe('resolveBrand (async)', () => {
    it('reads the settings global and resolves every view', async () => {
        const db = {
            document: {
                findFirst: async () => ({
                    data: {
                        siteTitle: 'Actuate Media',
                        brand: {
                            mode: 'inheritPublicBrand',
                            publicPrimaryLogoAssetId: 'primary-1',
                            faviconSourceAssetId: 'fav-1',
                            updatedAt: '2026-02-03T00:00:00.000Z',
                        },
                    },
                }),
            },
            media: {
                findMany: async () => [
                    {
                        id: 'primary-1',
                        publicUrl: HORIZONTAL.publicUrl,
                        mimeType: 'image/svg+xml',
                        width: 400,
                        height: 100,
                    },
                    {
                        id: 'fav-1',
                        publicUrl: FAVICON.publicUrl,
                        mimeType: 'image/png',
                        width: 32,
                        height: 32,
                    },
                ],
            },
        };
        const r = await resolveBrand(db, null);
        expect(r.admin.primary?.url).toBe(HORIZONTAL.publicUrl);
        expect(r.public.primary?.url).toBe(HORIZONTAL.publicUrl);
        expect(r.favicon.icon).toContain(FAVICON.publicUrl);
        expect(r.admin.initials).toBe('AM');
    });
    it('degrades gracefully with no media table', async () => {
        const db = { document: { findFirst: async () => ({ data: {} }) } };
        const r = await resolveBrand(db, 'Globex');
        expect(r.admin.primary).toBeNull();
        expect(r.favicon.icon).toBeNull();
        expect(r.public.initials).toBe('GL');
    });
});
//# sourceMappingURL=appearance.test.js.map