/**
 * Shared response extractors for Harness API responses.
 * Used across all toolset definitions — eliminates per-file duplication.
 */
import { isRecord } from "../utils/type-guards.js";
import { parseZipCsv } from "../utils/zip-csv.js";
/** Extract `data` from standard NG API responses: `{ status, data, ... }` */
export const ngExtract = (raw) => {
    if (raw === null || raw === undefined)
        return raw;
    const r = raw;
    return r.data ?? raw;
};
/** Extract paginated content from NG API responses: `{ data: { content, totalElements|totalItems } }` */
export const pageExtract = (raw) => {
    const r = raw;
    return {
        items: r.data?.content ?? [],
        total: r.data?.totalElements ?? r.data?.totalItems ?? 0,
    };
};
/** Pass-through extractor — returns raw response unchanged. Used for APIs that don't wrap in `data`. */
export const passthrough = (raw) => raw;
/**
 * STO Global Exemptions extractor.
 * API response: `{ exemptions: [...], pagination: { page, pageSize, totalPages, totalItems }, counts: {...} }`
 * Projects each exemption to a clean, display-friendly shape (issue title, severity, requester name,
 * target name, etc.) so the LLM picks the right columns and skips the opaque IDs. Normalized to the
 * standard `{ items, total, page, pageSize, totalPages, counts }` shape used by all other paginated
 * resources, with an explicit `_nextPageHint` so pagination can't be misinterpreted.
 */
export const stoExemptionsExtract = (raw, input) => {
    const r = raw;
    const page = r.pagination?.page ?? 0;
    const pageSize = r.pagination?.pageSize ?? 5;
    const totalPages = r.pagination?.totalPages ?? 0;
    const total = r.pagination?.totalItems ?? (r.exemptions?.length ?? 0);
    const hasMore = page + 1 < totalPages;
    const exemptions = r.exemptions ?? [];
    const items = exemptions.map((e) => ({
        issue_title: e.issueSummary?.title,
        severity: e.issueSummary?.severityCode,
        type: e.type,
        status: e.status,
        requested_by: e.requesterName,
        target: e.targetName || undefined,
        scope: e.scope,
        reason: e.reason || undefined,
        approved_by: e.approverName || undefined,
        created_at: e.created,
        expires_at: e.expiration,
        occurrences: e.numOccurrences,
    }));
    // Keep IDs OUT of the items so the LLM can't accidentally render them as a column.
    // Provide them in a separate lookup keyed by row index (1-based) for approve/reject actions.
    const _action_id_by_row = {};
    exemptions.forEach((e, idx) => { if (e.id)
        _action_id_by_row[idx + 1] = e.id; });
    // Reconstruct the active filter set from the actual request input so the
    // next-page hint paginates the SAME query. Dropping any of these would
    // switch the underlying dataset on the next call (Cursor review feedback).
    const filterKeys = ["status", "search"];
    const activeFilters = {};
    if (input) {
        for (const key of filterKeys) {
            const v = input[key];
            if (v !== undefined && v !== "" && v !== null)
                activeFilters[key] = v;
        }
    }
    const filterJson = JSON.stringify({ ...activeFilters, page: page + 1, size: pageSize });
    return {
        items,
        total,
        page,
        pageSize,
        totalPages,
        counts: r.counts,
        _action_id_by_row,
        _display_hint: "Render a compact table with columns: # | Issue Title | Severity | Type | Requested by | Target | Status. NEVER add an 'ID' column — the items contain no ID field by design. If the user asks to approve/reject row N, look up the ID in _action_id_by_row[N].",
        _nextPageHint: hasMore
            ? `For the next page, call harness_list with resource_type='security_exemption' and filters=${filterJson}. You MUST keep size=${pageSize} and ALL other filters identical — the backend computes offset = page × size, so changing size or dropping filters silently shifts the dataset. Pages remaining: ${totalPages - page - 1}.`
            : "No more pages — all exemptions have been returned.",
    };
};
/**
 * AI Evals control plane — paginated list: `{ data, page, limit, total_elements }`.
 */
