import LiveSelector from "../../types/LiveSelector.js";
import CoordinateSelector from "../../types/CoordinateSelector.js";
import StrandSelector from "../../types/StrandSelector.js";
import WorldlineSelector from "../../types/WorldlineSelector.js";
import QueryError from "../../errors/QueryError.js";
import ObserverBasis from "./ObserverBasis.js";
/** Runtime-backed source/config plan for an observer reading. */
export default class ObserverPlan {
    #source;
    name;
    match;
    expose;
    redact;
    basis;
    sourceKind;
    constructor(fields) {
        const checkedFields = requireFields(fields);
        const source = WorldlineSelector.from(checkedFields.source).clone();
        this.#source = source;
        this.name = requireNonEmptyString(checkedFields.name, 'name');
        this.match = normalizeMatch(checkedFields.match);
        this.expose = normalizeOptionalStringList(checkedFields.expose, 'expose');
        this.redact = normalizeOptionalStringList(checkedFields.redact, 'redact');
        this.basis = requireBasis(checkedFields.basis);
        this.sourceKind = selectorToSource(source).kind;
        Object.freeze(this);
    }
    get source() {
        return selectorToSource(this.#source);
    }
    toConfig() {
        return {
            match: matchToConfigValue(this.match),
            ...(this.expose !== null ? { expose: [...this.expose] } : {}),
            ...(this.redact !== null ? { redact: [...this.redact] } : {}),
            ...(!this.basis.isEmpty() ? { basis: this.basis.toConfigValue() } : {}),
        };
    }
}
function requireFields(fields) {
    if (fields !== null && typeof fields === 'object') {
        return fields;
    }
    throw new QueryError('observer plan requires object fields', {
        code: 'E_OBSERVER_PLAN_FIELDS',
    });
}
function selectorToSource(source) {
    if (source instanceof LiveSelector) {
        return source.toDTO();
    }
    if (source instanceof CoordinateSelector) {
        return source.toDTO();
    }
    if (source instanceof StrandSelector) {
        return source.toDTO();
    }
    throw new QueryError(`unrecognized observer plan source kind: ${source.constructor.name}`, {
        code: 'E_OBSERVER_PLAN_SOURCE_UNKNOWN',
        context: { sourceKind: source.constructor.name },
    });
}
function normalizeMatch(match) {
    if (typeof match === 'string') {
        return match;
    }
    if (!Array.isArray(match) || match.length === 0) {
        throw new QueryError('observer plan match must be a string or non-empty string array', {
            code: 'E_OBSERVER_PLAN_MATCH',
            context: { field: 'match' },
        });
    }
    return freezeStringList(match, 'match');
}
function matchToConfigValue(match) {
    if (typeof match === 'string') {
        return match;
    }
    return [...match];
}
function normalizeOptionalStringList(values, field) {
    if (values === undefined) {
        return null;
    }
    return freezeStringList(values, field);
}
function freezeStringList(values, field) {
    if (!Array.isArray(values)) {
        throw new QueryError('observer plan field must be a string array', {
            code: 'E_OBSERVER_PLAN_FIELD',
            context: { field },
        });
    }
    const normalized = [];
    for (const value of values) {
        if (typeof value !== 'string' || value.length === 0) {
            throw new QueryError('observer plan field entries must be non-empty strings', {
                code: 'E_OBSERVER_PLAN_FIELD',
                context: { field },
            });
        }
        normalized.push(value);
    }
    return Object.freeze(normalized);
}
function requireBasis(basis) {
    if (basis instanceof ObserverBasis) {
        return basis;
    }
    throw new QueryError('observer plan requires an ObserverBasis', {
        code: 'E_OBSERVER_PLAN_BASIS',
    });
}
function requireNonEmptyString(value, field) {
    if (typeof value === 'string' && value.length > 0) {
        return value;
    }
    throw new QueryError('observer plan field must be a non-empty string', {
        code: 'E_OBSERVER_PLAN_STRING',
        context: { field },
    });
}
