// Copyright (c) 2026 Modelware. All rights reserved.

import { getTemplateById, type TemplateCatalog } from './catalog.js';
import type { TemplateDefinition, TemplateNavigationExposure, TemplateResolutionCandidate, TemplateResolutionResult } from './types.js';

export function resolveTemplateById(catalog: TemplateCatalog, templateId: string): TemplateDefinition | undefined {
    return getTemplateById(catalog, templateId);
}

export function resolveTemplateForNavigation(
    catalog: TemplateCatalog,
    rdfTypes: ReadonlyArray<string>
): TemplateResolutionResult {
    const candidates = collectNavigationCandidates(catalog, rdfTypes);
    const template = selectBestTemplate(candidates);
    return { template, candidates };
}

function collectNavigationCandidates(
    catalog: TemplateCatalog,
    rdfTypes: ReadonlyArray<string>
): TemplateResolutionCandidate[] {
    const normalizedTypes = [...new Set(rdfTypes.map(normalizeIri).filter((value): value is string => value.length > 0))];
    const normalizedTypeSet = new Set(normalizedTypes);
    const results: TemplateResolutionCandidate[] = [];
    for (const template of catalog.all) {
        const exposures = template.exposures?.filter((exposure): exposure is TemplateNavigationExposure => exposure.kind === 'navigation') ?? [];
        if (exposures.length === 0) {
            continue;
        }
        let bestScoreForTemplate: number | undefined;
        for (const exposure of exposures) {
            const score = scoreNavigationExposure(exposure, normalizedTypeSet);
            if (score === undefined) {
                continue;
            }
            if (bestScoreForTemplate === undefined || score > bestScoreForTemplate) {
                bestScoreForTemplate = score;
            }
        }
        if (bestScoreForTemplate === undefined) {
            continue;
        }
        const rank = Number.isFinite(template.rank) ? Number(template.rank) : 0;
        results.push({ template, score: bestScoreForTemplate + rank });
    }
    return results.sort((left, right) => right.score - left.score || left.template.id.localeCompare(right.template.id));
}

function scoreNavigationExposure(
    exposure: TemplateNavigationExposure,
    rdfTypeSet: ReadonlySet<string>
): number | undefined {
    const anyTypeOf = (exposure.match?.anyTypeOf ?? []).map(normalizeIri).filter((value): value is string => value.length > 0);
    const allTypesOf = (exposure.match?.allTypesOf ?? []).map(normalizeIri).filter((value): value is string => value.length > 0);

    if (allTypesOf.length > 0 && allTypesOf.some((iri) => !rdfTypeSet.has(iri))) {
        return undefined;
    }
    if (anyTypeOf.length > 0 && !anyTypeOf.some((iri) => rdfTypeSet.has(iri))) {
        return undefined;
    }
    if (allTypesOf.length === 0 && anyTypeOf.length === 0) {
        return 0;
    }
    return (allTypesOf.length * 10) + anyTypeOf.length;
}

function selectBestTemplate(candidates: ReadonlyArray<TemplateResolutionCandidate>): TemplateDefinition | undefined {
    return candidates.length > 0 ? candidates[0].template : undefined;
}

function normalizeIri(value: string): string {
    return value.trim().replace(/^<|>$/g, '');
}