export const aiEvalsListExtract = (raw) => {
    const r = raw;
    return {
        items: r.data ?? [],
        total: r.total_elements ?? 0,
    };
};
/**
 * AI Evals — bare array response (e.g. suite evaluations, metric set entries list).
 */
export const aiEvalsArrayExtract = (raw) => {
    const arr = Array.isArray(raw) ? raw : [];
    return { items: arr, total: arr.length };
};
/**
 * SCS-specific extractor — strips null, undefined, empty string, empty array,
 * and empty object fields recursively from API responses. SCS payloads contain
 * ~40% empty/null fields; removing them yields significant token savings.
 */
export const scsCleanExtract = (raw) => {
    return stripEmptyFields(raw);
};
/**
 * Factory: SCS list extractor that strips empty fields AND selects only specified
 * fields from each item. Builds on scsCleanExtract to further reduce token usage
 * for list responses by keeping only actionable fields (IDs, names, counts, scores).
 *
 * @param fields - field names to retain from each list item
 */
export const scsListExtract = (fields) => (raw) => {
    const cleaned = stripEmptyFields(raw);
    if (!Array.isArray(cleaned))
        return cleaned;
    return cleaned.map(item => {
        if (item !== null && typeof item === "object" && !Array.isArray(item)) {
            return pickFields(item, fields);
        }
        return item;
    });
};
function pickFields(obj, fields) {
    const result = {};
    for (const field of fields) {
        if (field in obj && obj[field] !== undefined) {
            result[field] = obj[field];
        }
    }
    return result;
}
function stripEmptyFields(obj) {
    if (Array.isArray(obj))
        return obj.map(stripEmptyFields);
    if (obj !== null && typeof obj === "object") {
        const result = {};
        for (const [key, value] of Object.entries(obj)) {
            if (value === null || value === undefined)
                continue;
            if (value === "")
                continue;
            if (Array.isArray(value) && value.length === 0)
                continue;
            const cleaned = stripEmptyFields(value);
            if (typeof cleaned === "object" && cleaned !== null && !Array.isArray(cleaned)
                && Object.keys(cleaned).length === 0)
                continue;
            result[key] = cleaned;
        }
        return result;
    }
    return obj;
}
/**
 * Factory for HAR (Artifact Registry) list responses.
 * HAR wraps lists as `{ data: { <arrayKey>: [...], itemCount, pageIndex, ... }, status }`.
 * Normalizes to `{ items, total, pageIndex, pageSize, pageCount }` so the deep link
 * code can find the list via `LIST_ARRAY_KEYS`.
 */
export const harListExtract = (arrayKey) => (raw) => {
    const r = raw;
    const data = r.data;
    if (data && typeof data === "object" && !Array.isArray(data)) {
        const d = data;
        return {
            items: d[arrayKey] ?? [],
            total: d.itemCount ?? 0,
            pageIndex: d.pageIndex,
            pageSize: d.pageSize,
            pageCount: d.pageCount,
        };
    }
    return raw;
};
/**
 * Factory for v1 list responses (bare arrays).
 * If `wrapperKey` is provided, each item is unwrapped: `{ project: {...} }` → `{...}`.
 * Total is derived from array length since response headers aren't accessible.
 */
export const v1ListExtract = (wrapperKey) => (raw) => {
    const arr = Array.isArray(raw) ? raw : [];
    const items = wrapperKey
        ? arr.map(item => (isRecord(item) && wrapperKey in item ? item[wrapperKey] : item))
        : arr;
    return { items, total: items.length };
};
/** Factory for v1 single-item responses that may be wrapped: `{ org: {...} }` → `{...}`. */
export const v1Unwrap = (wrapperKey) => (raw) => {
    if (isRecord(raw) && wrapperKey in raw) {
        return raw[wrapperKey];
    }
    return raw;
};
/**
 * Organization GET/POST/PUT responses: unwrap standard NG `{ status, data: { organization } }`
 * and prefer `organization` over legacy `org` when both could appear.
 */
