import { ActionFormData, MessageFormData, ModalFormData } from "@minecraft/server-ui";
import { lockPlayerCamera, translate, unlockPlayerCamera } from "./miscUtils";
import { BooleanEntry, EnumEntry, NumberEntry, StringEntry } from "./configsUtils";
// Author: MehVahdJukaar
// very fancy book
// don't redistribute ARR
export class Page {
    constructor(id, name, parent, onFail, condition) {
        this.id = id;
        this.name = name;
        this.parent = parent;
        this.onFail = onFail;
        this.condition = condition;
    }
    isEnabled(p) {
        return this.condition?.(p) ?? true;
    }
}
export class HierarchicalPage extends Page {
    constructor(id, name, hasBody, parent, onFail, condition) {
        super(id, name, parent, onFail, condition);
        this.children = [];
        this.buttons = [];
        this.hasBody = hasBody;
    }
    async show(player, book) {
        book.markPageViewed(player, this);
        book.playOpenSound(player);
        const form = new ActionFormData();
        if (this.hasBody)
            form.body(this.parent ? book.translate(`${this.name}.body`) : book.translate(`body`));
        form.title(this.parent ? book.translate(this.name) : book.translate(""));
        const visibleButtons = this.buttons.filter(b => b.isVisible(player, book));
        visibleButtons.forEach(btn => btn.addToForm(form, player, book));
        const response = await form.show(player);
        if (!response.canceled && response.selection !== undefined) {
            await visibleButtons[response.selection].activate(player, book);
        }
        else if (this.onFail) {
            //open parent page
            const newUi = this.onFail(new Context(book, player));
            if (newUi)
                await newUi.open(player);
        }
    }
}
export class ConfigPage extends Page {
    constructor(id, name, configs, parent, submitAction, onFail, condition) {
        super(id, name, parent, onFail, condition);
        this.configs = configs;
        this.submitAction = submitAction;
        if (!this.configs) {
            throw new Error("ConfigPage must have at least one config entry");
        }
    }
    async show(player, book) {
        book.markPageViewed(player, this);
        book.playOpenSound(player);
        // Example implementation for config page
        const form = new ModalFormData();
        form.title(this.parent ? book.translate(this.name) : book.translate(""));
        for (const config of this.configs) {
            config.addToForm(form, player, book);
        }
        form.submitButton(translate("gui", "done"));
        const response = await form.show(player);
        if (!response.canceled && response.formValues !== undefined) {
            //set values
            let hasChanged;
            for (let i = 0; i < this.configs.length; i++) {
                const responseValue = response.formValues[i];
                const config = this.configs[i];
                const oldValue = config.getValue();
                if (oldValue != responseValue) {
                    config.setValue(responseValue, player, book);
                    hasChanged = true;
                }
            }
            if (this.submitAction) {
                const newUi = this.submitAction(new Context(book, player), hasChanged);
                if (newUi) {
                    await newUi.open(player);
                    return;
                }
            }
            if (this.parent) {
                await this.parent?.show(player, book);
            }
        }
        else if (this.onFail) {
            const newUi = this.onFail(new Context(book, player));
            if (newUi)
                await newUi.open(player);
        }
    }
}
export class FormPage extends Page {
    constructor(id, name, entries, parent, submitAction, onFail, condition) {
        super(id, name, parent, onFail, condition);
        this.entries = entries;
        this.submitAction = submitAction;
        if (entries.length == 0) {
            throw new Error("FormPage must have at least one entry");
        }
    }
    async show(player, book) {
        book.markPageViewed(player, this);
        book.playOpenSound(player);
        // Example implementation for config page
        const form = new ModalFormData();
        form.title(this.parent ? book.translate(this.name) : book.translate(""));
        // Add all form entries (textboxes, checkboxes, etc.)
        for (const config of this.entries) {
            config.addToForm(form, player, book);
        }
        form.submitButton(translate("gui", "done"));
        const response = await form.show(player);
        if (!response.canceled && response.formValues !== undefined) {
            const values = response.formValues;
            if (this.submitAction) {
                const modalResponse = new ModalResponse(values, this.entries);
                const newUi = this.submitAction(modalResponse, new Context(book, player));
                if (newUi) {
                    await newUi.open(player);
                    return;
                }
            }
            if (this.parent) {
                await this.parent?.show(player, book);
            }
        }
        else if (this.onFail) {
            const newUi = this.onFail(new Context(book, player));
            if (newUi)
                await newUi.open(player);
        }
    }
}
export class YesNoPage extends Page {
    constructor(id, name, hasBody, submitAction, parent, onFail, condition) {
        super(id, name, parent, onFail, condition);
        this.hasBody = hasBody;
        this.submitAction = submitAction;
    }
    async show(player, book) {
        book.markPageViewed(player, this);
        book.playOpenSound(player);
        const form = new MessageFormData();
        if (this.hasBody)
            form.body(book.translate(`${this.name}.body`));
        form.title(this.parent ? book.translate(this.name) : book.translate(""));
        form.button1(book.translate(`${this.name}.yes`));
        form.button2(book.translate(`${this.name}.no`));
        const response = await form.show(player);
        const context = new Context(book, player);
        if (!response.canceled && response.selection !== undefined) {
            if (this.submitAction) {
                const newUi = this.submitAction(response.selection == 0, context);
                if (newUi) {
                    await newUi.open(player);
                    return;
                }
            }
            if (this.parent) {
                await this.parent?.show(player, book);
            }
        }
        else if (this.onFail) {
            //open parent page
            const newUi = this.onFail(context);
            if (newUi)
                await newUi.open(player);
        }
    }
}
export class ModalEntry {
    constructor(name) {
        this.name = name;
    }
}
export class TextEntry extends ModalEntry {
    constructor(name, defaultValue) {
        super(name);
        this.defaultValue = defaultValue;
    }
    addToForm(form, player, ui) {
        form.textField("", ui.translate(this.name), this.defaultValue);
    }
}
export class SliderEntry extends ModalEntry {
    constructor(name, min, max, defaultValue) {
        super(name);
        this.min = min;
        this.max = max;
        this.defaultValue = defaultValue;
    }
    addToForm(form, player, ui) {
        form.slider(ui.translate(this.name), this.min, this.max, 1, this.defaultValue);
    }
}
export class CheckboxEntry extends ModalEntry {
    constructor(name, defaultValue = false) {
        super(name);
        this.defaultValue = defaultValue;
    }
    addToForm(form, player, ui) {
        form.toggle(ui.translate(this.name), this.defaultValue);
    }
}
export class DropdownEntry extends ModalEntry {
    constructor(name, options, selectedValue) {
        super(name);
        this.options = options;
        this.selectedValue = selectedValue;
    }
    addToForm(form, player, ui) {
        form.dropdown(ui.translate(this.name), this.options, this.options.indexOf(this.selectedValue));
    }
}
export class ModalResponse {
    constructor(values, entries) {
        this.values = values;
        this.text = [];
        this.slider = [];
        this.checkbox = [];
        this.dropdown = [];
        for (let i = 0; i < entries.length; i++) {
            const entry = entries[i];
            const value = values[i];
            if (entry instanceof TextEntry) {
                this.text.push(value);
            }
            else if (entry instanceof SliderEntry) {
                this.slider.push(value);
            }
            else if (entry instanceof CheckboxEntry) {
                this.checkbox.push(value);
            }
            else if (entry instanceof DropdownEntry) {
                this.dropdown.push(entry.options[value]);
            }
        }
    }
}
export class Button {
    constructor(name, iconPath = undefined, // Optional icon
    label // Optional explicit label
    ) {
        this.name = name;
        this.iconPath = iconPath;
        this.label = label;
    }
    isVisible(player, book) {
        return true;
    }
    async activate(player, book) {
    }
    ;
    getIconPath(player, ui) {
        if (this.iconPath === null)
            return null;
        return this.iconPath ?? ui.texture(this.name);
    }
    addToForm(form, player, book) {
        form.button(this.label ?? book.translate(this.name), this.getIconPath(player, book));
    }
}
class ActionButton extends Button {
    constructor(name, action, iconPath, // Optional icon
    label) {
        super(name, iconPath, label);
        this.action = action;
    }
    async activate(player, book) {
        const newUi = this.action(new Context(book, player));
        if (newUi)
            await newUi.open(player);
    }
}
export class PageButton extends Button {
    constructor(target, iconPath, // Optional icon
    label) {
        super(target.name, iconPath, label);
        this.target = target;
    }
    isVisible(player, book) {
        return this.target.isEnabled(player);
    }
    async activate(player, book) {
        await this.target.show(player, book);
    }
    getTarget() {
        return this.target;
    }
}
export class ViewedPageIconButton extends PageButton {
    constructor(target, iconPath, // Optional icon
    label) {
        super(target, iconPath, label);
    }
    getIconPath(player, book) {
        const path = super.getIconPath(player, book);
        if (path) {
            const viewed = book.hasPageBeenViewed(player, this.target);
            return viewed ? path : path + "_unseen";
        }
        return undefined;
    }
}
export class UI {
    constructor(root, pages, // pageId -> Page
    sound, pageMemory) {
        this.root = root;
        this.pages = pages;
        this.sound = sound;
        this.pageMemory = pageMemory;
        this.uiID = root.id;
    }
    async open(player, lockCamera = true) {
        await this.openPage(this.root.id, player, lockCamera);
    }
    async openPage(pageId, player, lockCamera = true) {
        const page = this.pages[pageId];
        if (page && page.isEnabled(player)) {
            let oldState;
            try {
                if (lockCamera) {
                    oldState = lockPlayerCamera(player);
                }
                await page.show(player, this);
            }
            finally {
                if (lockCamera) {
                    unlockPlayerCamera(player, oldState);
                }
            }
        }
        else {
            //open default page
            await this.open(player, lockCamera);
        }
    }
    markPageViewed(player, page) {
        this.pageMemory?.markPageViewed(page, player, this);
    }
    hasPageBeenViewed(player, page) {
        if (!this.pageMemory)
            return false;
        return this.pageMemory.hasViewedPage(page, player, this);
    }
    *walkPages() {
        const visited = new Set();
        const stack = [this.root];
        while (stack.length > 0) {
            const current = stack.pop();
            if (visited.has(current.id))
                continue;
            visited.add(current.id);
            yield current; // Yield the current page
            if (current instanceof HierarchicalPage) {
                // Push children for traversal (depth-first)
                for (let i = current.children.length - 1; i >= 0; i--) {
                    stack.push(current.children[i]);
                }
            }
        }
    }
    playOpenSound(player) {
        if (this.sound)
            player.playSound(this.sound);
    }
    translate(name) {
        return translate("gui", name.length == 0 ? this.uiID : `${this.uiID}.${name}`);
    }
    texture(iconPath) {
        return `textures/razz/sup/gui/${this.uiID}/${iconPath}`;
    }
}
class PageBuilder {
    constructor(name, trackSeenPages, openSound, parent, condition) {
        this.pageId = parent ? `${parent.pageId}.${name}` : name;
        this.simpleName = name;
        this.pageMemory = trackSeenPages;
        this.buildersById = parent?.buildersById ?? new Map();
        this.parentBuilder = parent;
        this.buildersById.set(this.pageId, this);
        this.condition = condition;
    }
    setOpenSound(sound) {
        this.openSound = sound;
        return this;
    }
    onFail(onFailAction) {
        this.failAction = onFailAction;
        return this;
    }
    setPageMemory(memory) {
        this.pageMemory = memory;
        return this;
    }
    build() {
        if (this.parentBuilder) {
            return this.parentBuilder.build();
        }
        const pages = {};
        const visited = new Set();
        // Create hierarchy starting from root
        const rootPage = this.createPageAndResolveHierarchy(undefined, pages, visited);
        for (const builder of this.buildersById.values()) {
            this.resolveChildButtons(builder, pages, this.pageMemory != undefined);
        }
        return new UI(rootPage, pages, this.openSound, this.pageMemory);
    }
    resolveChildHierarchy(childBuilder, parent, pages, visited) {
        childBuilder.createPageAndResolveHierarchy(parent, pages, visited);
    }
    resolveChildButtons(childBuilder, pages, trackSeen) {
        childBuilder.resolveButtons(pages, trackSeen);
    }
    resolveButtons(pages, trackSeen) {
    }
    createPageAndResolveHierarchy(parent, pages, visited) {
        visited.add(this.pageId);
        const page = this.createPageThrough(this, parent);
        pages[page.id] = page;
        return page;
    }
    // Protected helper to create pages through the instance
    createPageThrough(builder, parent) {
        return builder.doCreatePage(parent);
    }
}
export class HierarchicalPageBuilder extends PageBuilder {
    constructor() {
        super(...arguments);
        this.childrenIds = [];
        this.leaves = [];
        this.buttons = [];
        this.hasBody = true;
    }
    noBody() {
        this.hasBody = false;
        return this;
    }
    pageButton(targetId, iconPath = undefined, label) {
        this.buttons.push(new UnresolvedPageButton(targetId, iconPath, label));
        return this;
    }
    actionButton(name, action, iconPath = undefined, label) {
        // Add an ActionButton instead of a link button
        this.buttons.push(new ActionButton(name, action, iconPath, label));
        return this;
    }
    //just sugar to add dynamic entries
    with(callback) {
        callback(this);
        return this;
    }
    //shorthand
    section(id) {
        return this.pushSection(id).pop();
    }
    //shorthand
    sectionWithConfig(name, configEntry, condition) {
        return this.pushSection(name)
            .pushConfig("config", condition)
            .value(configEntry)
            .pop()
            .pop();
    }
    sectionWithConfigs(name, configEntries, condition) {
        const b = this.pushSection(name)
            .pushConfig("config", condition);
        for (const entry of configEntries) {
            b.value(entry);
        }
        return b.pop().pop();
    }
    pop() {
        if (!this.parentBuilder) {
            throw new Error("No parent builder to pop from.");
        }
        return this.parentBuilder;
    }
    pushSection(name, condition) {
        const child = new HierarchicalPageBuilder(name, this.pageMemory, this.openSound, this, condition);
        this.childrenIds.push(child.pageId);
        return child;
    }
    pushForm(name, condition) {
        const modalBuilder = new FormPageBuilder(name, this.pageMemory, this.openSound, this, condition);
        this.leaves.push(modalBuilder);
        return modalBuilder;
    }
    pushConfig(name, condition) {
        const configBuilder = new ConfigPageBuilder(name, this.pageMemory, this.openSound, this, condition);
        this.leaves.push(configBuilder);
        return configBuilder;
    }
    doCreatePage(parent) {
        return new HierarchicalPage(this.pageId, this.simpleName, this.hasBody, parent, this.failAction, this.condition);
    }
    createPageAndResolveHierarchy(parent, pages, visited) {
        if (visited.has(this.pageId))
            throw new Error(`Cycle detected at ${this.pageId}`);
        visited.add(this.pageId);
        // Create this page
        const page = this.createPageThrough(this, parent);
        pages[page.id] = page;
        if (parent instanceof HierarchicalPage) {
            parent.children.push(page);
        }
        // Resolve children using protected helper
        this.childrenIds.forEach(childId => {
            const childBuilder = this.buildersById.get(childId);
            if (childBuilder) {
                this.resolveChildHierarchy(childBuilder, page, pages, new Set(visited));
            }
        });
        // Resolve config pages
        this.leaves.forEach(configBuilder => {
            const configPage = this.createPageThrough(configBuilder, page);
            pages[configPage.id] = configPage;
            if (page instanceof HierarchicalPage) {
                page.children.push(configPage);
            }
        });
        return page;
    }
    resolveButtons(pages, trackSeen) {
        const currentPage = pages[this.pageId];
        if (!currentPage)
            return;
        // Resolve configured buttons
        this.buttons.forEach(button => {
            if (button instanceof ActionButton) {
                currentPage.buttons.push(button);
                return;
            }
            const target = pages[button.targetId];
            if (!target)
                throw new Error(`Missing target page: ${button.targetId}`);
            currentPage.buttons.push(trackSeen
                ? new ViewedPageIconButton(target, button.iconPath, button.label)
                : new PageButton(target, button.iconPath, button.label));
        });
        // Auto link children if not already linked
        for (const child of currentPage.children) {
            if (!currentPage.buttons.some(b => b instanceof PageButton && b.getTarget() === child)) {
                currentPage.buttons.push(trackSeen
                    ? new ViewedPageIconButton(child)
                    : new PageButton(child));
            }
        }
        // Add automatic back button
        if (currentPage.parent && !currentPage.buttons.some(b => b instanceof PageButton && b.getTarget() === currentPage.parent)) {
            currentPage.buttons.push(new PageButton(currentPage.parent, null, translate("gui", "back")));
        }
    }
}
export class FormPageBuilder extends PageBuilder {
    constructor() {
        super(...arguments);
        this.entries = [];
    }
    onSubmit(action) {
        this.submitAction = action;
        return this;
    }
    checkbox(name, defaultValue) {
        this.entries.push(new CheckboxEntry(name, defaultValue));
        return this;
    }
    text(name, defaultValue) {
        this.entries.push(new TextEntry(name, defaultValue));
        return this;
    }
    dropdown(name, options, selectedValue) {
        this.entries.push(new DropdownEntry(name, options, selectedValue));
        return this;
    }
    slider(name, min, max, defaultValue) {
        this.entries.push(new SliderEntry(name, min, max, defaultValue));
        return this;
    }
    doCreatePage(parent) {
        return new FormPage(this.pageId, this.simpleName, this.entries, parent, this.submitAction, this.failAction, this.condition);
    }
}
export class ConfigPageBuilder extends PageBuilder {
    constructor() {
        super(...arguments);
        this.entries = [];
    }
    defineNumber(name, min, max, defaultValue, step = 1) {
        if (min > max || defaultValue < min || defaultValue > max) {
            throw new Error(`Invalid number entry: ${name} min: ${min}, max: ${max}, defaultValue: ${defaultValue}`);
        }
        // Ensure step is positive
        if (step <= 0) {
            throw new Error(`Invalid step size: ${step}`);
        }
        const numberEntry = new NumberEntry(name, min, max, defaultValue, step);
        this.entries.push(numberEntry);
        return numberEntry;
    }
    defineStr(name, defaultValue, validator = s => true) {
        const stringEntry = new StringEntry(name, defaultValue, validator);
        this.entries.push(stringEntry);
        return stringEntry;
    }
    defineBool(name, defaultValue) {
        const booleanEntry = new BooleanEntry(name, defaultValue);
        this.entries.push(booleanEntry);
        return booleanEntry;
    }
    defineEnum(name, enumType, defaultValue) {
        const enumEntry = new EnumEntry(name, enumType, defaultValue);
        this.entries.push(enumEntry);
        return enumEntry;
    }
    value(entry) {
        this.entries.push(entry);
        return this;
    }
    onSubmit(action) {
        this.submitAction = action;
        return this;
    }
    pop() {
        return this.parentBuilder;
    }
    doCreatePage(parent) {
        return new ConfigPage(this.pageId, this.simpleName, this.entries, parent, this.submitAction, this.failAction, this.condition);
    }
}
export class YesNoPageBuilder extends PageBuilder {
    constructor() {
        super(...arguments);
        this.hasBody = true;
    }
    noBody() {
        this.hasBody = false;
        return this;
    }
    onSubmit(action) {
        this.submitAction = action;
        return this;
    }
    doCreatePage(parent) {
        return new YesNoPage(this.pageId, this.simpleName, this.hasBody, this.submitAction, parent, this.failAction, this.condition);
    }
}
export class UIBuilder {
    constructor(id) {
        this.id = id;
    }
    static create(id) {
        return new UIBuilder(id);
    }
    buttonsPage() {
        const hierarchicalPage = new HierarchicalPageBuilder(this.id);
        this.building = hierarchicalPage;
        return hierarchicalPage;
    }
    configPage() {
        const configPage = new ConfigPageBuilder(this.id);
        this.building = configPage;
        return configPage;
    }
    formPage() {
        const modalPage = new FormPageBuilder(this.id);
        this.building = modalPage;
        return modalPage;
    }
    yesNoPage() {
        const modalPage = new YesNoPageBuilder(this.id);
        this.building = modalPage;
        return modalPage;
    }
    build() {
        return this.building?.build();
    }
}
class UnresolvedPageButton {
    constructor(targetId, iconPath, label) {
        this.targetId = targetId;
        this.iconPath = iconPath;
        this.label = label;
    }
}
export class Context {
    constructor(parentUI, player) {
        this.parentUI = parentUI;
        this.player = player;
    }
    childUi() {
        return UIBuilder.create(this.parentUI.uiID);
    }
}
