/**
 * pipeline/profile-resolution.ts
 *
 * Resolves the correct weight profile for a given (mode, perspective, variant)
 * tuple. The scoring engine calls this to determine which dimensions and
 * weights apply to each test entry's composite score.
 *
 * Resolution order:
 *   1. Nested binding (variant provided):
 *      mode-profiles.<mode>.<variant>.<perspective> → profile name
 *   2. Flat binding (no variant):
 *      mode-profiles.<mode>.<perspective> → profile name
 *   3. Fallback: the "default" profile
 *
 * Supports both the new `profiles` format and the legacy flat `weights`
 * format (treated as a single profile named "default").
 *
 * @see docs/design-docs/named-scoring-profiles.md
 */
/**
 * Resolve all named profiles from a RubricConfig, normalizing the legacy
 * flat `weights` format into a `profiles` map with a single "default" entry.
 *
 * @returns A map of profile names → weight maps. Always includes "default".
 */
export function resolveProfiles(config) {
    if (config.profiles) {
        return config.profiles;
    }
    // Legacy format: flat weights → single "default" profile
    if (config.weights) {
        return { default: config.weights };
    }
    // Schema validation should prevent this, but be defensive
    throw new Error("rubrics config has neither 'profiles' nor 'weights' — cannot resolve scoring profiles");
}
/**
 * Resolve the weight profile for a specific (mode, perspective, variant) tuple.
 *
 * @param mode        - Canonical mode (e.g., "literacy", "mcp-server")
 * @param perspective - Entry perspective: "gold" (with docs) or "baseline" (without docs)
 * @param config      - Parsed rubrics config
 * @param variant     - Optional variant within the mode (e.g., "baseline", "agentic" for literacy)
 * @returns The resolved weight profile (dimension → weight map)
 *
 * @example
 *   // Nested: literacy mode with variant sub-keys
 *   resolveProfile("literacy", "gold", config, "baseline")     // → default profile
 *   resolveProfile("literacy", "baseline", config, "baseline")  // → output-only profile
 *   resolveProfile("literacy", "gold", config, "agentic")       // → default profile
 *
 *   // Flat: non-literacy modes
 *   resolveProfile("mcp-server", "gold", config)                // → mcp-behavior profile
 *   resolveProfile("unknown-mode", "gold", config)              // → default (fallback)
 */
export function resolveProfile(mode, perspective, config, variant) {
    const profiles = resolveProfiles(config);
    const modeProfiles = config["mode-profiles"];
    const modeEntry = modeProfiles?.[mode];
    let profileName;
    if (modeEntry && variant) {
        // Nested lookup: mode-profiles.<mode>.<variant>.<perspective>
        const variantEntry = modeEntry[variant];
        if (typeof variantEntry === "object" && variantEntry !== null) {
            profileName = variantEntry[perspective];
        }
    }
    if (!profileName && modeEntry) {
        // Flat lookup: mode-profiles.<mode>.<perspective>
        const directEntry = modeEntry[perspective];
        if (typeof directEntry === "string") {
            profileName = directEntry;
        }
    }
    if (profileName) {
        const profile = profiles[profileName];
        if (!profile) {
            throw new Error(`mode-profiles.${mode}.${variant ? variant + "." : ""}${perspective} references profile "${profileName}" ` +
                `which does not exist. Available profiles: ${Object.keys(profiles).join(", ")}`);
        }
        return profile;
    }
    // Fall back to "default" profile
    const defaultProfile = profiles["default"];
    if (!defaultProfile) {
        throw new Error(`No scoring profile found for mode="${mode}" perspective="${perspective}" ` +
            `and no "default" profile exists. ` +
            `Available profiles: ${Object.keys(profiles).join(", ")}`);
    }
    return defaultProfile;
}
