File size: 5,292 Bytes
1c730d1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
// Copyright (c) 2025-2026, RTE (https://www.rte-france.com)
// This Source Code Form is subject to the terms of the Mozilla Public License, version 2.0.
// If a copy of the Mozilla Public License, version 2.0 was not distributed with this file,
// you can obtain one at http://mozilla.org/MPL/2.0/.
// SPDX-License-Identifier: MPL-2.0

// Game-Mode beginner-assistance wiring, extracted from App.tsx to keep the
// hub under its size ceiling. Registers a handler on `gameBridge` that drives
// the workspace from a lever hint (the "most-used levers" panel):
//   - single-click ('inspect'): fill the Inspect field, center the NAD on the
//     element (resolving an injection / coupling switch to its home VL), and
//     open that substation's SLD so the beginner can inspect it;
//   - double-click ('simulate'): run the mapped action directly — a catalogue
//     branch disco/reco, or a coupling maneuver at the resolved VL. Magnitude-
//     free injection / PST levers carry no action, so they degrade to inspect
//     with a hint to set the amount in the SLD.
//
// App keeps ownership of the collaborators (diagrams + the two simulate
// entry points + the SLD opener) and passes them in, so this stays a thin
// orchestrator and the bare (non-game) app never touches the bridge.

import { useCallback, useEffect } from 'react';
import { api } from '../api';
import { gameBridge, type LeverInteractionMode } from '../game/gameBridge';
import { notifyError, notifyInfo } from '../utils/notifications';
import type { LeverInteraction } from '../types';
import type { DiagramsState } from './useDiagrams';

export interface LeverInteractionParams {
    diagrams: DiagramsState;
    /** Simulate a catalogue action (branch disco/reco) by id. */
    handleSimulateUnsimulatedAction: (actionId: string) => Promise<void>;
    /** Simulate a coupling maneuver (switch toggle) at a resolved VL. */
    handleSimulateLever: (spec: { voltageLevelId: string; switches: Record<string, boolean> }) => Promise<void>;
    /** Open the SLD overlay for a voltage level. */
    handleVlOpen: (vlName: string) => void;
}

export function useLeverInteraction(params: LeverInteractionParams): void {
    const { diagrams, handleSimulateUnsimulatedAction, handleSimulateLever, handleVlOpen } = params;

    const handleLeverInteraction = useCallback(
        async (interaction: LeverInteraction, mode: LeverInteractionMode) => {
            const { inspectQuery, category, simulate } = interaction;
            // The overflow tab isn't a NAD — center on the contingency tab instead.
            const tab = diagrams.activeTab === 'overflow' ? 'contingency' : diagrams.activeTab;

            // Fill the Inspect field with the clicked element (both modes). We
            // center explicitly below, so use the plain setter that doesn't pin
            // a focus tab (an injection / switch name never matches a NAD id,
            // which would otherwise leave that pin dangling).
            diagrams.setInspectQuery(inspectQuery);

            // A catalogue action (branch disco/reco) simulates directly by id.
            if (mode === 'simulate' && simulate?.actionId) {
                await handleSimulateUnsimulatedAction(simulate.actionId);
                return;
            }

            // Resolve the element's home VL for everything that isn't a branch
            // (a branch spans two VLs and is centered directly by its own id).
            let homeVl: string | null = null;
            if (category !== 'branch') {
                try {
                    const { voltage_level_ids } = await api.getElementVoltageLevels(inspectQuery);
                    if (voltage_level_ids.length === 1) homeVl = voltage_level_ids[0];
                } catch {
                    // Resolution is best-effort — fall through to centering on the id.
                }
            }

            // A coupling maneuver simulates directly once we know its VL.
            if (mode === 'simulate' && simulate?.switches) {
                if (homeVl) await handleSimulateLever({ voltageLevelId: homeVl, switches: simulate.switches });
                else notifyError('Could not locate the substation for this maneuver.');
                return;
            }

            // Magnitude-free lever double-clicked: it can't be simulated as-is,
            // so guide the operator to set the amount in the SLD, then fall
            // through to inspect (locate + open the substation).
            if (mode === 'simulate' && !simulate) {
                notifyInfo('Set the amount in the substation diagram, then Simulate.');
            }

            // Inspect: center the NAD, and open the SLD when the lever lives in
            // one substation (an injection or a coupling switch → a single VL).
            if (homeVl) {
                diagrams.zoomToElement(homeVl, tab);
                handleVlOpen(homeVl);
            } else {
                diagrams.zoomToElement(inspectQuery, tab);
            }
        },
        [diagrams, handleSimulateUnsimulatedAction, handleSimulateLever, handleVlOpen],
    );

    useEffect(() => {
        if (!gameBridge.isGameMode()) return;
        gameBridge.registerLeverHandler(handleLeverInteraction);
    }, [handleLeverInteraction]);
}