// Faceting: the distinct-values table over wafer metadata — "what can I
// group/compare/split by?" — generalized from tsmap's own metadata.ts so any
// host gets this for free from data it's already attaching to
// `waferConfig.metadata` (WaferMetadata), rather than re-deriving it from a
// host-specific raw-field shape. Pure and DOM-free.
import { compareNatural } from '../core/utils.js';
/** Bucket label for wafers with no value for the active facet field — kept
 *  visible (not dropped) so grouped output stays honest about what it's missing. */
export const FACET_NONE_VALUE = '(none)';
/**
 * camelCase/snake_case key → "Title Case" label — the one label convention
 * every metadata surface in this library uses for an uncurated field
 * (`DEFAULT_FACET_CURATION`'s own `label`s, e.g. "Program", intentionally
 * differ and are reserved for facet/group-by UI, not general display — see
 * `buildFacetTable`'s callers in canvas-adapter). Shared by the canvas
 * (Summary panel, metadata badge) and stats (HTML report) layers so a field
 * is never labelled two different ways depending which surface rendered it.
 */
export { prettyKey } from '../core/utils.js';
/** Default curation for wmap's own known `WaferMetadata` keys. Callers extend
 *  (not replace) this via `buildFacetTable`'s `curation` option — their own
 *  app-specific extra keys layer on top rather than needing to redeclare these. */
export const DEFAULT_FACET_CURATION = {
    split: { label: 'Split' },
    lot: { label: 'Lot' },
    product: { label: 'Product' },
    testProgram: { label: 'Program' },
    temperature: { label: 'Temperature' },
    testDate: { label: 'Test date', date: true },
    operator: { label: 'Operator' },
    // Identity fields, not grouping axes: unique per wafer by definition, so
    // "splitting" on them just recreates one group per wafer — never useful.
    // Present (not hidden entirely) so an explicit `facetableOnly: false` call
    // can still see them, matching tsmap's own "known but not offered by
    // default" pattern for low-value fields.
    waferId: { label: 'Wafer', facet: false },
};
function dateOnly(value) {
    const t = value.indexOf('T');
    return t > 0 ? value.slice(0, t) : value;
}
/** The faceting value of one metadata key for one wafer's metadata — the raw
 *  value, except date-curated fields are truncated to date-only so grouping
 *  is by day, not timestamp. */
export function facetValueOf(metadata, key, curation = DEFAULT_FACET_CURATION) {
    const raw = metadata?.[key];
    if (raw === undefined || raw === null || raw === '')
        return undefined;
    const str = String(raw);
    return curation[key]?.date ? dateOnly(str) : str;
}
/** Fields present with the same value on every item's `metadata` — the
 *  conservative lot identity a mixed population can safely display. A field
 *  that varies per item (e.g. `waferId`) is naturally excluded, since it
 *  won't be common across all items. Returns `{}` for an empty list (no
 *  false lot-level claim about zero wafers). */
export function commonMetadata(items) {
    if (!items.length)
        return {};
    const first = items[0].metadata ?? {};
    const common = {};
    for (const [k, v] of Object.entries(first)) {
        if (v === null || v === undefined || v === '')
            continue;
        if (items.every(it => (it.metadata ?? {})[k] === v))
            common[k] = v;
    }
    return common;
}
/**
 * Build the distinct-values table over a set of wafers' metadata. One entry
 * per key present on at least one item's `metadata`; curated keys appear
 * first in `DEFAULT_FACET_CURATION`/`curation` order, uncurated keys after
 * (labelled by their raw key). Counts are exact over the full input.
 */
export function buildFacetTable(items, options = {}) {
    const curation = { ...DEFAULT_FACET_CURATION, ...options.curation };
    const facetableOnly = options.facetableOnly ?? true;
    const present = new Set();
    for (const item of items) {
        for (const key of Object.keys(item.metadata ?? {}))
            present.add(key);
    }
    const knownOrder = Object.keys(curation);
    const ordered = [
        ...knownOrder.filter(k => present.has(k)),
        ...[...present].filter(k => !(k in curation)),
    ];
    const table = [];
    for (const key of ordered) {
        const known = curation[key];
        if (facetableOnly && known && known.facet === false)
            continue;
        const byValue = new Map();
        for (const item of items) {
            const v = facetValueOf(item.metadata, key, curation) ?? FACET_NONE_VALUE;
            const entry = byValue.get(v) ?? { waferCount: 0, dieCount: 0 };
            entry.waferCount += 1;
            entry.dieCount += item.dieCount ?? 0;
            byValue.set(v, entry);
        }
        if (byValue.size === 0)
            continue;
        const values = Array.from(byValue, ([value, c]) => ({
            value, waferCount: c.waferCount, dieCount: c.dieCount,
        }));
        // `(none)` always sorts last (it's the residual bucket), regardless of size.
        values.sort((a, b) => (a.value === FACET_NONE_VALUE ? 1 : 0) - (b.value === FACET_NONE_VALUE ? 1 : 0) ||
            b.waferCount - a.waferCount ||
            compareNatural(a.value, b.value));
        table.push({ key, label: known?.label ?? key, values, splittable: values.length > 1 });
    }
    return table;
}
//# sourceMappingURL=facets.js.map