# Copyright (c) 2018-2020, NVIDIA CORPORATION.  All rights reserved.
#
# NVIDIA CORPORATION and its licensors retain all intellectual property
# and proprietary rights in and to this software, related documentation
# and any modifications thereto.  Any use, reproduction, disclosure or
# distribution of this software and related documentation without an express
# license agreement from NVIDIA CORPORATION is strictly prohibited.
#

# pylint: disable=protected-access, access-member-before-definition

import abc
import asyncio
import contextlib
import os
import weakref
from datetime import datetime, timezone
from typing import Callable

import omni.kit.app
import omni.kit.commands
import omni.kit.test
import omni.ui as ui

from . import ext_controller
from .common import (
    ExtAuthorGroup,
    ExtensionCommonInfo,
    build_ext_info,
    check_can_be_toggled,
    get_categories,
    get_options,
    pull_extension_async,
    toggle_extension,
)
from .ext_components import ExtensionToggle, SimpleCheckBox, add_doc_link_button
from .ext_data_fetcher import get_ext_data_fetcher
from .ext_export_import import export_ext
from .ext_test_tab import ExtTestTab
from .exts_graph_window import ExtsGraphWidget
from .markdown_renderer import MarkdownText
from .styles import CATEGORY_ICON_SIZE, EXT_ICON_SIZE_LARGE
from .utils import clip_text, is_vscode_installed, open_in_vscode_if_enabled, version_to_str

ICON_ZONE_WIDTH = 120
CATEGORY_ZONE_WIDTH = 120


def get_ext_text_content(key: str, ext_info, ext_item: ExtensionCommonInfo):
    """Get extension readme/changelog from either local dict or registry extra data"""

    content_str = ""
    # Get the data
    if ext_item.is_local:
        content_str = ext_info.get("package", {}).get(key, "")
    else:
        fetcher = get_ext_data_fetcher()
        ext_data = fetcher.get_ext_data(ext_item.package_id)
        if ext_data:
            content_str = ext_data.get("package", {}).get(key, "")

    # Figure out if it is a path to a file or actual data
    if content_str and "\n" not in content_str:
        ext_path = ext_info.get("path", "")
        # If it is a path -> load file content
        readme_file = os.path.join(ext_path, content_str)
        if os.path.exists(readme_file):
            with open(readme_file, "r", encoding="utf-8") as f:
                content_str = f.read()

    return content_str


class PageBase:
    """
    Interface for any classes adding Tabs to the Extension Manager UI
    """

    @abc.abstractmethod
    def build_tab(self, ext_info: dict, ext_item: ExtensionCommonInfo) -> None:
        """Build the Tab."""

    @abc.abstractmethod
    def destroy(self) -> None:
        """Teardown the Tab."""

    @staticmethod
    @abc.abstractmethod
    def get_tab_name() -> str:
        """Get the name used for the tab name in the UI"""


def build_package_info(ext_info):
    if ext_info is None:
        return

    package_dict = ext_info.get("package", {})
    publish_dict = package_dict.get("publish", {})

    def add_info(name, value):
        if not value:
            return
        if isinstance(value, (tuple, list)):
            value = ", ".join(value)
        if isinstance(value, dict):
            value = str(value)

        with ui.HStack():
            ui.Label(name, width=100, style_type_name_override="ExtensionDescription.Content")

            if value.startswith("http://") or value.startswith("https://"):

                def open_link():
                    import webbrowser

                    webbrowser.open(value)

                ui.Label(
                    value,
                    style_type_name_override="ExtensionDescription.ContentLink",
                    mouse_pressed_fn=lambda x, y, b, a: b == 0 and open_link(),
                )
            else:
                ui.Label(value, style_type_name_override="ExtensionDescription.ContentValue")

    def add_package_info(name, key):
        add_info(name, package_dict.get(key, None))

    # Package info
    add_package_info("Authors", "authors")
    add_package_info("Keywords", "keywords")
    add_info("Repo Name", publish_dict.get("repoName", None))
    add_package_info("Repo Url", "repository")
    add_package_info("Github Release", "githubRelease")

    # Publish info
    ts = publish_dict.get("date", None)
    if not ts:
        ts = package_dict.get("publishDate", None)  # backward compatibility for deprecated key
    if ts:
        add_info("Publish Date", str(datetime.fromtimestamp(ts, tz=timezone.utc).astimezone()))
    add_info("Build Number", publish_dict.get("buildNumber", None))
    add_info("Kit Version", publish_dict.get("kitVersion", None))
    add_package_info("Target", "target")

    # Test info
    test = ext_info.get("test", None)
    if isinstance(test, (list, tuple)):
        for t in test:
            add_info("Test Waiver", t.get("waiver", None))


