"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.InputSpec = void 0;
const value_1 = require("./value");
const zod_1 = require("zod");
const zod_deep_partial_1 = require("zod-deep-partial");
const inputSpecTools_1 = require("./inputSpecTools");
/**
 * InputSpecs are the specs that are used by the os input specification form for this service.
 * Here is an example of a simple input specification
  ```ts
    const smallInputSpec = InputSpec.of({
      test: Value.boolean({
        name: "Test",
        description: "This is the description for the test",
        warning: null,
        default: false,
      }),
    });
  ```

  The idea of an inputSpec is that now the form is going to ask for
  Test: [ ] and the value is going to be checked as a boolean.
  There are more complex values like selects, lists, and objects. See {@link Value}

  Also, there is the ability to get a validator/parser from this inputSpec spec.
  ```ts
  const matchSmallInputSpec = smallInputSpec.validator();
  type SmallInputSpec = typeof matchSmallInputSpec._TYPE;
  ```

  Here is an example of a more complex input specification which came from an input specification for a service
  that works with bitcoin, like c-lightning.
  ```ts

    export const hostname = Value.string({
  name: "Hostname",
  default: null,
  description: "Domain or IP address of bitcoin peer",
  warning: null,
  required: true,
  masked: false,
  placeholder: null,
  pattern:
    "(^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$)|((^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$)|(^[a-z2-7]{16}\\.onion$)|(^([a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?\\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$))",
  patternDescription:
    "Must be either a domain name, or an IPv4 or IPv6 address. Do not include protocol scheme (eg 'http://') or port.",
});
export const port = Value.number({
  name: "Port",
  default: null,
  description: "Port that peer is listening on for inbound p2p connections",
  warning: null,
  required: false,
  range: "[0,65535]",
  integral: true,
  units: null,
  placeholder: null,
});
export const addNodesSpec = InputSpec.of({ hostname: hostname, port: port });

  ```
 */
