/**
 * Brand resolution.
 *
 * Turns stored {@link BrandSettings} (asset IDs) + Media Center rows into the
 * concrete logo/favicon URLs the admin shell, public site, and Next.js
 * metadata consume. Resolution encodes the fallback chains from the spec so
 * callers never hardcode logo paths and always degrade gracefully to site
 * initials.
 *
 * The pure functions (`resolveAdminBrand`, `resolvePublicBrand`,
 * `getFaviconMetadata`, `siteInitials`) take an already-fetched asset map and
 * are unit-tested; the async gatherers read the `settings` global + media rows.
 */
import { classifyLogoShape } from './logo-shape.js';
export const SETTINGS_GLOBAL_COLLECTION = 'settings';
export const DEFAULT_APPEARANCE = {
    adminColorModeDefault: 'system',
    adminAccentColor: 'purple',
    sidebarDensity: 'default',
    contentFont: 'DM Sans',
    reduceMotion: false,
};
export const DEFAULT_BRAND = {
    mode: 'inheritPublicBrand',
};
/** Up to two uppercase initials derived from the site title. */
export function siteInitials(siteTitle) {
    const cleaned = (siteTitle ?? '').trim();
    if (!cleaned)
        return 'A';
    const words = cleaned.split(/\s+/).filter(Boolean);
    if (words.length === 1) {
        return words[0].slice(0, 2).toUpperCase();
    }
    return (words[0][0] + words[1][0]).toUpperCase();
}
function toResolvedLogo(asset, alt) {
    if (!asset || !asset.publicUrl)
        return null;
    return {
        url: asset.publicUrl,
        alt,
        shape: classifyLogoShape(asset.width ?? undefined, asset.height ?? undefined),
        width: asset.width ?? undefined,
        height: asset.height ?? undefined,
    };
}
function altFor(brand, siteTitle) {
    return brand.defaultLogoAltText?.trim() || (siteTitle ?? '').trim() || 'Logo';
}
/**
 * Resolve the admin sidebar brand with the spec's fallback chains.
 *
 * inheritPublicBrand → admin uses the public assets.
 * customAdminBrand   → admin-specific assets win, falling back to public.
 * Collapsed mark falls back to favicon source, then site initials.
 */
export function resolveAdminBrand(brand, assets, siteTitle) {
    const alt = altFor(brand, siteTitle);
    const get = (id) => (id ? assets.get(id) : undefined);
    const custom = brand.mode === 'customAdminBrand';
    const primaryAsset = custom
        ? (get(brand.adminPrimaryLogoAssetId) ?? get(brand.publicPrimaryLogoAssetId))
        : get(brand.publicPrimaryLogoAssetId);
    const markAsset = custom
        ? (get(brand.adminLogoMarkAssetId) ??
            get(brand.publicLogoMarkAssetId) ??
            get(brand.faviconSourceAssetId))
        : (get(brand.publicLogoMarkAssetId) ?? get(brand.faviconSourceAssetId));
    const darkAsset = custom
        ? (get(brand.adminDarkLogoAssetId) ?? get(brand.publicDarkLogoAssetId))
        : get(brand.publicDarkLogoAssetId);
    return {
        primary: toResolvedLogo(primaryAsset, alt),
        mark: toResolvedLogo(markAsset, alt),
        dark: toResolvedLogo(darkAsset, alt),
        initials: siteInitials(siteTitle),
        inherited: !custom,
    };
}
/** Resolve the public site brand (header/footer/nav). */
export function resolvePublicBrand(brand, assets, siteTitle) {
    const alt = altFor(brand, siteTitle);
    const get = (id) => (id ? assets.get(id) : undefined);
    return {
        primary: toResolvedLogo(get(brand.publicPrimaryLogoAssetId), alt),
        mark: toResolvedLogo(get(brand.publicLogoMarkAssetId) ?? get(brand.faviconSourceAssetId), alt),
        dark: toResolvedLogo(get(brand.publicDarkLogoAssetId), alt),
        initials: siteInitials(siteTitle),
    };
}
/**
 * Favicon metadata for Next.js `metadata.icons`. Appends a cache-busting
 * `?v=` token derived from the brand's `updatedAt` so changing the favicon
 * invalidates the browser/CDN copy.
 */
