/**
 * @fileoverview Pure helpers for the Testing dashboard's AI-agent integration.
 *
 * These functions are intentionally free of Angular / component dependencies so
 * they can be unit-tested in isolation. The component
 * ({@link TestingDashboardComponent}) supplies a plain snapshot of its current
 * state and these helpers shape it into the key-value `AgentContext` object that
 * flows to the async chat agent and the realtime co-agent via
 * `NavigationService.SetAgentContext`.
 *
 * Mirrors the Data Explorer's agent-context depth (`data-explorer-agent-context.ts`):
 * everything on screen, bounded name lists, selected id+NAME, per-surface metrics,
 * and a mode-scoped tool surface keyed on the active tab.
 */
/** The four tabs the Testing dashboard supports. */
export const TESTING_TABS = ['dashboard', 'runs', 'analytics', 'review'];
/** The five status filters the Runs surface supports. */
export const TESTING_STATUS_FILTERS = ['all', 'running', 'passed', 'failed', 'error'];
/** The four time ranges the Runs surface supports. */
export const TESTING_TIME_RANGES = ['today', 'week', 'month', '90days'];
/** The two Review view modes (the queue vs. reviewed history). */
export const TESTING_REVIEW_VIEWS = ['queue', 'history'];
/**
 * Upper bound on how many names we publish in a name-list context field
 * (e.g. VisibleRunNames, TopFailingTests). Keeping the streamed note bounded
 * avoids flooding the co-agent with hundreds of names; when the underlying list
 * is larger we surface a companion total-count field instead. Mirrors the Data
 * Explorer's `AGENT_CONTEXT_RECORD_LIST_CAP` / `AGENT_CONTEXT_NAME_LIST_CAP`.
 */
export const TESTING_CONTEXT_LIST_CAP = 25;
/**
 * Upper bound on the analytics breakdown lists (top failing / slowest / most
 * expensive). These are already top-N on the service side, but we re-cap here so
 * the streamed note stays compact regardless of upstream changes.
 */
export const TESTING_CONTEXT_BREAKDOWN_CAP = 5;
/**
 * Type-guard / validator for a tab string. Keeps the `SwitchTestingTab` client
 * tool tolerant of arbitrary agent input — only the four known tabs are accepted.
 */
export function isValidTestingTab(tab) {
    return typeof tab === 'string' && TESTING_TABS.includes(tab);
}
/** Type-guard for a status filter string (used by `FilterTestsByStatus`). */
export function isValidTestingStatusFilter(status) {
    return typeof status === 'string' && TESTING_STATUS_FILTERS.includes(status);
}
/** Type-guard for a time-range string (used by `FilterTestsByTimeRange`). */
export function isValidTestingTimeRange(range) {
    return typeof range === 'string' && TESTING_TIME_RANGES.includes(range);
}
/**
 * Cap an array of names/strings to {@link TESTING_CONTEXT_LIST_CAP} (or a caller
 * override) entries. Pure + deterministic; never mutates the input. Tolerates a
 * non-finite cap by falling back to the default.
 */
export function capList(names, cap = TESTING_CONTEXT_LIST_CAP) {
    const safeCap = Number.isFinite(cap) && cap >= 0 ? Math.floor(cap) : TESTING_CONTEXT_LIST_CAP;
    return names.slice(0, safeCap);
}
/**
 * Resolve an agent-supplied run reference (either an exact run ID or a run name)
 * against the runs currently loaded on the Runs surface. The agent usually has
 * the ID from `GetTestRunDetails` / published context, but tolerantly accepts a
 * test name too. Pure + deterministic over the supplied `candidates`.
 *
 * Resolution order:
 *   1. exact id match (case-insensitive — UUIDs may arrive upper/lower-cased)
 *   2. exact test-name match (case-insensitive)
 *   3. first case-insensitive *contains* match on the test name
 *
 * @param reference - a run ID or a (partial) test name
 * @param candidates - the runs currently loaded on the Runs surface
 * @returns the matched run, or a clear error message
 */
