import { Schema } from "./schema.js";
export function computeTitleFromName(name) {
    name = name.replace(/_/g, ' ').replace(/([a-z0-9])&([A-Z])/g, "$1 $2");
    return name[0].toUpperCase() + name.slice(1);
}
function getInputType(_name, schema) {
    if (schema.editor) {
        return schema.editor;
    }
    // Check format as fallback (e.g., "document", "media", "date", etc.)
    if (schema.schema.format) {
        return schema.schema.format;
    }
    switch (schema.type) {
        case 'number':
        case 'integer':
            return 'number';
        case 'boolean':
            return 'checkbox';
        case 'string':
            return "text";
        default:
            return 'text';
    }
}
export class Node {
    parent;
    schema;
    name;
    // change observer
    observer; // return false to stop bubbling
    constructor(parent, schema, name) {
        this.parent = parent;
        this.schema = schema;
        this.name = name;
    }
    get isRoot() {
        return !this.parent;
    }
    get root() {
        return this.parent ? this.parent.root : this;
    }
    get path() {
        return this.parent ? this.parent.path.concat(this.name) : [];
    }
    get isScalar() {
        return false;
    }
    get isListItem() {
        return false;
    }
    get isObject() {
        return false;
    }
    get isList() {
        return false;
    }
    get title() {
        return this.schema.title || computeTitleFromName(this.name);
    }
    onChange(node) {
        if (this.observer) {
            if (this.observer(this) === false) {
                return;
            }
            ;
        }
        this.parent && this.parent.onChange(node);
    }
}
export class ManagedObjectBase extends Node {
    constructor(parent, schema, name) {
        super(parent, schema, name);
    }
    get isObject() {
        return true;
    }
    getProperty(name) {
        const schema = this.schema.properties[name];
        if (schema.isMulti) {
            return new ManagedListProperty(this, schema, this.getOrInitArrayProperty(name));
        }
        else if (schema.isObject) {
            return new ManagedObjectProperty(this, schema, schema.name, this.getOrInitObjectProperty(name));
        }
        else {
            return new ManagedProperty(this, schema);
        }
    }
    getOrInitObjectProperty(name) {
        let value = this.value[name];
        if (!value) {
            this.value[name] = value = {};
        }
        return value;
    }
    getOrInitArrayProperty(name) {
        let value = this.value[name];
        if (!value) {
            this.value[name] = value = [];
        }
        return value;
    }
    setPropertyValue(name, value) {
        if (this.value[name] !== value) {
            this.value[name] = value;
            return true;
        }
        return false;
    }
    getPropertyValue(name) {
        return this.value[name];
    }
    get properties() {
        const out = [];
        for (const schema of Object.values(this.schema.properties)) {
            if (schema.isMulti) {
                out.push(new ManagedListProperty(this, schema, this.getOrInitArrayProperty(schema.name)));
            }
            else if (schema.isObject) {
                out.push(new ManagedObjectProperty(this, schema, schema.name, this.getOrInitObjectProperty(schema.name)));
            }
            else {
                out.push(new ManagedProperty(this, schema));
            }
        }
        return out;
    }
    [Symbol.iterator]() {
        return this.properties[Symbol.iterator]();
    }
}
export class ManagedObject extends ManagedObjectBase {
    value;
    constructor(schema, value = {}) {
        super(null, schema instanceof Schema ? schema : new Schema(schema), '#root');
        this.value = value;
    }
}
export class ManagedObjectProperty extends ManagedObjectBase {
    value;
    constructor(parent, schema, name, value) {
        super(parent, schema, name);
        this.value = value;
    }
}
export class ManagedProperty extends Node {
    constructor(parent, schema) {
        super(parent, schema, schema.name);
        if (parent.value[this.name] === undefined && schema.defaultValue !== undefined) {
            parent.value[this.name] = schema.defaultValue;
        }
    }
    get isScalar() {
        return true;
    }
    set value(value) {
        const changed = this.parent.setPropertyValue(this.name, value);
        if (changed) {
            this.onChange(this);
        }
    }
    get value() {
        return this.parent.getPropertyValue(this.name);
    }
    getInputType() {
        return getInputType(this.name, this.schema);
    }
}
export class ManagedListProperty extends Node {
    value;
    items = [];
    constructor(parent, schema, value) {
        super(parent, schema, schema.name);
        this.value = value;
        for (const _v of this.value) {
            this.add();
        }
    }
    get isList() {
        return true;
    }
    newItem(index) {
        if (this.schema.isObject) {
            return new ManageObjectEntry(this, index);
        }
        else {
            return new ManagedScalarEntry(this, index);
        }
    }
    add() {
        const item = this.newItem(this.items.length);
        this.items.push(item);
        return item;
    }
    //TODO change is fired even if the removed item is transient
    // how to mark an item as transient (an added item not yet set by the user)
    remove(index) {
        const value = this.value;
        if (index >= 0 && index < this.items.length) {
            this.items.splice(index, 1);
            // update indexes
            for (let i = 0, l = this.items.length; i < l; i++) {
                const item = this.items[i];
                item.index = i;
                item.name = String(i);
            }
        }
        if (Array.isArray(value) && index >= 0 && index < value.length) {
            value.splice(index, 1);
            // TODO trigger onChange only if this item isn't transient
            this.onChange(this);
        }
    }
    [Symbol.iterator]() {
        return this.items[Symbol.iterator]();
    }
    item(index) {
        if (index < 0 || index >= this.value.length) {
            return undefined;
        }
        const item = this.value[index];
        if (this.schema.isObject) {
            return new ManagedObjectProperty(this, this.schema, String(index), item);
        }
        else {
            return new ManagedScalarEntry(this, index);
        }
    }
    /**
     * remove undefined items from the end of the list if any
     */
    trim() {
        while (this.items[this.items.length - 1] === undefined) {
            this.items.pop();
        }
    }
}
export class ManageObjectEntry extends ManagedObjectBase {
    index;
    key;
    constructor(parent, index) {
        super(parent, parent.schema, String(index));
        this.index = index;
        if (parent.value[index] === undefined) {
            parent.value[index] = {};
        }
        this.key = this.name + '@' + Date.now();
    }
    get isListItem() {
        return true;
    }
    set value(value) {
        this.parent.value[this.index] = value;
    }
    get value() {
        return this.parent.value[this.index];
    }
}
export class ManagedScalarEntry extends Node {
    index;
    key;
    constructor(parent, index) {
        super(parent, parent.schema, String(index));
        this.index = index;
        if (parent.value[index] === undefined && parent.schema.defaultValue !== undefined) {
            parent.value[index] = parent.schema.defaultValue;
        }
        this.key = this.name + '@' + Date.now();
    }
    get isScalar() {
        return true;
    }
    get isListItem() {
        return true;
    }
    set value(value) {
        this.parent.value[this.index] = value;
    }
    get value() {
        return this.parent.value[this.index];
    }
    getInputType() {
        return getInputType(this.name, this.schema);
    }
}
//# sourceMappingURL=ManagedObject.js.map