export const unwrapOrgResponse = (raw) => {
    const inner = ngExtract(raw);
    if (isRecord(inner)) {
        if ("organization" in inner && inner.organization !== null && typeof inner.organization === "object") {
            return inner.organization;
        }
        if ("org" in inner && inner.org !== null && typeof inner.org === "object") {
            return inner.org;
        }
    }
    return inner;
};
/**
 * Project GET/POST/PUT responses: unwrap NG `{ status, data: { project } }` → project entity.
 */
export const unwrapProjectResponse = (raw) => {
    const inner = ngExtract(raw);
    if (isRecord(inner) && "project" in inner && inner.project !== null && typeof inner.project === "object") {
        return inner.project;
    }
    return inner;
};
/**
 * Project LIST responses: unwrap NG `{ data: { content: [{ project: {...} }, ...] } }`.
 * Each item in `content` is wrapped in a `{ project: {...} }` envelope — unwrap to
 * expose `identifier`, `name`, `orgIdentifier` directly on each item.
 */
export const projectListExtract = (raw) => {
    const r = raw;
    const rawItems = r.data?.content ?? [];
    const items = rawItems.map(item => {
        if (isRecord(item) && "project" in item && item.project !== null && typeof item.project === "object") {
            return item.project;
        }
        return item;
    });
    return {
        items,
        total: r.data?.totalElements ?? r.data?.totalItems ?? 0,
    };
};
/** Factory for GraphQL field extraction (used by CCM). */
export const gqlExtract = (field) => (raw) => {
    const r = raw;
    return r.data?.[field] ?? raw;
};
/**
 * Extracts the runtime input template from the Harness pipeline template endpoint.
 * Unwraps `data.inputSetTemplateYaml`, `data.hasInputSets`, `data.modules`, and adds
 * a `_hint` field describing whether inputs are required.
 */
export const runtimeInputExtract = (raw) => {
    const r = raw;
    return {
        inputSetTemplateYaml: r.data?.inputSetTemplateYaml ?? null,
        hasInputSets: r.data?.hasInputSets ?? false,
        modules: r.data?.modules ?? [],
        _hint: r.data?.inputSetTemplateYaml
            ? "This YAML template shows all runtime inputs needed. Fields with '<+input>' are required. Pass matching key-value pairs to harness_execute(action='run', inputs={...})."
            : "This pipeline has no runtime inputs. You can execute it without providing any inputs.",
    };
};
/**
 * Extracts CCM list responses with views/totalCount structure.
 * Maps `data.views` → `items` and `data.totalCount` → `total`.
 * Used by multiple CCM APIs that return this response pattern.
 */
export const ccmViewsExtract = (raw) => {
    const r = raw;
    return {
        items: r.data?.views ?? [],
        total: r.data?.totalCount ?? 0,
    };
};
/**
 * Extracts CCM cost breakdown data from GraphQL perspectiveGrid response.
 * Maps `data.perspectiveGrid.data` → `items` and `data.perspectiveTotalCount` → `total`.
 */
export const ccmBreakdownExtract = (raw) => {
    const r = raw;
    return {
        items: r.data?.perspectiveGrid?.data ?? [],
        total: r.data?.perspectiveTotalCount ?? 0,
    };
};
/**
 * Extracts CCM cost time series stats from GraphQL perspectiveTimeSeriesStats response.
 * Returns the `stats` array from `data.perspectiveTimeSeriesStats.stats`.
 */
export const ccmTimeseriesExtract = (raw) => {
    const r = raw;
    return r.data?.perspectiveTimeSeriesStats?.stats ?? [];
};
/**
 * Extracts CCM cost summary from a dual-mode GraphQL response.
 * When `data.ccmMetaData` is present (metadata query), returns it directly.
 * Otherwise returns `{ trendStats, forecastCost }` for a perspective summary query.
 */
export const ccmSummaryExtract = (raw) => {
    const r = raw;
    if (!r.data)
        return raw;
    if (r.data.ccmMetaData)
        return r.data.ccmMetaData;
    return {
        trendStats: r.data.perspectiveTrendStats,
        forecastCost: r.data.perspectiveForecastCost,
    };
};
/**
 * Extracts CCM perspective-scoped recommendations from GraphQL response.
 * Returns `{ items, stats }` from `data.recommendationsV2` and `data.recommendationStatsV2`.
 */