export function resolveTestRunByReference(reference, candidates) {
    const needle = reference.trim().toLowerCase();
    if (!needle) {
        return { ok: false, error: 'Provide a test-run ID or a test name to select.' };
    }
    if (candidates.length === 0) {
        return { ok: false, error: 'No test runs are currently loaded in the Runs view to select from.' };
    }
    const byId = candidates.find(c => c.id.toLowerCase() === needle);
    if (byId) {
        return { ok: true, run: byId };
    }
    const byExactName = candidates.find(c => c.testName.toLowerCase() === needle);
    if (byExactName) {
        return { ok: true, run: byExactName };
    }
    const byContains = candidates.find(c => c.testName.toLowerCase().includes(needle));
    if (byContains) {
        return { ok: true, run: byContains };
    }
    const sample = candidates.slice(0, 5).map(c => c.testName).join(', ');
    return { ok: false, error: `No loaded test run matches "${reference}". Loaded runs include: ${sample}.` };
}
/** Build the always-on KPI slice shared by every tab. */
function buildKpiContext(input) {
    const context = {
        ActiveTab: input.ActiveTab,
        ActiveRunCount: input.ActiveRunCount,
        TotalTestsRun: input.TotalTestsRun,
        PassedCount: input.PassedCount,
        PassRate: roundTo(input.PassRate, 1),
        FailureCount: input.FailureCount,
        SkippedCount: input.SkippedCount,
        ActiveTestCount: input.ActiveTestCount,
        AverageRunDuration: Math.round(input.AverageRunDuration),
        TotalTestCost: roundTo(input.TotalTestCost, 2),
        PendingReviewCount: input.PendingReviewCount,
        PassRateTrend: roundTo(input.PassRateTrend, 1),
    };
    if (input.ActiveRunNames.length > 0) {
        context['ActiveRunNames'] = capList(input.ActiveRunNames);
    }
    return context;
}
/** Build the Runs-surface slice (selected run id+NAME, visible runs, filters). */
function buildRunsContext(runs) {
    const context = {
        StatusFilter: runs.StatusFilter,
        TimeRange: runs.TimeRange,
        SearchText: runs.SearchText,
        VisibleRunCount: runs.VisibleRunCount,
        SelectedRunId: runs.SelectedRunId,
        SelectedRunName: runs.SelectedRunName,
        DetailPanelOpen: runs.DetailPanelOpen,
    };
    if (runs.VisibleRunNames.length > 0) {
        context['VisibleRunNames'] = capList(runs.VisibleRunNames);
        if (runs.VisibleRunNames.length > TESTING_CONTEXT_LIST_CAP) {
            context['VisibleRunNamesTruncated'] = true;
        }
    }
    return context;
}
/** Build the Analytics-surface slice (window + breakdown name lists). */
function buildAnalyticsContext(a) {
    return {
        SelectedDays: a.SelectedDays,
        VersionCount: a.VersionCount,
        TopFailingTests: capList(a.TopFailingTests, TESTING_CONTEXT_BREAKDOWN_CAP),
        SlowestTests: capList(a.SlowestTests, TESTING_CONTEXT_BREAKDOWN_CAP),
        MostExpensiveTests: capList(a.MostExpensiveTests, TESTING_CONTEXT_BREAKDOWN_CAP),
    };
}
/** Build the Review-surface slice (queue depth, agreement, view mode). */
function buildReviewContext(r) {
    return {
        View: r.View,
        PendingCount: r.PendingCount,
        ReviewedCount: r.ReviewedCount,
        AverageHumanRating: roundTo(r.AverageHumanRating, 1),
        AgreementRate: roundTo(r.AgreementRate, 1),
        HistorySearchText: r.HistorySearchText,
    };
}
/**
 * Build the agent-visible context object for the Testing dashboard.
 *
 * Always publishes the KPI slice (active-run count, run totals, pass rate, cost,
 * average duration, pending-review count, trend). Additionally publishes the
 * detailed slice for the ACTIVE tab — the Runs surface's selected run id+name,
 * visible run names and filters; the Analytics surface's window + breakdown name
 * lists; or the Review surface's queue depth, agreement rate and view mode.
 *
 * Keeping this a pure function (no `this`) makes the context shape unit-testable
 * and decouples it from change-detection timing. Numbers are rounded to keep the
 * agent payload compact and human-readable.
 *
 * @param input - the component's current state snapshot
 * @returns a flat key-value object suitable for `SetAgentContext`
 */
export function buildTestingAgentContext(input) {
    const context = buildKpiContext(input);
    if (input.ActiveTab === 'runs' && input.Runs) {
        Object.assign(context, buildRunsContext(input.Runs));
    }
    else if (input.ActiveTab === 'analytics' && input.Analytics) {
        Object.assign(context, buildAnalyticsContext(input.Analytics));
    }
    else if (input.ActiveTab === 'review' && input.Review) {
        Object.assign(context, buildReviewContext(input.Review));
    }
    return context;
}
/** Round a number to a fixed number of decimal places, tolerating non-finite input. */
function roundTo(value, decimals) {
    if (!Number.isFinite(value)) {
        return 0;
    }
    const factor = Math.pow(10, decimals);
    return Math.round(value * factor) / factor;
}
//# sourceMappingURL=testing-agent-context.js.map