// Copyright 2026 Kushview, LLC <info@kushview.net>
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <memory>

#include <element/juce/audio_basics.hpp>
#include <element/node.hpp>
#include <element/parameter.hpp>
#include <element/signals.hpp>
#include <element/taptempo.hpp>

namespace element {

class MidiMapping;
class Session;

/** Applies an incoming MIDI message to some destination (a node parameter,
    tempo, transport, ...).

    Targets are pure with respect to MIDI hardware: apply() is intended to be
    called on the message thread and can be unit tested by feeding messages
    directly.
*/
class MappingTarget
{
public:
    virtual ~MappingTarget() = default;

    /** True if this target can currently be applied. */
    virtual bool isValid() const = 0;

    /** Apply a matching MIDI message.
        @param message  The incoming note or controller message.
        @param toggle   For note events: latch on each note-on vs momentary.
    */
    virtual void apply (const juce::MidiMessage& message, bool toggle) = 0;
};

//=============================================================================
/** Targets a node parameter (regular index, or a special Enabled/Bypass/Mute or
    Input/Output gain parameter). */
class ParameterTarget : public MappingTarget
{
public:
    ParameterTarget (const Node& node, int parameterIndex);
    ~ParameterTarget() override = default;

    bool isValid() const override;
    void apply (const juce::MidiMessage& message, bool toggle) override;

private:
    Node model;
    ProcessorPtr object;
    ParameterPtr parameter; // null when targeting a special parameter
    int parameterIndex { -1 };

    void applyToParameter (const juce::MidiMessage&, bool toggle);
    void applySpecial (const juce::MidiMessage&, bool toggle);
    void applyGain (const juce::MidiMessage&, bool toggle);
};

//=============================================================================
/** Targets the session tempo as a tap-tempo control: each matching note-on, or
    each recognised controller edge, counts as a tap and the averaged BPM is
    written to the session, which the audio engine picks up on the message
    thread. */
class TempoTarget : public MappingTarget
{
public:
    /** @param sessionData     The session tree whose tempo is written.
        @param tapTempo         Shared tap-tempo accumulator (owned by MappingEngine)
                                so UI and MIDI taps contribute to the same state.
        @param tempoTapApplied  Fired on every recognised tap so the UI can flash
                                the TAP button; owned by MappingEngine, so it
                                outlives this target.
        @param triggerMode      Controller trigger mode, see MidiMapping::isTriggerEdge().
        @param triggerValue     Threshold for the "above" trigger mode. */
    TempoTarget (const juce::ValueTree& sessionData,
                 TapTempo& tapTempo,
                 Signal<void()>& tempoTapApplied,
                 const juce::String& triggerMode = "above",
                 int triggerValue = 67);
    ~TempoTarget() override = default;

    bool isValid() const override;
    void apply (const juce::MidiMessage& message, bool toggle) override;

private:
    juce::ValueTree session;
    TapTempo& tapTempo;
    Signal<void()>& tempoTapApplied;
    juce::String triggerMode;
    int triggerValue;
    int lastControllerValue { -1 }; // edge state, so a held knob taps only once
};

//=============================================================================
/** Resolves a MidiMapping into a concrete target. Returns nullptr if the
    mapping cannot currently be resolved (e.g. missing node).
    @param tapTempo         Shared accumulator passed to a TempoTarget, if created.
    @param tempoTapApplied  Flash signal forwarded to a TempoTarget, if created. */
std::unique_ptr<MappingTarget> createTarget (const MidiMapping& mapping, Session& session, TapTempo& tapTempo, Signal<void()>& tempoTapApplied);

} // namespace element