export const ccmRecommendationsExtract = (raw) => {
    const r = raw;
    return {
        items: r.data?.recommendationsV2?.items ?? [],
        stats: r.data?.recommendationStatsV2,
    };
};
/** Extract dashboard list response: `{ items, pages, resource }` */
export const dashboardListExtract = (raw) => {
    const r = raw;
    return {
        items: r.resource ?? [],
        total: r.items ?? 0,
    };
};
/**
 * Extracts dashboard data from a ZIP ArrayBuffer containing CSVs.
 * Matches v1 `get_dashboard_data` behavior: ZIP → CSV → structured JSON tables.
 */
export const dashboardDataExtract = (raw) => {
    if (raw instanceof ArrayBuffer) {
        return parseZipCsv(raw);
    }
    return raw;
};
// ---------------------------------------------------------------------------
// Chaos Engineering extractors
// ---------------------------------------------------------------------------
/**
 * Extract chaos paginated list response: { data: [...], pagination: { totalItems } }
 * Used by chaos experiments and templates.
 */
export const chaosPageExtract = (raw) => {
    const r = raw;
    return {
        items: r.data ?? [],
        total: r.pagination?.totalItems ?? (Array.isArray(r.data) ? r.data.length : 0),
    };
};
/**
 * Extract chaos probe list response: { totalNoOfProbes, data: [...] }
 */
export const chaosProbeListExtract = (raw) => {
    const r = raw;
    return {
        items: r.data ?? [],
        total: r.totalNoOfProbes ?? (Array.isArray(r.data) ? r.data.length : 0),
    };
};
/**
 * Extract chaos infrastructure list response: { totalNoOfInfras, infras: [...] }
 */
export const chaosInfraListExtract = (raw) => {
    const r = raw;
    return {
        items: r.infras ?? [],
        total: r.totalNoOfInfras ?? (Array.isArray(r.infras) ? r.infras.length : 0),
    };
};
/**
 * Extract chaos K8s infrastructure list response:
 * { infras: [...], totalNoOfInfrastructures, pagination }
 */
export const chaosK8sInfraListExtract = (raw) => {
    const r = raw;
    return {
        items: r.infras ?? [],
        total: r.totalNoOfInfrastructures ?? (Array.isArray(r.infras) ? r.infras.length : 0),
    };
};
/**
 * Extract chaos hub list response: { items: [...], pagination: { totalItems } }
 */
export const chaosHubListExtract = (raw) => {
    const r = raw;
    return {
        items: r.items ?? [],
        total: r.pagination?.totalItems ?? (Array.isArray(r.items) ? r.items.length : 0),
    };
};
/** Extract chaos DR test list response: { drtests: [...] } */
export const chaosDRTestListExtract = (raw) => {
    const r = raw;
    const items = r.drtests ?? [];
    return { items, total: items.length };
};
// ---------------------------------------------------------------------------
// Feature Management Enterprise (FME) extractors
// ---------------------------------------------------------------------------
/**
 * Flattens `trafficType.id` → `trafficTypeId` at the top level of an FME item.
 * Enables deep link templates to reference `trafficTypeId` directly.
 */
export function flattenTrafficType(item) {
    const tt = item.trafficType;
    if (tt && typeof tt === "object" && !Array.isArray(tt)) {
        const ttRecord = tt;
        if (ttRecord.id !== undefined && item.trafficTypeId === undefined) {
            item.trafficTypeId = ttRecord.id;
        }
    }
}
/** Extract FME feature flag list — passthrough with trafficType.id flattened on each item. */
export const fmeListExtract = (raw) => {
    if (raw && typeof raw === "object") {
        const r = raw;
        const objects = r.objects;
        if (Array.isArray(objects)) {
            for (const item of objects) {
                if (item && typeof item === "object") {
                    flattenTrafficType(item);
                }
            }
        }
    }
    return raw;
};
/** Extract FME feature flag single item — passthrough with trafficType.id flattened. */
export const fmeGetExtract = (raw) => {
    if (raw && typeof raw === "object") {
        flattenTrafficType(raw);
    }
    return raw;
};
//# sourceMappingURL=extractors.js.map