export function getFaviconMetadata(brand, assets) {
    const source = brand.faviconSourceAssetId ? assets.get(brand.faviconSourceAssetId) : undefined;
    const generated = (brand.generatedFaviconAssetIds ?? [])
        .map((id) => assets.get(id)?.publicUrl)
        .filter((u) => Boolean(u));
    if (!source || !source.publicUrl) {
        return { icon: null, apple: null, type: null, generated };
    }
    const version = brand.updatedAt ? Date.parse(brand.updatedAt) : null;
    const bust = version && !Number.isNaN(version) ? `?v=${version}` : '';
    const url = `${source.publicUrl}${bust}`;
    return { icon: url, apple: url, type: source.mimeType || null, generated };
}
async function readSettingsBlob(db) {
    const d = db;
    if (!d?.document?.findFirst)
        return {};
    const doc = await d.document.findFirst({
        where: { collection: SETTINGS_GLOBAL_COLLECTION, deletedAt: null },
    });
    const data = doc?.data;
    return data && typeof data === 'object' ? data : {};
}
export function parseAppearance(blob) {
    const a = blob.appearance ?? {};
    return {
        adminColorModeDefault: a.adminColorModeDefault ?? DEFAULT_APPEARANCE.adminColorModeDefault,
        adminAccentColor: a.adminAccentColor ?? DEFAULT_APPEARANCE.adminAccentColor,
        sidebarDensity: a.sidebarDensity ?? DEFAULT_APPEARANCE.sidebarDensity,
        contentFont: a.contentFont ?? DEFAULT_APPEARANCE.contentFont,
        reduceMotion: a.reduceMotion ?? DEFAULT_APPEARANCE.reduceMotion,
        updatedAt: a.updatedAt,
        updatedBy: a.updatedBy,
    };
}
export function parseBrand(blob) {
    const b = blob.brand ?? {};
    return { ...DEFAULT_BRAND, ...b };
}
/** Collect every asset ID referenced by a brand settings record. */
export function brandAssetIds(brand) {
    const ids = [
        brand.publicPrimaryLogoAssetId,
        brand.publicLogoMarkAssetId,
        brand.publicDarkLogoAssetId,
        brand.adminPrimaryLogoAssetId,
        brand.adminLogoMarkAssetId,
        brand.adminDarkLogoAssetId,
        brand.faviconSourceAssetId,
        ...(brand.generatedFaviconAssetIds ?? []),
    ].filter((id) => Boolean(id));
    return Array.from(new Set(ids));
}
function mediaRowToAsset(row) {
    const id = typeof row.id === 'string' ? row.id : null;
    if (!id)
        return null;
    const publicUrl = (typeof row.publicUrl === 'string' && row.publicUrl) ||
        (typeof row.storageKey === 'string' && /^(https?:\/\/|\/)/.test(row.storageKey)
            ? row.storageKey
            : '');
    return {
        id,
        publicUrl: publicUrl || '',
        mimeType: typeof row.mimeType === 'string' ? row.mimeType : '',
        width: typeof row.width === 'number' ? row.width : null,
        height: typeof row.height === 'number' ? row.height : null,
        fileName: typeof row.filename === 'string' ? row.filename : null,
        sizeBytes: typeof row.fileSize === 'number' ? row.fileSize : null,
    };
}
/** Fetch the referenced media rows and key them by asset ID. */
export async function gatherBrandAssets(db, ids) {
    const map = new Map();
    if (ids.length === 0)
        return map;
    const d = db;
    if (!d?.media?.findMany)
        return map;
    const rows = await d.media.findMany({ where: { id: { in: ids }, deletedAt: null } });
    for (const row of rows) {
        const asset = mediaRowToAsset(row);
        if (asset)
            map.set(asset.id, asset);
    }
    return map;
}
export async function getAppearanceSettings(db) {
    return parseAppearance(await readSettingsBlob(db));
}
export async function getBrandSettings(db) {
    return parseBrand(await readSettingsBlob(db));
}
/**
 * One-shot resolver used by the public brand endpoint and the admin shell:
 * read the settings blob, gather referenced assets, and resolve every view.
 */
export async function resolveBrand(db, siteTitle) {
    const blob = await readSettingsBlob(db);
    const brand = parseBrand(blob);
    const title = siteTitle ?? (typeof blob.siteTitle === 'string' ? blob.siteTitle : null);
    const assets = await gatherBrandAssets(db, brandAssetIds(brand));
    return {
        brand,
        admin: resolveAdminBrand(brand, assets, title),
        public: resolvePublicBrand(brand, assets, title),
        favicon: getFaviconMetadata(brand, assets),
        assets: Array.from(assets.values()),
        siteTitle: title ?? null,
    };
}
//# sourceMappingURL=resolve.js.map