# Released under the MIT License. See LICENSE for details.
#
"""Provides help related ui."""

from __future__ import annotations

from typing import override, TYPE_CHECKING

from efro.util import asserttype
import bacommon.docui.v1 as dui1
import bauiv1 as bui

from bauiv1lib.docui import DocUIController

if TYPE_CHECKING:
    from typing import Any

    from bacommon.docui import DocUIRequest, DocUIResponse

    from bauiv1lib.docui import DocUILocalAction, DocUIWindow


class InventoryUIController(DocUIController):
    """DocUI setup for inventory."""

    def __init__(self, player_profiles_only: bool = False) -> None:
        self._next_selected_profile: str | None = None
        self._player_profiles_only = player_profiles_only

    @override
    def fulfill_request(self, request: DocUIRequest) -> DocUIResponse:

        response: DocUIResponse

        # If we only want player profiles, we can skip the whole cloud
        # request bit.
        if self._player_profiles_only:
            response = dui1.Response(
                page=dui1.Page(
                    title='{"r":"inventoryText"}',
                    title_is_lstr=True,
                    rows=[],
                )
            )
        else:
            # *Most* of our inventory comes from the cloud - we just supply
            # profiles ourself so it works offline.
            response = self.fulfill_request_cloud(request, 'classicinventory')

            assert isinstance(request, dui1.Request)
            assert isinstance(response, dui1.Response)

            if request.path != '/':
                return response

            signed_in = (
                bui.app.plus is not None
                and bui.app.plus.accounts.primary is not None
            )

            # If anything went wrong, replace the error page they sent us with
            # a minimal 'most stuff is only available online' page.
            inv_only_signin_t = '{"r":"inventoryOnlyAvailableSignedInText"}'
            inv_only_online_t = '{"r":"inventoryOnlyAvailableOnlineText"}'
            if response.status is not dui1.ResponseStatus.SUCCESS:
                response = dui1.Response(
                    page=dui1.Page(
                        title='{"r":"inventoryText"}',
                        title_is_lstr=True,
                        rows=[
                            dui1.ButtonRow(
                                center_content=True,
                                buttons=[
                                    dui1.Button(
                                        (
                                            inv_only_signin_t
                                            if not signed_in
                                            else inv_only_online_t
                                        ),
                                        label_is_lstr=True,
                                        texture='white',
                                        size=(600, 100),
                                        color=(1, 1, 1, 0.0),
                                        label_scale=0.7,
                                        label_color=(1, 0.4, 0.4, 0.8),
                                    )
                                ],
                            ),
                        ],
                    ),
                )

        # Now add in our profiles, which we handle locally so it is
        # available offline.
        response.page.rows = [
            dui1.ButtonRow(
                title='{"r":"playerProfilesWindow.titleText"}',
                title_is_lstr=True,
                subtitle='{"r":"playerProfilesWindow.explanationText"}',
                subtitle_is_lstr=True,
                button_spacing=15,
                buttons=self._get_profile_buttons(),
            ),
            dui1.ButtonRow(
                spacing_top=-15,
                spacing_bottom=15,
                padding_left=13,
                buttons=[
                    dui1.Button(
                        '{"r":"editProfileWindow.titleNewText"}',
                        dui1.Local(
                            default_sound=False,
                            immediate_local_action='new_profile',
                        ),
                        label_is_lstr=True,
                        style=dui1.ButtonStyle.MEDIUM,
                        size=(200, 60),
                        scale=0.8,
                        color=(0.6, 0.5, 0.8, 1.0),
                        label_color=(1, 1, 1, 1),
                    ),
                ],
            ),
        ] + response.page.rows

        return response

    @override
    def local_action(self, action: DocUILocalAction) -> None:
        if action.name == 'new_profile':
            self._new_profile(action)
        elif action.name == 'edit_profile':
            self._edit_profile(action)
        else:
            bui.screenmessage(
                f'Invalid local-action "{action.name}".', color=(1, 0, 0)
            )
            bui.getsound('error').play()

    @override
    def restore_window_shared_state(
        self, window: DocUIWindow, state: dict
    ) -> None:
        """Called when a window shared state is being restored."""

        if not isinstance(window.request, dui1.Request):
            return

        # If desired, set the profile button that will be selected in
        # the new window. We do this when coming back from creating a
        # new profile/etc.
        if (
            window.request.path == '/'
            and self._next_selected_profile is not None
        ):
            state['selection'] = f'$(WIN)|profile.{self._next_selected_profile}'

            # Only do this once (return to normal selection save/restore
            # after).
            self._next_selected_profile = None

    def _on_profile_save(self, name: str) -> None:
        # An editor we launched tells us it saved a profile.

        # Have this one selected when we go back to the listing.
        self._next_selected_profile = name
        bui.pushcall(self._notify_profiles_changed)

    def _on_profile_delete(self, name: str) -> None:
        # An editor we launched tells us it deleted a profile.

        # Ask the inventory list to select/show the profile right before
        # the one we're deleting.
        profiles = bui.app.config.get('Player Profiles', {})
        items = list(profiles.items())
        items.sort(key=lambda x: asserttype(x[0], str).lower())

        namelower = name.lower()

        prevname = items[0][0] if items else None
        for item in items:
            if item[0].lower() < namelower:
                prevname = item[0]
            else:
                break

        if prevname is not None:
            self._next_selected_profile = prevname

        self._notify_profiles_changed()

    def _notify_profiles_changed(self) -> None:
        import bascenev1 as bs

        # If there's a team-chooser in existence, tell it the profile-list
        # has probably changed.
        session = bs.get_foreground_host_session()
        if session is not None:
            session.handlemessage(bs.PlayerProfilesChangedMessage())

    def _get_profile_buttons(self) -> list[dui1.Button]:
        # pylint: disable=too-many-locals

        plus = bui.app.plus
        assert plus is not None
        classic = bui.app.classic
        assert classic is not None

        buttons: list[dui1.Button] = []

        profiles = bui.app.config.get('Player Profiles', {})
        items = list(profiles.items())
        items.sort(key=lambda x: asserttype(x[0], str).lower())

        account_name: str | None
        if plus.get_v1_account_state() == 'signed_in':
            account_name = plus.get_v1_account_display_string()
        else:
            account_name = None

        spaz_appearances = classic.spaz_appearances
        spaz_appearance_default = spaz_appearances['Spaz']

        for p_name, p_info in items:
            if p_name == '__account__' and account_name is None:
                continue
            color, highlight = classic.get_player_profile_colors(p_name)
            tval = (
                account_name
                if p_name == '__account__'
                else classic.get_player_profile_icon(p_name) + p_name
            )
            assert tval is not None

            tcolor: Any = bui.safecolor(color, 0.4) + (1.0,)
            assert len(tcolor) == 4

            appearance = spaz_appearances.get(p_info['character'])
            if appearance is None:
                appearance = spaz_appearance_default

            buttons.append(
                dui1.Button(
                    texture='white',
                    size=(145, 175),
                    action=dui1.Local(
                        default_sound=False,
                        immediate_local_action='edit_profile',
                        immediate_local_action_args={'profile': p_name},
                    ),
                    # color=(0.6, 0.5, 0.7, 1.0),
                    color=(1, 1, 1, 0.0),
                    widget_id=f'profile.{p_name}',
                    decorations=[
                        dui1.Image(
                            appearance.icon_texture,
                            position=(0, 15),
                            size=(140, 140),
                            mask_texture='characterIconMask',
                            tint_texture=appearance.icon_mask_texture,
                            tint_color=color,
                            tint2_color=highlight,
                        ),
                        dui1.Text(
                            tval,
                            position=(0, -75),
                            size=(130, 40),
                            flatness=1.0,
                            shadow=1.0,
                            color=tcolor,
                        ),
                    ],
                )
            )

        return buttons

    def _new_profile(self, action: DocUILocalAction) -> None:
        # pylint: disable=cyclic-import
        from bauiv1lib.profile.edit import EditProfileWindow

        bui.getsound('swish').play()

        plus = bui.app.plus
        assert plus is not None

        # Clamp at 100 profiles (otherwise the server will and that's less
        # elegant looking).
        profiles = bui.app.config.get('Player Profiles', {})
        if len(profiles) > 100:
            bui.screenmessage(
                bui.Lstr(
                    translate=(
                        'serverResponses',
                        'Max number of profiles reached.',
                    )
                ),
                color=(1, 0, 0),
            )
            bui.getsound('error').play()
            return

        action.window.main_window_replace(
            lambda: EditProfileWindow(
                existing_profile=None,
                on_profile_save=bui.WeakCallPartial(self._on_profile_save),
                on_profile_delete=bui.WeakCallPartial(self._on_profile_delete),
            )
        )

    def _edit_profile(self, action: DocUILocalAction) -> None:
        # pylint: disable=cyclic-import
        from bauiv1lib.profile.edit import EditProfileWindow

        bui.getsound('swish').play()

        profile = action.args.get('profile')
        assert isinstance(profile, str)

        action.window.main_window_replace(
            lambda: EditProfileWindow(
                profile,
                on_profile_save=bui.WeakCallPartial(self._on_profile_save),
                on_profile_delete=bui.WeakCallPartial(self._on_profile_delete),
            )
        )
