"""Generation mode and simple-mode event wiring helpers.

This module contains mode-switch and simple-mode related handlers to keep
``events.__init__`` as a thin facade.
"""

from typing import Any, Sequence

import gradio as gr

from .. import generation_handlers as gen_h
from .context import GenerationWiringContext
from acestep.constants import MODE_TO_TASK_TYPE


def _on_repaint_mode_change(mode, current_strength, memory):
    """Update slider value and interactivity when repaint mode changes."""
    if mode == "conservative":
        new_memory = current_strength if 0.0 < current_strength < 1.0 else memory
        return gr.update(value=0.0, interactive=False), new_memory
    if mode == "aggressive":
        new_memory = current_strength if 0.0 < current_strength < 1.0 else memory
        return gr.update(value=1.0, interactive=False), new_memory
    return gr.update(value=memory, interactive=True), memory


def _on_repaint_strength_change(strength, current_mode):
    """Auto-switch mode when slider hits boundary values."""
    if strength == 0.0 and current_mode != "conservative":
        return gr.update(value="conservative"), gr.update(interactive=False)
    if strength == 1.0 and current_mode != "aggressive":
        return gr.update(value="aggressive"), gr.update(interactive=False)
    if current_mode != "balanced" and 0.0 < strength < 1.0:
        return gr.update(value="balanced"), gr.update(interactive=True)
    return gr.skip(), gr.skip()


