import { nothing } from 'lit';
export var FragmentState;
(function (FragmentState) {
    FragmentState[FragmentState["Initialized"] = 0] = "Initialized";
    FragmentState[FragmentState["Created"] = 1] = "Created";
    FragmentState[FragmentState["Attached"] = 2] = "Attached";
    FragmentState[FragmentState["Started"] = 3] = "Started";
    FragmentState[FragmentState["Resumed"] = 4] = "Resumed";
    FragmentState[FragmentState["Paused"] = 5] = "Paused";
    FragmentState[FragmentState["Stopped"] = 6] = "Stopped";
    FragmentState[FragmentState["Detached"] = 7] = "Detached";
    FragmentState[FragmentState["Destroyed"] = 8] = "Destroyed";
})(FragmentState || (FragmentState = {}));
/**
 * `FragmentManager` is the class responsible for performing actions
 * on the app's fragments, such as adding, removing, or replacing them
 * and adding them to the back stack.
 */
export class FragmentManager {
    /**
     * Stores fragments by I
     */
    fragments = new Map();
    /**
     * The reference to the host activity or fragment.
     */
    host;
    constructor(host) {
        this.host = host;
    }
    async onDestroy() {
        // Clean up all fragments when the host is destroyed
        for (const [key, fragment] of this.fragments) {
            await fragment.onDestroy();
            this.removeFragmentStyles(fragment);
            this.fragments.delete(key);
        }
    }
    /**
     * Attaches a fragment to a parent Activity or Fragment
     *
     * @param fragment The fragment to add.
     * @param parent The parent fragment or activity.
     * @param renderTarget Optional DOM target to render to.
     * @param data Optional data to pass to the `onCreate()` method.
     */
    async attachFragment(key, fragment, parent, data) {
        // Ensure fragment has correct parent
        fragment.parent = parent;
        this.fragments.set(key, fragment);
        await fragment.onCreate(data);
        fragment.state = FragmentState.Created;
        await fragment.onAttach(data);
        fragment.state = FragmentState.Attached;
    }
    /**
     * Detaches a fragment from its parent.
     * @param fragment
     * @returns
     */
    async detachFragment(key) {
        const fragment = this.findFragment(key);
        if (!fragment) {
            throw new Error(`Fragment with key ${key} not found.`);
        }
        this.hideFragment(fragment);
        await fragment.onDetach();
        fragment.state = FragmentState.Detached;
        await fragment.onDestroy();
        fragment.state = FragmentState.Destroyed;
        this.fragments.delete(key);
        // Clear parent reference
        fragment.parent = undefined;
    }
    /**
     * Shows a Fragment (calls lifecycle methods and renders)
     * @param fragment The fragment to render.
     * @param renderTarget Optional render target, if any.
     */
    async showFragment(key, renderTarget) {
        const fragment = this.findFragment(key);
        if (!fragment) {
            throw new Error(`Fragment with key ${key} not found.`);
        }
        if (fragment.state === FragmentState.Resumed) {
            return;
        }
        // Tell the fragment it is now started.
        await fragment.onStart();
        fragment.state = FragmentState.Started;
        await fragment.onResume();
        fragment.state = FragmentState.Resumed;
        if (renderTarget) {
            fragment.setRenderRoot(renderTarget);
            fragment.requestUpdate({ app: false, activity: false });
        }
        else {
            this.host.requestUpdate({ app: false, activity: true });
        }
        this.setFragmentStyles(fragment);
    }
    /**
     * Sets styles for a fragment by adding a class to the root element.
     * This is useful for applying specific styles to fragments.
     *
     * The root element is either fragment's render root or the document element.
     * @param fragment The fragment to set styles for.
     */
    setFragmentStyles(fragment) {
        const root = fragment.renderer.renderRoot ?? document?.documentElement;
        if (!root) {
            return;
        }
        const classes = [];
        const ctor = fragment.constructor;
        classes.push(ctor.name.toLowerCase());
        if (typeof fragment.rootClass === 'function') {
            const className = fragment.rootClass();
            if (className) {
                classes.push(className);
            }
        }
        root.classList.add(...classes);
    }
    /**
     * Removes styles for a fragment by removing a class from the root element.
     * This is useful for cleaning up styles when a fragment is no longer visible.
     *
     * The root element is either fragment's render root or the document element.
     * @param fragment The fragment to remove styles for.
     */
    removeFragmentStyles(fragment) {
        const root = fragment.renderer.renderRoot ?? document?.documentElement;
        if (!root) {
            return;
        }
        const classes = [];
        const ctor = fragment.constructor;
        classes.push(ctor.name.toLowerCase());
        if (typeof fragment.rootClass === 'function') {
            const className = fragment.rootClass();
            if (className) {
                classes.push(className);
            }
        }
        root.classList.remove(...classes);
    }
    /**
     * Lists all visible fragments, that is in the fragments with the `FragmentState.Resumed` state.
     * @returns The list of visible fragments
     */
    getVisible() {
        return Array.from(this.fragments.values()).filter((fragment) => fragment.state === FragmentState.Resumed);
    }
    /**
     * Hides a Fragment (calls lifecycle methods and removes from DOM if necessary)
     */
    async hideFragment(fragment) {
        let ref;
        if (typeof fragment === 'string') {
            ref = this.findFragment(fragment);
        }
        else {
            ref = fragment;
        }
        if (!ref) {
            throw new Error(`Fragment not found`);
        }
        await ref.onPause();
        ref.state = FragmentState.Paused;
        await ref.onStop();
        ref.state = FragmentState.Stopped;
        this.removeFragmentStyles(ref);
        // if necessary, you would remove its template content from the DOM
        // however, with rendering logic encapsulated in Fragment, this isn't needed here
    }
    findFragment(key) {
        return this.fragments.get(key);
    }
    renderFragment(key) {
        const fragment = this.findFragment(key);
        if (!fragment || fragment.state !== FragmentState.Resumed) {
            return nothing;
        }
        return fragment.render() || nothing;
    }
    updateActiveFragments() {
        for (const [, fragment] of this.fragments) {
            if ([FragmentState.Started, FragmentState.Resumed].includes(fragment.state)) {
                fragment.requestUpdate({ app: false, activity: false });
            }
        }
    }
}
//# sourceMappingURL=FragmentManager.js.map