/**
 * Field library mutation helpers
 *
 * Pure, framework-free functions that add / edit / remove a reusable custom
 * field definition on a WorkspaceConfig. They return a new config object (the
 * input is never mutated) so the HTTP layer can persist the result through
 * `Workspace.writeConfig()`.
 *
 * Deliberately independent of SchemaRegistry / Zod entity validation: the
 * field library is *soft* metadata (it drives the picker + agent awareness),
 * not an enforced schema. Applying a field to an entry is an ordinary
 * frontmatter write elsewhere; nothing here touches entity validation.
 *
 * Identity is `key`, global across the workspace (one `phase` everywhere).
 * Editing options ripples to every entry using the field; existing entry
 * values are never touched (removal is non-destructive — orphaned values stay
 * on disk and simply stop being offered).
 */
import { CustomFieldDefSchema } from './types.js';
/** A mutation that fails a precondition (not found, collision, bad shape). */
export class FieldLibraryError extends Error {
    code;
    constructor(message, 
    /** Stable machine code so the route maps to a status without string-matching. */
    code) {
        super(message);
        this.code = code;
        this.name = 'FieldLibraryError';
    }
}
/** Field key shape: lowercase, snake_case, must start with a letter. Mirrors
 *  the client's `normalizedKey` normalization. */
const KEY_RE = /^[a-z][a-z0-9_]*$/;
/** Current field library (empty when none defined). */
export function getFieldLibrary(config) {
    return config.field_library ?? [];
}
/** Look up one definition by key. */
export function getFieldDef(config, key) {
    return getFieldLibrary(config).find(f => f.key === key);
}
/**
 * Validate a field definition's shape. Enforces the key shape and, for select
 * fields, a non-empty list of unique, non-empty options. Throws
 * FieldLibraryError on failure. Returns the parsed definition.
 */
export function validateFieldDef(input) {
    const parsed = CustomFieldDefSchema.safeParse(input);
    if (!parsed.success) {
        throw new FieldLibraryError(`Invalid field definition: ${parsed.error.issues.map(i => i.message).join('; ')}`, 'invalid_field');
    }
    const def = parsed.data;
    if (!KEY_RE.test(def.key)) {
        throw new FieldLibraryError(`Field key "${def.key}" must be lowercase snake_case and start with a letter`, 'invalid_field');
    }
    validateOptions(def.options);
    return def;
}
/** Shared option-list validation (non-empty, no blanks, unique). */
function validateOptions(options) {
    if (options.length === 0) {
        throw new FieldLibraryError('A Select field needs at least one option', 'invalid_field');
    }
    if (options.some(v => v.trim() === '')) {
        throw new FieldLibraryError('Select options cannot be empty', 'invalid_field');
    }
    if (new Set(options).size !== options.length) {
        throw new FieldLibraryError('Select options must be unique', 'invalid_field');
    }
}
/** Deep-clone the library so callers never mutate the live config. */
function cloneLibrary(config) {
    return structuredClone(config.field_library ?? []);
}
/**
 * Add a new field definition. Returns a new WorkspaceConfig.
 * Rejects a key already used by another definition.
 */
export function addFieldDef(config, input) {
    const def = validateFieldDef({
        key: input.key,
        type: 'select',
        options: input.options,
        ...(input.label !== undefined ? { label: input.label } : {}),
        ...(input.created_by !== undefined ? { created_by: input.created_by } : {}),
        created_at: new Date().toISOString(),
    });
    if (getFieldDef(config, def.key)) {
        throw new FieldLibraryError(`A field named "${def.key}" already exists`, 'key_exists');
    }
    const library = [...cloneLibrary(config), def];
    return { ...config, field_library: library };
}
/**
 * Update an existing definition's options and/or label. The key is the
 * identity and is not changed (renaming a key = add + remove, since it would
 * orphan existing usages). Returns a new WorkspaceConfig.
 */
export function updateFieldDef(config, key, patch) {
    const current = getFieldDef(config, key);
    if (!current) {
        throw new FieldLibraryError(`No field named "${key}"`, 'key_not_found');
    }
    if (patch.options !== undefined)
        validateOptions(patch.options);
    const next = {
        ...current,
        ...(patch.options !== undefined ? { options: patch.options } : {}),
        ...(patch.label !== undefined ? { label: patch.label } : {}),
    };
    const library = cloneLibrary(config).map(f => (f.key === key ? next : f));
    return { ...config, field_library: library };
}
/**
 * Remove a definition from the library. Returns a new WorkspaceConfig.
 * Non-destructive to entries — any stored values stay in their frontmatter.
 */
export function removeFieldDef(config, key) {
    if (!getFieldDef(config, key)) {
        throw new FieldLibraryError(`No field named "${key}"`, 'key_not_found');
    }
    const library = cloneLibrary(config).filter(f => f.key !== key);
    // Drop the array entirely when it empties, to keep workspace.json tidy.
    const next = { ...config, field_library: library };
    if (library.length === 0)
        delete next.field_library;
    return next;
}
//# sourceMappingURL=field-library.js.map