# Copyright (C) 2026 ANSYS, Inc. and/or its affiliates.
# SPDX-License-Identifier: Apache-2.0
#
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""
Transaction Method Status Badge
================================

A minimal Dash app showing how to use the
:class:`~ansys.solutions.dash_super_components.TransactionMethodStatusBadge`
component to display the status of a SAF transaction method as a compact
badge.

See the
`Transaction Method Status Badge
<https://upgraded-carnival-wn6lkym.pages.github.io/version/stable/api/dash-super-components/user-guide/transaction-method-status-badge.html#ref_transaction_method_status_badge>`_
page in the User Guide for the full reference documentation.

In a real SAF solution every step exposes one or more transaction methods
(decorated with ``@transaction``).  The GLOW REST API exposes their status at:

.. code-block:: text

    GET /steps/<step-name>:<method-name>

The :class:`~ansys.solutions.dash_super_components.TransactionMethodStatusBadge`
component polls that endpoint at a configurable interval and renders a compact
badge showing the current status.
This example embeds a minimal Flask server that mimics the GLOW REST API so
you can explore the component without a running GLOW server.  The mock server
listens on ``http://127.0.0.1:8061``.
For a detailed example of how to set up this component with a full SAF solution, see the
`showcase example
<https://github.com/ansys/saf/blob/main/packages/dash-super-components/examples/showcase_all/src/ansys/solutions/showcase_dash_super_components/ui/pages/transaction_method_status_badge_page.py>`_
in the repository.

.. note::

   Run this file directly to launch the app:

   .. code-block:: bash

       pip install ansys-solutions-dash-super-components requests
       python example_transaction_method_status_badge.py

   Then open ``http://localhost:8050``. Use the switches and the buttons to trigger
   a successful or failing transaction and watch the status badges update in
   real time.