def register_generation_mode_handlers(
    context: GenerationWiringContext,
    mode_ui_outputs: Sequence[Any],
    auto_checkbox_inputs: Sequence[Any],
    auto_checkbox_outputs: Sequence[Any],
) -> None:
    """Register generation mode and simple-mode handlers."""

    generation_section = context.generation_section
    results_section = context.results_section
    dit_handler = context.dit_handler
    llm_handler = context.llm_handler

    # Shared handler for mode-change and initial page load — extracted to
    # avoid duplicating the lambda and to keep both call sites in sync.
    def _handle_mode_change(mode: str, prev: str | None):
        """Proxy mode-change handling for both .change() and .load() events."""
        return gen_h.handle_generation_mode_change(mode, prev, llm_handler)

    mode_change_inputs = [
        generation_section["generation_mode"],
        generation_section["previous_generation_mode"],
    ]
    dcw_default_outputs = [
        generation_section["dcw_mode"],
        generation_section["dcw_scaler"],
        generation_section["dcw_high_scaler"],
    ]

    # ========== Generation Mode Change ==========
    generation_section["generation_mode"].change(
        fn=_handle_mode_change,
        inputs=mode_change_inputs,
        outputs=mode_ui_outputs,
    )

    # ========== Initial Mode State on Page Load ==========
    # compute_mode_ui_updates() controls visibility for 44 mode-dependent UI
    # components (think_checkbox, generate_btn_row, src_audio_row, etc.) but
    # is only triggered via the .change() event above.  Gradio does not fire
    # .change() for the initial value assignment, so mode-dependent state is
    # never applied on first render — causing the Think checkbox (and
    # potentially other components) to be missing on page load.
    # This .load() event fires once on page load to initialize all
    # mode-dependent UI state using the same handler.
    load_event = context.demo.load(
        fn=_handle_mode_change,
        inputs=mode_change_inputs,
        outputs=mode_ui_outputs,
    )
    load_event.then(
        fn=gen_h.update_dcw_defaults_for_think,
        inputs=[generation_section["think_checkbox"]],
        outputs=dcw_default_outputs,
    )

    generation_section["think_checkbox"].change(
        fn=gen_h.update_dcw_defaults_for_think,
        inputs=[generation_section["think_checkbox"]],
        outputs=dcw_default_outputs,
    )

    # ========== Extract Mode: Auto-fill caption from track_name ==========
    generation_section["track_name"].change(
        fn=gen_h.handle_extract_track_name_change,
        inputs=[
            generation_section["track_name"],
            generation_section["generation_mode"],
        ],
        outputs=[generation_section["captions"]],
    )

    # Validate source audio eagerly so users get immediate feedback on invalid files.
    generation_section["src_audio"].change(
        fn=lambda src_audio: gen_h.validate_uploaded_audio_file(src_audio, "source"),
        inputs=[generation_section["src_audio"]],
        outputs=[generation_section["src_audio"]],
    )

    # ========== Extract/Lego Mode: Auto-fill audio_duration from src_audio ==========
    generation_section["src_audio"].change(
        fn=gen_h.handle_extract_src_audio_change,
        inputs=[
            generation_section["src_audio"],
            generation_section["generation_mode"],
        ],
        outputs=[generation_section["audio_duration"]],
    )

    # ========== Simple Mode Instrumental Checkbox ==========
    generation_section["simple_instrumental_checkbox"].change(
        fn=gen_h.handle_simple_instrumental_change,
        inputs=[generation_section["simple_instrumental_checkbox"]],
        outputs=[generation_section["simple_vocal_language"]],
    )

    generation_section["random_desc_btn"].click(
        fn=gen_h.load_random_simple_description,
        inputs=[],
        outputs=[
            generation_section["simple_query_input"],
            generation_section["simple_instrumental_checkbox"],
            generation_section["simple_vocal_language"],
        ],
    )

    generation_section["create_sample_btn"].click(
        fn=lambda query, instrumental, vocal_lang, temp, top_k, top_p, debug: gen_h.handle_create_sample(
            llm_handler, query, instrumental, vocal_lang, temp, top_k, top_p, debug
        ),
        inputs=[
            generation_section["simple_query_input"],
            generation_section["simple_instrumental_checkbox"],
            generation_section["simple_vocal_language"],
            generation_section["lm_temperature"],
            generation_section["lm_top_k"],
            generation_section["lm_top_p"],
            generation_section["constrained_decoding_debug"],
        ],
        outputs=[
            generation_section["captions"],
            generation_section["lyrics"],
            generation_section["bpm"],
            generation_section["audio_duration"],
            generation_section["key_scale"],
            generation_section["vocal_language"],
            generation_section["simple_vocal_language"],
            generation_section["time_signature"],
            generation_section["instrumental_checkbox"],
            generation_section["generate_btn"],
            generation_section["simple_sample_created"],
            generation_section["think_checkbox"],
            results_section["is_format_caption_state"],
            results_section["status_output"],
            generation_section["generation_mode"],
        ],
    ).then(
        fn=gen_h.uncheck_auto_for_populated_fields,
        inputs=list(auto_checkbox_inputs),
        outputs=list(auto_checkbox_outputs),
    ).then(
        # After creating a sample the generation_mode is set to "Custom"
        # (task_type="text2music"). If task_type was already "text2music" the
        # task_type.change event does not fire, leaving instruction_display_gen
        # stale (e.g. the LM instruction from a previous Remix session). Force
        # it to the correct DiT text2music instruction unconditionally.
        fn=lambda: gen_h.update_instruction_ui(
            dit_handler, MODE_TO_TASK_TYPE["Custom"], None, [], False, None
        ),
        inputs=[],
        outputs=[generation_section["instruction_display_gen"]],
    )

    generation_section["repaint_mode"].change(
        fn=_on_repaint_mode_change,
        inputs=[
            generation_section["repaint_mode"],
            generation_section["repaint_strength"],
            generation_section["repaint_strength_memory"],
        ],
        outputs=[
            generation_section["repaint_strength"],
            generation_section["repaint_strength_memory"],
        ],
    )
    generation_section["repaint_strength"].change(
        fn=_on_repaint_strength_change,
        inputs=[
            generation_section["repaint_strength"],
            generation_section["repaint_mode"],
        ],
        outputs=[
            generation_section["repaint_mode"],
            generation_section["repaint_strength"],
        ],
    )