class InputSpec {
    constructor(spec, validator) {
        this.spec = spec;
        this.validator = validator;
        this._TYPE = null;
        this._PARTIAL = null;
        this.partialValidator = (0, zod_deep_partial_1.zodDeepPartial)(this.validator);
    }
    /**
     * Builds the runtime form specification and combined Zod validator from this InputSpec's fields.
     *
     * @returns An object containing the resolved `spec` (field specs keyed by name) and a combined `validator`
     */
    async build(options) {
        const answer = {};
        const validator = {};
        for (const k in this.spec) {
            const built = await this.spec[k].build(options);
            answer[k] = built.spec;
            validator[k] = built.validator;
        }
        return {
            spec: answer,
            validator: zod_1.z.object(validator),
        };
    }
    /**
     * Adds multiple fields to this spec at once, returning a new `InputSpec` with extended types.
     *
     * @param build - A record of {@link Value} entries, or a function receiving typed tools that returns one
     */
    add(build) {
        const addedValues = build instanceof Function ? build((0, inputSpecTools_1.createInputSpecTools)()) : build;
        const newSpec = { ...this.spec, ...addedValues };
        const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [
            k,
            v.validator,
        ])));
        return new InputSpec(newSpec, newValidator);
    }
    /**
     * Returns a new InputSpec containing only the specified keys.
     * Use `true` to keep a field as-is, or a nested object to filter sub-fields of object-typed fields.
     *
     * @example
     * ```ts
     * const full = InputSpec.of({
     *   name: Value.text({ name: 'Name', required: true, default: null }),
     *   settings: Value.object({ name: 'Settings' }, InputSpec.of({
     *     debug: Value.toggle({ name: 'Debug', default: false }),
     *     port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
     *   })),
     * })
     * const filtered = full.filter({ name: true, settings: { debug: true } })
     * ```
     */
    filter(keys, keepByDefault) {
        const newSpec = {};
        for (const k of Object.keys(this.spec)) {
            const filterVal = keys[k];
            const value = this.spec[k];
            if (!value)
                continue;
            if (filterVal === true) {
                newSpec[k] = value;
            }
            else if (typeof filterVal === 'object' && filterVal !== null) {
                const objectMeta = value._objectSpec;
                if (objectMeta) {
                    const filteredInner = objectMeta.inputSpec.filter(filterVal, keepByDefault);
                    newSpec[k] = value_1.Value.object(objectMeta.params, filteredInner);
                }
                else {
                    newSpec[k] = value;
                }
            }
            else if (keepByDefault && filterVal !== false) {
                newSpec[k] = value;
            }
        }
        const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
        return new InputSpec(newSpec, newValidator);
    }
    /**
     * Returns a new InputSpec with the specified keys disabled.
     * Use `true` to disable a field, or a nested object to disable sub-fields of object-typed fields.
     * All fields remain in the spec — disabled fields simply cannot be edited by the user.
     *
     * @param keys - Which fields to disable, using the same shape as {@link FilterKeys}
     * @param message - The reason the fields are disabled, displayed to the user
     *
     * @example
     * ```ts
     * const spec = InputSpec.of({
     *   name: Value.text({ name: 'Name', required: true, default: null }),
     *   settings: Value.object({ name: 'Settings' }, InputSpec.of({
     *     debug: Value.toggle({ name: 'Debug', default: false }),
     *     port: Value.number({ name: 'Port', required: true, default: 8080, integer: true }),
     *   })),
     * })
     * const disabled = spec.disable({ name: true, settings: { debug: true } }, 'Managed by the system')
     * ```
     */
    disable(keys, message) {
        const newSpec = {};
        for (const k in this.spec) {
            const filterVal = keys[k];
            const value = this.spec[k];
            if (!filterVal) {
                newSpec[k] = value;
            }
            else if (filterVal === true) {
                newSpec[k] = value.withDisabled(message);
            }
            else if (typeof filterVal === 'object' && filterVal !== null) {
                const objectMeta = value._objectSpec;
                if (objectMeta) {
                    const disabledInner = objectMeta.inputSpec.disable(filterVal, message);
                    newSpec[k] = value_1.Value.object(objectMeta.params, disabledInner);
                }
                else {
                    newSpec[k] = value.withDisabled(message);
                }
            }
        }
        const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
        return new InputSpec(newSpec, newValidator);
    }
    /**
     * Resolves a key path to its corresponding display name path.
     * Each key is mapped to the `name` property of its built {@link ValueSpec}.
     * Recurses into `Value.object` sub-specs for nested paths.
     *
     * @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
     * @param options - Build options providing effects and prefill data
     * @returns Array of display names (e.g. `["Settings", "Debug"]`)
     */
    async namePath(path, options) {
        if (path.length === 0)
            return [];
        const [key, ...rest] = path;
        const value = this.spec[key];
        if (!value)
            return [];
        const built = await value.build(options);
        const name = 'name' in built.spec ? built.spec.name : key;
        if (rest.length === 0)
            return [name];
        const objectMeta = value._objectSpec;
        if (objectMeta) {
            const innerNames = await objectMeta.inputSpec.namePath(rest, options);
            return [name, ...innerNames];
        }
        return [name];
    }
    /**
     * Resolves a key path to the description of the target field.
     * Recurses into `Value.object` sub-specs for nested paths.
     *
     * @param path - Typed tuple of field keys (e.g. `["settings", "debug"]`)
     * @param options - Build options providing effects and prefill data
     * @returns The description string, or `null` if the field has no description or was not found
     */
    async description(path, options) {
        if (path.length === 0)
            return null;
        const [key, ...rest] = path;
        const value = this.spec[key];
        if (!value)
            return null;
        if (rest.length === 0) {
            const built = await value.build(options);
            return 'description' in built.spec
                ? built.spec.description
                : null;
        }
        const objectMeta = value._objectSpec;
        if (objectMeta) {
            return objectMeta.inputSpec.description(rest, options);
        }
        return null;
    }
    /**
     * Returns a new InputSpec filtered to only include keys present in the given partial object.
     * For nested `Value.object` fields, recurses into the partial value to filter sub-fields.
     *
     * @param partial - A deep-partial object whose defined keys determine which fields to keep
     */
    filterFromPartial(partial) {
        const newSpec = {};
        for (const k of Object.keys(partial)) {
            const value = this.spec[k];
            if (!value)
                continue;
            const objectMeta = value._objectSpec;
            if (objectMeta) {
                const partialVal = partial[k];
                if (typeof partialVal === 'object' && partialVal !== null) {
                    const filteredInner = objectMeta.inputSpec.filterFromPartial(partialVal);
                    newSpec[k] = value_1.Value.object(objectMeta.params, filteredInner);
                    continue;
                }
            }
            newSpec[k] = value;
        }
        const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
        return new InputSpec(newSpec, newValidator);
    }
    /**
     * Returns a new InputSpec with fields disabled based on which keys are present in the given partial object.
     * For nested `Value.object` fields, recurses into the partial value to disable sub-fields.
     * All fields remain in the spec — disabled fields simply cannot be edited by the user.
     *
     * @param partial - A deep-partial object whose defined keys determine which fields to disable
     * @param message - The reason the fields are disabled, displayed to the user
     */
    disableFromPartial(partial, message) {
        const newSpec = {};
        for (const k in this.spec) {
            const value = this.spec[k];
            if (!(k in partial)) {
                newSpec[k] = value;
                continue;
            }
            const objectMeta = value._objectSpec;
            if (objectMeta) {
                const partialVal = partial[k];
                if (typeof partialVal === 'object' && partialVal !== null) {
                    const disabledInner = objectMeta.inputSpec.disableFromPartial(partialVal, message);
                    newSpec[k] = value_1.Value.object(objectMeta.params, disabledInner);
                    continue;
                }
            }
            newSpec[k] = value.withDisabled(message);
        }
        const newValidator = zod_1.z.object(Object.fromEntries(Object.entries(newSpec).map(([k, v]) => [k, v.validator])));
        return new InputSpec(newSpec, newValidator);
    }
    /**
     * Creates an `InputSpec` from a plain record of {@link Value} entries.
     *
     * @example
     * ```ts
     * const spec = InputSpec.of({
     *   username: Value.text({ name: 'Username', required: true, default: null }),
     *   verbose: Value.toggle({ name: 'Verbose Logging', default: false }),
     * })
     * ```
     */
    static of(spec) {
        const validator = zod_1.z.object(Object.fromEntries(Object.entries(spec).map(([k, v]) => [k, v.validator])));
        return new InputSpec(spec, validator);
    }
}
exports.InputSpec = InputSpec;
//# sourceMappingURL=inputSpec.js.map