class OverviewPage(PageBase):
    """
    Build a class with same interface and add it to ExtInfoWidget.pages
    below to have it show up as a tab
    """

    def build_tab(self, ext_info, ext_item: ExtensionCommonInfo):
        def content_label(text):
            ui.Label(text, style_type_name_override="ExtensionDescription.Content")

        readme_str = get_ext_text_content("readme", ext_info, ext_item)

        with ui.ScrollingFrame(
            horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_AS_NEEDED,
            vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
        ):
            with ui.ZStack():
                ui.Rectangle(style_type_name_override="ExtensionDescription.ContentBackground")
                with ui.HStack():
                    ui.Spacer(width=10)
                    with ui.VStack(height=0):
                        ui.Spacer(height=10)

                        # All the meta info about the package (version, date, authors)
                        build_package_info(ext_info)

                        ui.Spacer(height=15)

                        # Readme
                        MarkdownText(readme_str)

                        # Preview ?
                        if ext_item.preview_image_path:
                            ui.Image(
                                ext_item.preview_image_path,
                                alignment=ui.Alignment.H_CENTER,
                                fill_policy=ui.FillPolicy.PRESERVE_ASPECT_FIT,
                                width=700,
                                height=700,
                            )
                        else:
                            content_label("Preview: 'package/preview_image' is not specified.")

    def destroy(self):
        pass

    @staticmethod
    def get_tab_name():
        return "OVERVIEW"


class ChangelogPage(PageBase):
    def build_tab(self, ext_info, ext_item: ExtensionCommonInfo):
        changelog_str = get_ext_text_content("changelog", ext_info, ext_item)
        if not changelog_str:
            changelog_str = "[no changelog]"

        # word_wrap is important here because changelog can
        # be Markdown, and if word_wrap is defalut, Label
        # ignores everything after double hash. word_wrap fixes it.
        with ui.ScrollingFrame(
            horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_OFF,
            vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
        ):
            with ui.ZStack():
                ui.Rectangle(style_type_name_override="ExtensionDescription.ContentBackground")
                with ui.HStack():
                    ui.Spacer(width=10)
                    with ui.VStack(height=0):
                        ui.Spacer(height=10)
                        MarkdownText(changelog_str)

    def destroy(self):
        pass

    @staticmethod
    def get_tab_name():
        return "CHANGELOG"


class DependenciesPage(PageBase):
    def __init__(self):
        self._ext_graph = None

    def build_tab(self, ext_info, ext_item: ExtensionCommonInfo):
        # Graph available if extension is enabled:
        if ext_info.get("state", {}).get("enabled", False):
            self._ext_graph = ExtsGraphWidget(ext_info.get("package").get("id"))
        else:
            with ui.VStack(height=0):
                manager = omni.kit.app.get_app().get_extension_manager()

                # Enable result
                result, exts, err = manager.solve_extensions([ext_item.id], add_enabled=True, return_only_disabled=True)
                if not result:
                    ui.Label(
                        "Enabling this extension will fail with the following error:",
                        style_type_name_override="ExtensionDescription.Header",
                    )
                    ui.Label(err, style_type_name_override="ExtensionDescription.ContentError")
                else:
                    ui.Label(
                        "Enabling this extension will enable the following extensions in order:",
                        style_type_name_override="ExtensionDescription.Header",
                    )
                    ui.Label(
                        "".join(f"\n - {ext['id']}" for ext in exts),
                        style_type_name_override="ExtensionDescription.Content",
                    )
                ui.Spacer(height=30)

                # Dependencies
                ui.Label("Dependencies (in config):", style_type_name_override="ExtensionDescription.Header")
                ui.Spacer(height=4)
                for k, v in ext_info.get("dependencies", {}).items():
                    label_text = f"{k}"
                    tag = v.get("tag", None)
                    if tag:
                        label_text += f"-{tag}"

                    version = v.get("version", "*.*.*")
                    if version:
                        label_text += f"  |  v{version}"

                    optional = v.get("optional", False)
                    if optional:
                        label_text += "(optional)"
                    ui.Label(label_text, style_type_name_override="ExtensionDescription.Content")

    def destroy(self):
        pass

    @staticmethod
    def get_tab_name():
        return "DEPENDENCIES"


