/*
Copyright 2025 New Vector Ltd.
Copyright 2026 Element Creations Ltd.

SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/

import type { Root } from "react-dom/client";
import { type LegacyModuleApiExtension } from "./legacy-modules";
import { type LegacyCustomisationsApiExtension } from "./legacy-customisations";
import { type ConfigApi } from "./config";
import { type I18nApi } from "./i18n";
import { type CustomComponentsApi } from "./custom-components";
import { type NavigationApi } from "./navigation.ts";
import { type DialogApiExtension } from "./dialog.ts";
import { type AccountAuthApiExtension } from "./auth.ts";
import { type ProfileApiExtension } from "./profile.ts";
import { type ExtrasApi } from "./extras.ts";
import { type BuiltinsApi } from "./builtins.ts";
import { type StoresApi } from "./stores.ts";
import { type ClientApi } from "./client.ts";
import { type WidgetLifecycleApi } from "./widget-lifecycle.ts";
import { type WidgetApi } from "./widget.ts";
import { type CustomisationsApi } from "./customisations.ts";
import { type ComposerApi } from "./composer.ts";

/**
 * Module interface for modules to implement.
 * @public
 */
export interface Module {
    load(): Promise<void>;
}

const moduleSignature: Record<keyof Module, Type> = {
    load: "function",
};

/**
 * Module interface for modules to export as the default export.
 * @public
 */
export interface ModuleFactory {
    readonly moduleApiVersion: string;
    new (api: Api): Module;
    readonly prototype: Module;
}

const moduleFactorySignature: Record<keyof ModuleFactory, Type> = {
    moduleApiVersion: "string",
    prototype: "object",
};

export interface ModuleExport {
    default: ModuleFactory;
}

const moduleExportSignature: Record<keyof ModuleExport, Type> = {
    default: "function",
};

type Type = "function" | "string" | "number" | "boolean" | "object";

function isInterface<T>(obj: unknown, type: "object" | "function", keys: Record<keyof T, Type>): obj is T {
    if (obj === null || typeof obj !== type) return false;
    for (const key in keys) {
        if (typeof (obj as Record<keyof T, unknown>)[key] !== keys[key]) return false;
    }
    return true;
}

export function isModule(module: unknown): module is ModuleExport {
    return (
        isInterface(module, "object", moduleExportSignature) &&
        isInterface(module.default, "function", moduleFactorySignature) &&
        isInterface(module.default.prototype, "object", moduleSignature)
    );
}

/**
 * The API for modules to interact with the application.
 * @public
 */
export interface Api
    extends
        LegacyModuleApiExtension,
        LegacyCustomisationsApiExtension,
        DialogApiExtension,
        AccountAuthApiExtension,
        ProfileApiExtension {
    /**
     * The API to read config.json values.
     * Keys should be scoped to the module in reverse domain name notation.
     * @public
     */
    readonly config: ConfigApi;
    /**
     * The internationalisation API.
     * @public
     */
    readonly i18n: I18nApi;
    /**
     * The root node the main application is rendered to.
     * Intended for rendering sibling React trees.
     * @public
     */
    readonly rootNode: HTMLElement;

    /**
     * The custom message component API.
     * @alpha
     */
    readonly customComponents: CustomComponentsApi;

    /**
     * Allows modules to render components that are part of Element Web.
     * @alpha
     */
    readonly builtins: BuiltinsApi;

    /**
     * API to navigate the application.
     * @public
     */
    readonly navigation: NavigationApi;

    /**
     * Allows modules to insert extra UI into Element Web.
     * @alpha
     */
    readonly extras: ExtrasApi;

    /**
     * Allows modules to access a limited functionality of certain stores from Element Web.
     */
    readonly stores: StoresApi;

    /**
     * Access some very specific functionality from the client.
     */
    readonly client: ClientApi;

    /**
     * API for modules to auto-approve widget preloading, identity token requests, and capability requests.
     * @alpha Subject to change.
     */
    readonly widgetLifecycle: WidgetLifecycleApi;

    /**
     * API for modules to interact with widgets in Element Web, including getting what widgets
     * are active in a given room.
     * @alpha Subject to change.
     */
    readonly widget: WidgetApi;

    /**
     * Allows modules to customise behaviour of app's components.
     * @alpha Subject to change.
     */
    readonly customisations: CustomisationsApi;

    /**
     * Allows modules to customise the message composer.
     * @alpha Subject to change.
     */
    readonly composer: ComposerApi;

    /**
     * Create a ReactDOM root for rendering React components.
     * Exposed to allow modules to avoid needing to bundle their own ReactDOM.
     * @param element - the element to render use as the root.
     * @public
     */
    createRoot(element: Element): Root;
}