"""

# %%
# Set up imports and the Dash application
# ---------------------------------------
#
# :class:`~dash_mantine_components.MantineProvider` must wrap the entire layout
# for Mantine-based components to render correctly.  The React version must also
# be set **before** importing the component library — this is a Dash Mantine
# Components requirement when using Dash 2.x.
#
# A tiny Flask server is embedded in the same process as the Dash app so the
# mock GLOW API is ready before the component starts polling. This requires
# additional imports for the mock server and callbacks.

import threading
import time

from ansys.solutions.dash_super_components import TransactionMethodStatusBadge
from dash import _dash_renderer
from dash.exceptions import PreventUpdate
from dash_extensions.enrich import DashProxy, Input, Output, State, callback, dcc, html
import dash_mantine_components as dmc
from flask import Flask, Response, jsonify, request as flask_request
import requests
from werkzeug.serving import make_server

# Required only for Dash 2.x to use Mantine-based components
_dash_renderer._set_react_version("18.2.0")

app = DashProxy(__name__)

# %%
# Embedded mock GLOW API server
# --------------------------------
#
# ``TransactionMethodStatusBadge`` polls
# ``{glow_api_url}/steps/{step-name}:{method-name}``
# and expects a JSON body that contains a ``"status"`` field.  Recognized
# status values are ``"run-required"``, ``"running"``, ``"completed"`` and
# ``"failed"``.
#
# The mock server stores the current status for each ``step:method`` key in a
# plain Python dict that can be updated from Dash callbacks by POSTing to the
# mock server's ``/steps/<step_method>`` endpoint.

MOCK_API_PORT = 8061
MOCK_API_URL = f"http://127.0.0.1:{MOCK_API_PORT}"

# Simple dict owned exclusively by the mock server process/thread. Do not access from Dash callbacks
# as this is not thread-safe and is only intended to be updated via the mock server endpoints.
_statuses: dict[str, str] = {}

mock_server = Flask("mock_glow_api_badge")


@mock_server.route("/steps/<path:step_method>", methods=["GET"])
def get_step_status(step_method: str) -> Response:
    """Get the status for *step_method*."""
    return jsonify({"status": _statuses.get(step_method, "run-required")})


@mock_server.route("/steps/<path:step_method>", methods=["POST"])
def set_step_status(step_method: str) -> Response:
    """Set the status for *step_method* (used by Dash callbacks to mock transaction status)."""
    _statuses[step_method] = flask_request.json["status"]
    return jsonify({"ok": True})


def _run_mock_server() -> None:
    server = make_server("127.0.0.1", MOCK_API_PORT, mock_server)
    server.serve_forever()


threading.Thread(target=_run_mock_server, daemon=True).start()

# %%
# Define the step and methods to monitor
# ----------------------------------------
#
# Each ``TransactionMethodStatusBadge`` monitors a single *(step, method)* pair.
# Here both badges belong to the same step (``"mesher"``) but monitor different
# methods.  In a real solution they could also belong to different steps.

STEP_NAME = "mesher"
GENERATE_MESH_METHOD = "generate_mesh"
CHECK_QUALITY_METHOD = "check_quality"

# %%
# Build the layout
# ----------------
#
# A :class:`~ansys.solutions.dash_super_components.TransactionMethodStatusBadge`
# cannot be added directly to the layout because in a real SAF-based solution
# it needs access to the project URL, which is only available through the GLOW
# ``DashClient`` in a callback.
# Therefore, placeholder ``html.Div`` elements are added and populated with
# badge instances via a callback on page load.
#
# The layout contains two cards — one for *Generate Mesh* and one for
# *Check Quality* — each with a short description, a *Simulate failure* toggle,
# a *Run* button, and a badge placeholder.

app.layout = dmc.MantineProvider(
    html.Div(
        [
            dcc.Location("url", refresh=False),
            dmc.Title("Mesh Generation Workflow", order=2, mb="md"),
            dmc.Text(
                "Run each meshing step independently. "
                "Enable the failure toggle before clicking Run to simulate an error.",
                c="dimmed",
                mb="xl",
            ),
            dmc.Stack(
                [
                    dmc.Paper(
                        dmc.Group(
                            [
                                dmc.Stack(
                                    [
                                        dmc.Text("Generate Mesh", fw=600, size="sm"),
                                        dmc.Text(
                                            "Builds the computational mesh from the geometry.",
                                            c="dimmed",
                                            size="xs",
                                        ),
                                    ],
                                    gap=2,
                                    style={"flex": 1},
                                ),
                                dmc.Switch(
                                    id="switch-generate-mesh",
                                    label="Simulate failure",
                                    color="red",
                                    size="sm",
                                ),
                                dmc.Button("Run", id="btn-generate-mesh", size="sm"),
                                html.Div(
                                    id="badge-placeholder-generate-mesh",
                                    style={"width": "180px"},
                                ),
                            ],
                            align="center",
                        ),
                        p="md",
                        withBorder=True,
                        radius="md",
                    ),
                    dmc.Paper(
                        dmc.Group(
                            [
                                dmc.Stack(
                                    [
                                        dmc.Text("Check Quality", fw=600, size="sm"),
                                        dmc.Text(
                                            "Validates mesh quality metrics against defined "
                                            "thresholds.",
                                            c="dimmed",
                                            size="xs",
                                        ),
                                    ],
                                    gap=2,
                                    style={"flex": 1},
                                ),
                                dmc.Switch(
                                    id="switch-check-quality",
                                    label="Simulate failure",
                                    color="red",
                                    size="sm",
                                ),
                                dmc.Button("Run", id="btn-check-quality", size="sm", disabled=True),
                                html.Div(
                                    id="badge-placeholder-check-quality",
                                    style={"width": "180px"},
                                ),
                            ],
                            align="center",
                        ),
                        p="md",
                        withBorder=True,
                        radius="md",
                    ),
                ],
                gap="sm",
            ),
        ],
        style={"maxWidth": 720, "margin": "40px auto", "padding": "0 16px"},
    )
)

# %%
# Initialise the badges in a callback
# -------------------------------------
#
# The badges are created inside a callback so that in a real SAF-based
# solution the project URL can be injected via the GLOW ``DashClient``. In this
# example, the ``MOCK_API_URL`` is passed directly.


@callback(
    Output("badge-placeholder-generate-mesh", "children"),
    Input("url", "pathname"),
    prevent_initial_call=False,
)
def init_badge_generate_mesh(pathname: str) -> TransactionMethodStatusBadge:
    """Add a TransactionMethodStatusBadge for the Generate Mesh method."""
    return TransactionMethodStatusBadge(
        url=MOCK_API_URL,
        step_name=STEP_NAME,
        method_name=GENERATE_MESH_METHOD,
        aio_id="badge-generate-mesh",
        label_props={"children": "Generate Mesh:"},
        interval_props={"interval": 2000},
    )


@callback(
    Output("badge-placeholder-check-quality", "children"),
    Input("url", "pathname"),
    prevent_initial_call=False,
)
def init_badge_check_quality(pathname: str) -> TransactionMethodStatusBadge:
    """Add a TransactionMethodStatusBadge for the Check Quality method."""
    return TransactionMethodStatusBadge(
        url=MOCK_API_URL,
        step_name=STEP_NAME,
        method_name=CHECK_QUALITY_METHOD,
        aio_id="badge-check-quality",
        label_props={"children": "Check Quality:"},
        interval_props={"interval": 2000},
    )


# %%
# Enable "Check Quality" only after "Generate Mesh" completes
# -------------------------------------------------------------
#
# The ``status_badge`` sub-component of each badge exposes its current status
# string as its ``children`` property (for example, ``"COMPLETED"``).  A simple
# callback reads that value and keeps the Check Quality **Run** button disabled
# until the Generate Mesh method has finished successfully.


@callback(
    Output("btn-check-quality", "disabled"),
    Input(TransactionMethodStatusBadge.ids.status_badge("badge-generate-mesh"), "children"),
)
def toggle_check_quality_button(generate_mesh_status: str | None) -> bool:
    """Enable the Check Quality button only when Generate Mesh is completed."""
    return (generate_mesh_status or "").upper() != "COMPLETED"


# %%
# Add callbacks to simulate transactions
# ----------------------------------------
#
# Each button has its own callback that reads the corresponding switch state,
# marks the method as ``"running"`` immediately, then schedules the final status
# (``"completed"`` or ``"failed"``) after a short delay — just as a real SAF
# transaction method would.  The callback returns ``True`` to the badge's
# ``activate_monitoring`` store, which starts the polling interval.


@callback(
    Output(
        TransactionMethodStatusBadge.ids.activate_monitoring("badge-generate-mesh"),
        "data",
    ),
    Input("btn-generate-mesh", "n_clicks"),
    State("switch-generate-mesh", "checked"),
    prevent_initial_call=True,
)
def run_generate_mesh(n_clicks: int, should_fail: bool) -> bool:
    """Start the Generate Mesh transaction and activate monitoring."""
    if not n_clicks:
        raise PreventUpdate

    # Convert step and method names to the format expected by the mock server endpoint (hyphenated)
    step_name_hyphenated = STEP_NAME.replace("_", "-")
    generate_mesh_method_hyphenated = GENERATE_MESH_METHOD.replace("_", "-")

    requests.post(
        f"{MOCK_API_URL}/steps/{step_name_hyphenated}:{generate_mesh_method_hyphenated}",
        json={"status": "running"},
    )

    def _finish() -> None:
        time.sleep(4)
        status = "failed" if should_fail else "completed"
        requests.post(
            f"{MOCK_API_URL}/steps/{step_name_hyphenated}:{generate_mesh_method_hyphenated}",
            json={"status": status},
        )

    threading.Thread(target=_finish, daemon=True).start()
    return True


@callback(
    Output(
        TransactionMethodStatusBadge.ids.activate_monitoring("badge-check-quality"),
        "data",
    ),
    Input("btn-check-quality", "n_clicks"),
    State("switch-check-quality", "checked"),
    prevent_initial_call=True,
)
def run_check_quality(n_clicks: int, should_fail: bool) -> bool:
    """Start the Check Quality transaction and activate monitoring."""
    if not n_clicks:
        raise PreventUpdate

    # Convert step and method names to the format expected by the mock server endpoint (hyphenated)
    step_name_hyphenated = STEP_NAME.replace("_", "-")
    check_quality_method_hyphenated = CHECK_QUALITY_METHOD.replace("_", "-")

    requests.post(
        f"{MOCK_API_URL}/steps/{step_name_hyphenated}:{check_quality_method_hyphenated}",
        json={"status": "running"},
    )

    def _finish() -> None:
        time.sleep(4)
        status = "failed" if should_fail else "completed"
        requests.post(
            f"{MOCK_API_URL}/steps/{step_name_hyphenated}:{check_quality_method_hyphenated}",
            json={"status": status},
        )

    threading.Thread(target=_finish, daemon=True).start()
    return True


# %%
# Run the app
# -----------

if __name__ == "__main__":
    app.run()