class PackagesPage(PageBase):
    def build_tab(self, ext_info, ext_item: ExtensionCommonInfo):
        manager = omni.kit.app.get_app().get_extension_manager()
        package_info = ext_info.get("package")
        packages = manager.fetch_extension_packages(package_info.get("id"))

        with ui.VStack(height=0):
            ui.Label(
                "All available packages for version {} :".format(package_info["version"]),
                style_type_name_override="ExtensionDescription.Title",
            )
            for p in packages:
                package_id = p["package_id"]
                with ui.CollapsableFrame(package_id, collapsed=True):
                    with ui.HStack():
                        ui.Spacer(width=20)
                        with ui.VStack():
                            build_package_info(manager.get_registry_extension_dict(package_id))

    def destroy(self):
        pass

    @staticmethod
    def get_tab_name():
        return "PACKAGES"


class ExtTestPage(PageBase):
    def __init__(self):
        self.tab = ExtTestTab()

    def build_tab(self, ext_info, ext_item: ExtensionCommonInfo):
        self.tab.build(ext_info)

    def destroy(self):
        self.tab.destroy()

    @staticmethod
    def get_tab_name():
        return "TESTS"


class ExtInfoWidget:

    pages = [OverviewPage, ChangelogPage, DependenciesPage, PackagesPage, ExtTestPage]
    current_page = 0

    def __init__(self):
        # Widgets for tabs
        self.__tabs = []
        self.__pages = []

        # Put into frame to enable rebuilding of UI
        self._frame = ui.Frame()

        self._ext_manager = omni.kit.app.get_app().get_extension_manager()
        self._ext_change_sub = self._ext_manager.get_change_event_stream().create_subscription_to_pop(
            lambda *_: self._refresh_once_next_frame(), name="ExtInfoWidget"
        )
        self._ext_graph = None
        self._version_menu = None

        self._selected_version = None
        self._selected_package_id = None

        # Refresh when new ext data fetched
        fetcher = get_ext_data_fetcher()

        def on_data_fetched(package_id):
            if self._selected_package_id == package_id:
                self._refresh_once_next_frame()

        fetcher.on_data_fetched_fn = on_data_fetched

        self.select_ext(None)

        self.page_tabs = []
        self.update_tabs()

        self.set_show_graph_fn(None)

    def update_tabs(self):
        self.page_tabs = []
        for page in self.pages:
            self.page_tabs.append(page())

    def set_visible(self, visible):
        self._frame.visible = visible

    def select_ext(self, ext_summary):
        """Select extension to display info about. Can be None"""
        self._ext_summary = ext_summary
        self._selected_version = None
        self._selected_package_id = None
        self._refresh_once_next_frame()

    def _refresh(self):

        # Fetch all extensions for currently selected extension summary
        extensions = (
            self._ext_manager.fetch_extension_versions(self._ext_summary.fullname)
            if self._ext_summary is not None
            else []
        )

        def _draw_empty_page():
            with self._frame:
                ui.Label("Select an extension")

        if len(extensions) == 0:
            _draw_empty_page()
            return

        # If user hasn't yet changed the version to display summary advices which should be shown by default:
        if self._selected_version is None:
            self._selected_version = self._ext_summary.default_ext["version"]

        # Now actual extension is selected (including version). Get all the info for it
        selected_index = next(
            (i for i, v in enumerate(extensions) if v["version"][:4] == self._selected_version[:4]), None
        )
        # If selected version is not available, select latest. That can happen if latest published is incompatible with
        # current target. In summaries it will show later version than the one you can select by default.
        if self._selected_version is not None and selected_index is None:
            selected_index = 0
            self._selected_version = extensions[selected_index]["version"]

        if selected_index is None:
            _draw_empty_page()
            return

        selected_ext = extensions[selected_index]
        is_version_latest = selected_index == 0
        ext_id = selected_ext["id"]
        package_id = selected_ext["package_id"]
        self._selected_package_id = package_id

        # If this extension is enabled or other version of this extension is enabled:
        any_version_enabled = any(ext["enabled"] for ext in extensions)

        ext_item, ext_info = build_ext_info(ext_id, package_id)  # -> Tuple[ExtensionCommonInfo, dict]:

        # Get ext info either from local or remote index
        if not ext_info:
            _draw_empty_page()
            return

        # Gather more info by solving dependencies. When other version of this extension is already enabled solving is
        # known to fail because of conflict. It will always display a red icon then.
        # When we actually toggle it we will first turn off that version. So for that case do not solve extensions.
        # An alternative solution might be to manually build a list of all enabled extensions, but exclude current one.
        if not any_version_enabled:
            ext_item.solve_extensions()

        if not ext_item.is_local:
            fetcher = get_ext_data_fetcher()
            fetcher.fetch(ext_item)

        ext_path = ext_info.get("path", "")

        package_dict = ext_info.get("package", {})
        docs_dict = ext_info.get("documentation", {})
        if ext_item.is_local:
            # For local extensions add version to ext_id to make it more concrete (for later enabling etc.)
            name = package_dict.get("name")
            version = package_dict.get("version", None)
            ext_id = name + "-" + version if version else name
        name = package_dict.get("name", "")
        enabled = ext_item.enabled

        category = get_categories().get(ext_item.category)

        ################################################################################################################
        # Build version selector menu

        def select_version(version):
            self._selected_version = version
            self._refresh_once_next_frame()

        self._version_menu = ui.Menu("Version")
        with self._version_menu:

            ui.MenuItem("Version(s)", enabled=False)
            ui.Separator()

            for i, e in enumerate(extensions):
                version = e["version"]
                version_str = version_to_str(version[:4])
                color = 0xFF76B900 if selected_index == i else 0xFFC0C0C0
                ui.MenuItem(
                    version_str,
                    checked=selected_index == i,
                    checkable=True,
                    style={"color": color, "margin": 0},
                    triggered_fn=lambda v=version: select_version(v),
                )

            # Publish / Unpublish buttons
            if get_options().publishing:
                ui.Separator()
                ui.MenuItem("Publishing", enabled=False)
                ui.Separator()
                if ext_item.is_local:
                    ui.MenuItem(
                        "PUBLISH", triggered_fn=lambda ext_id=ext_id: self._ext_manager.publish_extension(ext_id)
                    )
                else:
                    ui.MenuItem(
                        "UNPUBLISH", triggered_fn=lambda ext_id=ext_id: self._ext_manager.unpublish_extension(ext_id)
                    )

            # Uninstall
            can_be_uninstalled = not ext_item.enabled and ext_item.location_tag == "INSTALLED"
            if can_be_uninstalled:
                ui.Separator()
                ui.MenuItem(
                    "UNINSTALL", triggered_fn=lambda ext_id=ext_id: self._ext_manager.uninstall_extension(ext_id)
                )

        ################################################################################################################
        ################################################################################################################

        def draw_icon_block():
            # Icon
            with ui.HStack(width=ICON_ZONE_WIDTH):
                ui.Spacer()
                with ui.VStack(width=EXT_ICON_SIZE_LARGE[0]):
                    ui.Spacer()
                    ui.Image(ext_item.icon_path, height=EXT_ICON_SIZE_LARGE[1], style={"color": 0xFFFFFFFF})
                    ui.Spacer()
                ui.Spacer()

        ################################################################################################################

        def draw_top_row_block():
            with ui.HStack():

                # Big button
                if not ext_item.is_local:
                    if ext_info.get("state", {}).get("isPulled", False):
                        ui.Button(
                            "INSTALLING...",
                            style_type_name_override="ExtensionDescription.DownloadingButton",
                            width=100,
                            height=30,
                        )
                    else:

                        def on_click(item=ext_item):
                            asyncio.ensure_future(pull_extension_async(item, self._refresh_once_next_frame))

                        ui.Button(
                            "INSTALL",
                            style_type_name_override="ExtensionDescription.InstallButton",
                            width=100,
                            height=30,
                            clicked_fn=on_click,
                        )
                else:
                    can_update = not is_version_latest
                    is_autoload_enabled = ext_controller.is_autoload_enabled(ext_item.id)
                    if not ext_item.enabled and ext_item.is_toggle_blocked and is_autoload_enabled:

                        def on_click():
                            omni.kit.app.get_app().restart()

                        ui.Button(
                            "RESTART APP",
                            style_type_name_override="ExtensionDescription.RestartButton",
                            clicked_fn=on_click,
                            width=100,
                            height=30,
                        )
                    elif can_update:

                        def on_click(ext_id=ext_id, switch_off_on=(enabled and not ext_item.is_toggle_blocked)):
                            latest_ext = extensions[0]
                            latest_ext_id = latest_ext["id"]
                            latest_ext_version = latest_ext["version"]

                            # If autoload for that one is enabled, move it to latest:
                            if ext_controller.is_autoload_enabled(ext_id):
                                ext_controller.toggle_autoload(ext_id, False)
                                if check_can_be_toggled(latest_ext_id, for_autoload=True):
                                    ext_controller.toggle_autoload(latest_ext_id, True)

                            # If extension is enabled, switch off to latest:
                            if switch_off_on:
                                toggle_extension(ext_id, False)
                                toggle_extension(latest_ext_id, True)

                            # Finally switch UI to show newest version
                            select_version(latest_ext_version)

                        ui.Button(
                            "UPDATE",
                            style_type_name_override="ExtensionDescription.UpdateButton",
                            clicked_fn=on_click,
                            width=100,
                            height=30,
                        )
                    else:
                        ui.Button(
                            "UP TO DATE",
                            style_type_name_override="ExtensionDescription.UpToDateButton",
                            width=100,
                            height=30,
                        )

                # Version selector
                with ui.VStack(width=0):
                    ui.Spacer()
                    ui.Button(name="options", width=40, height=22, clicked_fn=self._version_menu.show)
                    ui.Spacer()

                # Toggle
                ui.Spacer(width=15)
                ExtensionToggle(ext_item, with_label=True, show_install_button=False)

                # Autoload toggle
                with ui.HStack():
                    ui.Spacer(width=20)

                    def toggle_autoload(value, ext_id=ext_id):
                        if check_can_be_toggled(ext_id, for_autoload=True):
                            ext_controller.toggle_autoload(ext_id, value)
                        self._refresh_once_next_frame()

                    SimpleCheckBox(
                        ext_controller.is_autoload_enabled(ext_id), toggle_autoload, "AUTOLOAD", enabled=True
                    )
                ui.Spacer(width=20)

                def add_icon_button(name, on_click, tooltip=None):
                    with ui.VStack(width=0):
                        ui.Spacer()

                        # cannot get style tooltip override to work, so force it here
                        b = ui.Button(
                            name=name,
                            style_type_name_override="IconButton",
                            width=23,
                            height=18,
                            clicked_fn=on_click,
                            style={"Label": {"color": 0xFF444444}},
                        )
                        if tooltip:
                            b.set_tooltip_fn(lambda *_: ui.Label(tooltip))

                        ui.Spacer()

                        return b

                def add_open_button(url, name, prefer_vscode=False, tooltip=None):
                    def on_click(url=url):
                        open_in_vscode_if_enabled(url, prefer_vscode)

                    b = add_icon_button(name, on_click)
                    if not tooltip:
                        tooltip = url
                    b.set_tooltip_fn(lambda *_: ui.Label(tooltip))

                # Doc button
                add_doc_link_button(ext_item, package_dict, docs_dict)
                ui.Spacer(width=8)

                if ext_item.is_local:
                    # Extension "open folder" button
                    ext_folder = os.path.dirname(ext_path) if os.path.isfile(ext_path) else ext_path
                    add_open_button(ext_folder, name="OpenFolder")

                    ui.Spacer(width=8)

                    # Extension "open folder" button
                    if is_vscode_installed():
                        add_open_button(ext_folder, name="OpenInVSCode", prefer_vscode=True, tooltip="Open in VSCode")
                        ui.Spacer(width=8)

                    # Extension "open config" button
                    add_open_button(ext_info.get("configPath", ""), name="OpenConfig", prefer_vscode=True)

                    ui.Spacer(width=8)

                    # Extension export button
                    def on_export(ext_id=ext_id):
                        export_ext(ext_id)

                    add_icon_button("Export", on_export, tooltip=f"Export {ext_info['package']['packageId']}")

        ################################################################################################################

        def draw_extension_name_block():
            with ui.HStack():
                # Extension id
                ui.Label(
                    ext_item.title,
                    height=0,
                    width=0,
                    style_type_name_override="ExtensionDescription.Label",
                    tooltip=ext_id,
                )
                ui.Spacer()

                # Remove that separate rect block with ext_id?
                # with ui.ZStack(height=0, width=0):
                #     ui.Rectangle(style_type_name_override="ExtensionDescription.Rectangle", name="Name")
                #     ui.Label(ext_id, style_type_name_override="ExtensionDescription.Id", name="Name")

        ################################################################################################################

        def draw_extension_description_block():
            with ui.HStack():

                ui.Label(
                    clip_text(ext_item.description),
                    height=0,
                    width=50,
                    style_type_name_override="ExtensionDescription.Label",
                )

        ################################################################################################################

        def draw_extension_version_block():
            with ui.HStack():
                if ext_item.version:
                    ui.Label(
                        "v" + ext_item.version,
                        style_type_name_override="ExtensionDescription.Label",
                        width=0,
                        name="Version",
                    )

                def add_separator():
                    ui.Spacer(width=10)
                    ui.Line(alignment=ui.Alignment.LEFT, style_type_name_override="ExtensionDescription.Label", width=0)
                    ui.Spacer(width=10)

                add_separator()

                # Repository/Local
                text = f"Registry: {ext_item.provider_name}" if ext_item.provider_name else "Local"
                ui.Label(text, style_type_name_override="ExtensionDescription.Label", width=0)

                ui.Spacer()

        ################################################################################################################

        def draw_category_block():
            # Categoty Info and Icon
            with ui.HStack(width=CATEGORY_ZONE_WIDTH):
                ui.Spacer()
                with ui.VStack(width=CATEGORY_ICON_SIZE[0]):
                    ui.Spacer()
                    ui.Image(category["image"], width=CATEGORY_ICON_SIZE[0], height=CATEGORY_ICON_SIZE[1])
                    ui.Spacer(height=10)
                    ui.Label(category["name"], alignment=ui.Alignment.CENTER)
                    ui.Spacer()
                ui.Spacer()

        def draw_ext_source_block():
            with ui.HStack(height=0):
                with ui.ZStack(height=32, width=0):
                    ui.Image(style_type_name_override="ExtensionDescription.CommunityImage", width=148, height=32)
                    with ui.HStack(width=0):
                        ui.Spacer(width=52)
                        name = ext_item.author_group.get_ui_name()
                        ui.Label(name, style_type_name_override="ExtensionDescription.CommunityLabel")

                ui.Spacer(width=2)
                if ext_item.is_untrusted:
                    with ui.ZStack(height=30):
                        with ui.VStack():
                            ui.Spacer(height=2)
                            ui.Rectangle(style_type_name_override="ExtensionDescription.UnverifiedRectangle")
                        ui.Label(
                            "UNVERIFIED",
                            alignment=ui.Alignment.RIGHT_CENTER,
                            style_type_name_override="ExtensionDescription.UnverifiedLabel",
                        )

        def draw_content_block():
            def set_page(index):
                for i in self.__tabs:
                    i.selected = False
                for i in self.__pages:
                    i.visible = False

                self.__tabs[index].selected = True
                self.__pages[index].visible = True
                ExtInfoWidget.current_page = index

            ui.Spacer(height=10)

            with ui.HStack(height=20):

                tab_buttons = []

                for index, page in enumerate(self.page_tabs):
                    tab_btn = ui.Button(
                        page.get_tab_name(),
                        width=0,
                        style_type_name_override="ExtensionDescription.Tab",
                        clicked_fn=lambda i=index: set_page(i),
                    )
                    tab_buttons.append(tab_btn)
                    if index < len(self.page_tabs) - 1:
                        ui.Line(
                            style_type_name_override="ExtensionDescription.TabLine",
                            alignment=ui.Alignment.H_CENTER,
                            height=20,
                            width=40,
                        )

                ui.Spacer()
                self.__tabs[:] = tab_buttons

            ui.Spacer(height=8)

            with ui.ZStack():
                pages = []

                for page_tab in self.page_tabs:
                    page = ui.Frame(build_fn=lambda i=ext_info, e=ext_item, page_tab=page_tab: page_tab.build_tab(i, e))
                    pages.append(page)

                self.__pages[:] = pages

            set_page(ExtInfoWidget.current_page)  # Default page

        ################################################################################################################
        ################################################################################################################
        # Build Actual UI Layout of all blocks

        with self._frame:
            with ui.ZStack():
                ui.Rectangle(style_type_name_override="ExtensionDescription.Background")
                with ui.VStack(spacing=4):
                    # Top Margin
                    ui.Spacer(height=15)
                    with ui.HStack(height=0, spacing=5):

                        draw_icon_block()

                        with ui.VStack():

                            draw_top_row_block()

                            ui.Spacer(height=10)

                            draw_extension_name_block()

                            draw_extension_description_block()

                            ui.Spacer(height=10)

                            draw_extension_version_block()

                        draw_category_block()

                    #
                    ui.Spacer(height=4)

                    if ext_item.author_group != ExtAuthorGroup.NVIDIA:
                        draw_ext_source_block()

                    with ui.HStack():
                        ui.Spacer(width=10)

                        # Content page
                        with ui.VStack():
                            # Separator line
                            ui.Line(
                                height=0,
                                alignment=ui.Alignment.BOTTOM,
                                style_type_name_override="ExtensionDescription.TabLineFull",
                            )

                            draw_content_block()

                        ui.Spacer(width=10)
                    ui.Spacer(height=5)

    def _refresh_once_next_frame(self):
        async def _delayed_refresh(weak_widget):
            await omni.kit.app.get_app().next_update_async()

            w = weak_widget()
            if w:
                w._refresh_task = None
                w._refresh()

        with contextlib.suppress(Exception):
            self._refresh_task.cancel()

        self._refresh_task = asyncio.ensure_future(_delayed_refresh(weakref.ref(self)))

    def set_show_graph_fn(self, fn: Callable):
        self._show_graph_fn = fn

    def _show_graph(self, ext_id):
        if self._show_graph_fn:
            self._show_graph_fn(ext_id)

    def destroy(self):
        self._ext_change_sub = None
        if self._ext_graph:
            self._ext_graph.destroy()
            self._ext_graph = None

        for page in self.page_tabs:
            page.destroy()
        # We want to None the instances also

        self._version_menu = None
        self.__tabs = []
        self.__pages = []
