import { Entity } from "./entity";
import { Line } from "./core";
import { LayoutParams } from "./layouts";
export class RoleDef {
    constructor() {
        this.name = "";
        this.notesOnly = false;
        this.index = 0;
    }
}
export class RawBlock extends Entity {
    constructor(content, contentType = "md") {
        super();
        this.content = content;
        this.contentType = contentType;
        this.TYPE = "RawBlock";
    }
    debugValue() {
        return Object.assign(Object.assign({}, super.debugValue()), { content: this.content, contentType: this.contentType });
    }
}
export function isBlock(item) {
    return item.TYPE === "Block";
}
export function isLine(item) {
    return item.TYPE === "Line";
}
export function isRawBlock(item) {
    return item.TYPE === "RawBlock";
}
export class Block extends Entity {
    constructor(blockType, parent = null, name = null) {
        super();
        this.TYPE = "Block";
        this.blockItems = [];
        this.localCycle = null;
        this.localAtomsPerBeat = null;
        this.localBreaks = null;
        this.localRoles = new Map();
        this._parentBlock = null;
        this._currRoleDef = null;
        this._currentLine = null;
        this._unnamedLayoutParams = [];
        this._namedLayoutParams = new Map();
        this._layoutParams = null;
        this.blockType = blockType;
        this.name = name;
        this._parentBlock = parent;
        if (parent) {
            this.setParent(parent);
        }
    }
    children() {
        return this.blockItems;
    }
    get parentBlock() {
        return this._parentBlock;
    }
    get cycle() {
        var _a, _b;
        if (this.localCycle !== null) {
            return this.localCycle;
        }
        return (_b = (_a = this.parentBlock) === null || _a === void 0 ? void 0 : _a.cycle) !== null && _b !== void 0 ? _b : null;
    }
    get atomsPerBeat() {
        var _a, _b;
        if (this.localAtomsPerBeat !== null) {
            return this.localAtomsPerBeat;
        }
        return (_b = (_a = this.parentBlock) === null || _a === void 0 ? void 0 : _a.atomsPerBeat) !== null && _b !== void 0 ? _b : 1;
    }
    get breaks() {
        var _a, _b;
        if (this.localBreaks !== null) {
            return this.localBreaks;
        }
        return (_b = (_a = this.parentBlock) === null || _a === void 0 ? void 0 : _a.breaks) !== null && _b !== void 0 ? _b : [];
    }
    get unnamedLayoutParams() {
        return this._unnamedLayoutParams;
    }
    get namedLayoutParams() {
        return this._namedLayoutParams;
    }
    get layoutParams() {
        if (this._layoutParams == null) {
            this._layoutParams = this.findUnnamedLayoutParams();
            if (this._layoutParams == null) {
                this._layoutParams = this.snapshotLayoutParams();
                this._unnamedLayoutParams.push(this._layoutParams);
            }
        }
        return this._layoutParams;
    }
    resetLayoutParams() {
        this._layoutParams = null;
        this.resetLine();
    }
    snapshotLayoutParams() {
        const effectiveCycle = this.cycle;
        if (effectiveCycle == null) {
            throw new Error("Cannot create layout params: no cycle defined");
        }
        return new LayoutParams({
            cycle: effectiveCycle,
            beatDuration: this.atomsPerBeat,
            layout: this.breaks,
        });
    }
    findUnnamedLayoutParams() {
        const effectiveCycle = this.cycle;
        if (effectiveCycle == null)
            return null;
        return (this._unnamedLayoutParams.find((lp) => {
            return (lp.beatDuration == this.atomsPerBeat && effectiveCycle.equals(lp.cycle) && lp.lineBreaksEqual(this.breaks));
        }) || null);
    }
    ensureNamedLayoutParams(name) {
        let lp = this._namedLayoutParams.get(name) || null;
        if (lp == null || this._layoutParams != lp) {
            if (lp == null) {
                lp = this.snapshotLayoutParams();
                this._namedLayoutParams.set(name, lp);
            }
            else {
                this.localCycle = lp.cycle;
                this.localAtomsPerBeat = lp.beatDuration;
                this.localBreaks = lp.lineBreaks;
            }
            this._layoutParams = lp;
            this.resetLine();
        }
        return this._layoutParams;
    }
    getRole(name) {
        var _a, _b;
        const local = this.localRoles.get(name.toLowerCase());
        if (local) {
            return local;
        }
        return (_b = (_a = this.parentBlock) === null || _a === void 0 ? void 0 : _a.getRole(name)) !== null && _b !== void 0 ? _b : null;
    }
    get currRoleDef() {
        if (this._currRoleDef !== null) {
            return this._currRoleDef;
        }
        if (this.parentBlock) {
            return this.parentBlock.currRoleDef;
        }
        const roles = Array.from(this.localRoles.values());
        return roles.length > 0 ? roles[roles.length - 1] : null;
    }
    setCurrRole(name) {
        name = name.trim().toLowerCase();
        if (name === "") {
            throw new Error("Role name cannot be empty");
        }
        let roleDef = this.getRole(name);
        if (roleDef == null) {
            roleDef = this.newRoleDef(name, name === "sw");
        }
        this._currRoleDef = roleDef;
    }
    get currentLine() {
        if (this._currentLine === null) {
            return this.newLine();
        }
        return this._currentLine;
    }
    newLine() {
        if (this._currentLine !== null && this._currentLine.isEmpty) {
            this.removeBlockItem(this._currentLine);
        }
        this._currentLine = new Line();
        this.addBlockItem(this._currentLine);
        return this._currentLine;
    }
    resetLine() {
        this._currentLine = null;
    }
    newRoleDef(name, notesOnly = false) {
        name = name.trim().toLowerCase();
        if (name === "") {
            throw new Error("Role name cannot be empty");
        }
        if (this.localRoles.has(name)) {
            throw new Error(`Role '${name}' already exists in this block`);
        }
        const rd = new RoleDef();
        rd.name = name;
        rd.notesOnly = notesOnly;
        rd.index = this.localRoles.size;
        this.localRoles.set(name, rd);
        return rd;
    }
    addBlockItem(item) {
        item.setParent(this);
        this.blockItems.push(item);
    }
    removeBlockItem(item) {
        const index = this.blockItems.indexOf(item);
        if (index >= 0) {
            this.blockItems.splice(index, 1);
            item.setParent(null);
        }
        return index;
    }
    debugValue() {
        const out = Object.assign(Object.assign({}, super.debugValue()), { blockType: this.blockType, blockItems: this.blockItems.map((c) => c.debugValue()) });
        if (this.name) {
            out.name = this.name;
        }
        if (this.localCycle) {
            out.localCycle = this.localCycle.uuid;
        }
        if (this.localAtomsPerBeat !== null) {
            out.localAtomsPerBeat = this.localAtomsPerBeat;
        }
        if (this.localBreaks !== null) {
            out.localBreaks = this.localBreaks;
        }
        if (this.localRoles.size > 0) {
            out.localRoles = Array.from(this.localRoles.keys());
        }
        return out;
    }
}
export function findContainingBlock(entity) {
    let current = entity.parent;
    while (current !== null) {
        if (current instanceof Block) {
            return current;
        }
        current = current.parent;
    }
    return null;
}
export class SectionBlock extends Block {
    constructor(sectionName, parent = null) {
        super("section", parent, sectionName);
    }
    children() {
        const heading = new RawBlock(`# ${this.name}`, "md");
        return [heading, ...this.blockItems];
    }
}
export class RepeatBlock extends Block {
    constructor(repeatCount, parent = null) {
        super("repeat", parent);
        this.repeatCount = repeatCount;
    }
    children() {
        if (this.repeatCount <= 0) {
            return this.blockItems;
        }
        const expanded = [];
        for (let i = 0; i < this.repeatCount; i++) {
            expanded.push(...this.blockItems);
        }
        return expanded;
    }
}
export class CycleBlock extends Block {
    constructor(cycle, parent = null) {
        super("cycle", parent);
        this.localCycle = cycle;
    }
}
export class BeatDurationBlock extends Block {
    constructor(atomsPerBeat, parent = null) {
        super("beatduration", parent);
        this.localAtomsPerBeat = atomsPerBeat;
    }
}
export class BreaksBlock extends Block {
    constructor(breaks, parent = null) {
        super("breaks", parent);
        this.localBreaks = breaks;
    }
}
export class RoleBlock extends Block {
    constructor(roleName, notesOnly, parent = null) {
        super("role", parent);
        this.newRoleDef(roleName, notesOnly);
    }
}
export class GroupBlock extends Block {
    constructor(groupName, parent = null) {
        super("group", parent, groupName);
    }
}
//# sourceMappingURL=block.js.map